The agent.open implementation — load the definition, resolve the
provider/CLI, create the block, enqueue the layout insert, and register
the controller. Extracted from the RPC closure so the HTTP surface
(POST /api/v1/agent/open, backing the OpenAgent MCP tool) shares this
exact code path instead of re-deriving it: the TOCTOU serialization via
AGENT_OPEN_LOCKS (reagent P1 on PR #2059), the ABF provider shadowing,
the resume-session seeding guard, and the CLI path resolution all live
here once. Same extraction shape as open_pane / handle_pane_open.
See docs/reports/REPORT_AGENT_OPEN_API_GAP_2026_09_06.md.
agent.open, but launching into an existing, empty agent pane
({view:"agent"}, the picker) instead of creating a new one and
inserting it into the layout. Used by Layouts → Open layout…
(SPEC_LAYOUT_FILES_2026_09_25.md §3.5), which rebuilds the saved split
tree first and then launches each agent into its placeholder — so every
check agent.open makes (admission, one live instance, provider, CLI,
resume, config files) applies unchanged. If the agent is already open in
this tab, that pane is returned and the placeholder is left as a picker.
Identity M4b-4 (spec §6.5.8): record agent.open’s launch of user agent
agent_id on block_id — a lifecycle-only update, never
instance_create with a fresh record (whose fold would blank the agent’s
account, bundle and workspace) — and stamp the block with the row id.
A row that is gone (the agent deleted mid-open — the delete handlers do
not take AGENT_OPEN_LOCKS) or a failed write fails the open: the
spawn would otherwise resolve to nothing, tokenless and past the
credential gate (Codex P1 on #3584). A failed stamp is only logged — for a
user agent the block’s agentId already names its row.
Identity M4b-4 (spec §6.5.8): a user agent’s local row, backfilled from
the shared registry if it is another channel’s — and confirmed, or the
open fails: without a local row the spawn would resolve to nothing, with
no UID/token and no credential gate, exactly the process M4b closes (Codex
P1 on #3584). agent_def_list listed the agent, so a missing row here is a
failed backfill, not a missing agent.
Resolve the (env_var, value) pair to inject for this agent’s model
vendor override, if any — redirects the harness at a non-default
backend (e.g. ANTHROPIC_BASE_URL for a claude-provider agent).
Pure — no I/O — so it’s directly unit-testable without the full
async spawn-time harness this is called from.
The agent agent.open opens: a user agent (is_seeded == 0), named by
definition id or by display name (ASCII case-insensitive) — its id first,
then its name. Only when no user agent answers is a template consulted,
and then only to refuse it with TEMPLATE_NOT_OPENABLE: opening one would
launch a session that is no agent’s (spec §6.5.8, template sessions); an
agent is created from a template in the picker. A template’s id is its
lowercase name (claude), which a user agent created from it shares
case-insensitively, so templates must never be matched first.