The workspace git snapshot is pinned per session so a rebuild (compaction,
/compress) replays it instead of re-probing a repo that moved. Two gaps let
the rebuild re-probe anyway and rewrite the system prompt mid-session:
- The pin was keyed by resolve_context_cwd(), which is None when no cwd is
bound (CLI launch dir) and the path once the TUI /compress binds the
session cwd: the same directory under two keys. Key by the directory the
probe actually inspects (resolve_context_cwd() or resolve_agent_cwd()).
- An agent that did not build the session's prompt (resumed, a fresh
gateway/TUI agent whose first act is /compress, a kill -9 restart) had
no pin, so its first rebuild probed git now instead of replaying the
session-start bytes. Seed the pin from the prompt the session already
sends (cached copy, else its persisted row), only when that prompt names
this cwd and its snapshot's Root covers it.
reset_session_state still drops the pin, so /new, /resume and /branch
re-snapshot at their own session start.
- _transcript_row_texts re-implemented agent.message_content.flatten_message_text
and the api_content sidecar rule; the note can only land on a user row,
so the transcript scan now skips assistant/tool rows (the bulk of the bytes).
- Three sites computed "names of agent.tools"; tools.mcp_tool_agent gains
agent_tool_names() used by the switch note and conversation_loop, which
also stops importing the private _def_name across modules. The name list
is only captured when a switch was announced.
- split_runtime_boundary() is the single owner of the runtime-block
rpartition/END check for both identity_line_value and
_stored_prompt_matches_runtime.
- platform_surface_hint was a public alias of _platform_hint; the function is
now platform_hint (its docstring pointed at the pre-move module).
- consume_gateway_turn_context_notes and consume_surface_switch_note share
_pop_turn_note so the two one-shot channels have identical semantics.
- platform check hoisted above the transcript scan.
Move the six surface-switch helpers out of the conversation_loop facade
into agent/surface_switch.py (AGENTS.md: new behaviour goes in a topical
sibling), and fold the review findings on #104494:
- MoA and codex_app_server turns never stamp the api_content sidecar, so
the staged note could not be read back from the transcript and was
re-sent on every turn after a switch. Those modes now skip the note
(stored prompt still reused).
- The announced surface was parsed with split(".") — a plugin platform
with a dot in its name would never compare equal and re-stage the note
every turn. The note now closes the name with a fixed terminator.
- One identity-line parser (identity_line_value) shared by
_stored_prompt_matches_runtime and the switch detector instead of two
copies of the runtime-boundary/rpartition logic; tool names via the
existing tools.mcp_tool_agent._def_name; the transcript scan is bounded
to the last 200 rows (it ran every turn over the whole history).
- consume_surface_switch_note reduced to a plain pop; developer-guide
prompt-assembly.md updated (Platform is no longer an identity field);
17 new tests trimmed to 10 (same-shape pin/retire variants folded).
Restoring Platform as an identity field still turns 5 tests red.