v0.4.9
L5 — Memory containment (gemini)
The 0.4.8 surface-isolation matrix closed five Gemini channels (native body, operator memory path, tool surface, GEMINI.md hierarchical discovery, MCP whitelist). 0.4.9 closes a sixth — memory persistence. pi-shell-acp is the canonical memory authority on the pi side (semantic-memory + Denote llmlog); no backend may run a parallel memory layer that survives across sessions. The Claude and Codex sides already enforce this (Claude via CLAUDE_CONFIG_DIR overlay + disallowedTools + skillPlugins:[], Codex via -c memories.{generate,use}_memories=false + -c history.persistence="none" + -c features.memories=false). 0.4.9 adds the matching closure for Gemini.
experimental.memoryV2:false+experimental.autoMemory:falsepinned in overlaysettings.json. memoryV2 is Gemini's "editGEMINI.md/MEMORY.mddirectly viaedit/write_file" mode (defaulttrueupstream); autoMemory is the background extraction agent that writes.patchfiles into a project memory inbox (defaultfalseupstream). TheGEMINI_SYSTEM_MDoverride already replaces Gemini's system prompt body, so memoryV2's prompt steering never reaches the model — but the explicit pin holds even if the override path ever breaks (defense in depth). The overlaysettings.jsonclosure widens from 14 keys to 16.<configDir>/{tmp,history,projects}/swept at every spawn.ensureGeminiConfigOverlaynow unconditionallyrmSyncs these three subtrees and recreates them empty, so anytmp/<slug>/memory/MEMORY.md,tmp/<slug>/.inbox/<kind>/*.patch, command-history subtree, orprojects/directory content written by a previous gemini session does not carry. The binary-owned<configDir>/projects.jsonfile is a separate overlay-private project map and is not part of this sweep; the operator's native~/.gemini/projects.jsonstill never flows through. The L4 closure (context.fileNamesentinel +memoryBoundaryMarkers:[]) already keeps Gemini from reading memory files; the L5 sweep is filesystem hygiene + defense-in-depth in case L4 ever breaks. The constant is renamed fromGEMINI_OVERLAY_EMPTY_DIRStoGEMINI_OVERLAY_SWEPT_DIRSto reflect the stronger contract.- Root-level
<configDir>/GEMINI.mdand<configDir>/MEMORY.mdswept by the existing stale-entry cleanup. Neither name is onGEMINI_OVERLAY_BINARY_OWNED, so the cleanup loop's fall-throughrmSyncremoves them at every spawn. The model can still try to write these files within a session viawrite_file, but they cannot survive into the next one. check-backends124 → 134 assertions. Two assertions for the newexperimental.{memoryV2,autoMemory}keys; five for the L5 sweep behaviour (pre-seedtmp/<slug>/memory/MEMORY.md, autoMemory inbox patch, rootGEMINI.md, rootMEMORY.md→ confirm none survive the nextensureGeminiConfigOverlaycall); three for the engraving substitution defuse below.
Engraving substitution defuse (gemini)
Recent gemini-cli (post 0.42-nightly, packages/core/src/prompts/utils.ts applySubstitutions) walks the GEMINI_SYSTEM_MD override file and rewrites ${AgentSkills}, ${SubAgents}, ${AvailableTools}, and ${<toolName>_ToolName} with their runtime values. The substitution is intended for gemini-shipped templates, but the same pass runs over the operator-supplied override file — so any ${...} literal inside an engraving (e.g. a shell example) silently mutates on the gemini backend only, while landing verbatim on Claude (_meta.systemPrompt) and Codex (-c developer_instructions).
defuseGeminiSubstitutionsslides the$and{apart with a zero-width space (U+200B) before writingsystem.md. Every substitution regex misses; the model still reads the same visual string, but Gemini's carrier bytes are intentionally not byte-identical to Claude/Codex for affected${...}literals. Restores the cross-backend invariant that the same engraving is not semantically interpolated differently on Gemini. Documented inline at the function definition with the gemini-cli source pointer.
Internal — Backend dependency bumps
@agentclientprotocol/claude-agent-acp0.31.4 → 0.32.0. SDK pin stays at@agentclientprotocol/sdk@0.21.0; transitive@anthropic-ai/claude-agent-sdkadvances 0.2.121 → 0.2.126. Visible bridge-side change: Claude session updates may now carry_meta._claude/originonusage_updatenotifications when the underlying message is a task-notification followup (autonomous work triggered by a system message rather than the user prompt). The bridge's event-mapper passes_metathrough unchanged, so the new field flows to pi without code change. InternaltoolUpdateFromEditToolResponse→toolUpdateFromDiffToolResponserename is consumed inside claude-agent-acp; bridge does not import the symbol.@zed-industries/codex-acp0.12.0 → 0.13.0. Codex 0.124 → 0.128.0. Rust agent-client-protocol pin stays at=0.11.1(same as Zed and the TS SDK 0.21.0 wire). codex-acp internals shifted to asyncAuthManager+EnvironmentManagerand added aThreadGoalUpdatedevent — emitted as plain agent text viaclient.send_agent_text("Goal updated (active): …")and forwarded by the bridge as ordinary text. Mode IDs (read-only/auto/full-access) and the-c features.<key>=falsegating surface are unchanged.- devDeps
@mariozechner/pi-{ai,coding-agent,tui}0.70.2 → 0.73.0. Aligns the typecheck fence with the version operators are running. pi-mono 0.71.0 removed the built-ingemini-cliprovider (Token Plan API route), not thegoogleAPI source —getModels("google")still shipsgemini-3-flash-preview(1,048,576 context, 65,536 maxTokens), socheck-modelsassertions hold. ExtensionAPI gainedgetEditorComponent()accessor +thinking_level_selectevent +ProviderConfig.name+MessageEndEventResult— all additive; the bridge does not subscribe to any of these surfaces.
Documentation
- AGENTS.md Hard Rule #9 widens the surface-isolation matrix to include L5 — Memory containment (per backend), and re-states pi-shell-acp's role as the canonical memory authority.
- Comment drift fix in
acp-bridge.ts: the placeholder note "if (systemMdResolution.value)always takes the override branch" now reflects the upstreamvalue && !isDisabledsemantic gemini-cli adopted along with the prompt-provider refactor.
Internal — Release flow standardization
- Removed
scripts/release.shand thepackage.jsonreleasescript entry. The--notes-from-tag --title v<version>pattern paired with lightweight tags was the root cause of v0.4.7 / v0.4.6 / v0.4.1 / v0.3.x shipping with empty release bodies and bare-version titles. The script produced lightweight tags (no annotation message), then--notes-from-tagrendered an empty body — consistent low-quality releases by design. Removed rather than patched. .pi/prompts/make-release.mdrewritten as a self-contained release procedure. Pre-flight 0–7 (argument shape / clean tree / tag absent / CHANGELOG section / version match /pnpm check/ghauth + repo/permission consistency / push dry-run) → tag → push → agenda stamp (pi:release:tag, release-page link) → CHANGELOG section extracted as--notes-file→gh release create --title "v<version>" --notes-file …→gh release viewverify → Google Chat notify → temp-file cleanup. Each step's bash block restatesVERSION="$ARGUMENTS"and re-derivesREMOTE/REPO_URL/REPO_NAME/REPO_TAGso slash-command bash invocations split across the agent do not silently drop variables. Title is fixed tov<version>; theme lives in the CHANGELOG body's first H3, not the title.npm publishand downstream consumer bumps (agent-config 4-file pin) are explicitly out of scope.