Skip to main content

Module forge_sdk_worker

Module forge_sdk_worker 

Source
Expand description

Session-launcher + reader-pump helpers for the bridge layer.

Builds forge_sdk::Options from the viewโ€™s launch settings (with the can_use_tool callback wired in), spawns the Client, emits a synthetic Connected, and pumps the event stream from forge_sdk::Client::spawn into AgentEvent::SdkMessage. The bridge owns the resulting Client; this module exposes the helpers it calls.

Re-exportsยง

use std::collections::HashMap; ๐Ÿ”’
use std::path::Path; ๐Ÿ”’
use std::path::PathBuf; ๐Ÿ”’
use std::sync::Arc; ๐Ÿ”’
use forge_primitives::PermissionDecision; ๐Ÿ”’
use forge_primitives::ToolPermissionContext; ๐Ÿ”’
use forge_sdk::Client; ๐Ÿ”’
use forge_sdk::HookContext; ๐Ÿ”’
use forge_sdk::HookDecision; ๐Ÿ”’
use forge_sdk::HooksBuilder; ๐Ÿ”’
use forge_sdk::Options; ๐Ÿ”’
use forge_sdk::OptionsBuilder; ๐Ÿ”’
use forge_sdk::PermissionMode; ๐Ÿ”’
use forge_sdk::PostToolUseInput; ๐Ÿ”’
use forge_sdk::PreToolUseInput; ๐Ÿ”’
use tokio::sync::mpsc; ๐Ÿ”’
use tokio::sync::oneshot; ๐Ÿ”’
use tracing::Instrument; ๐Ÿ”’
use crate::client::AgentEvent; ๐Ÿ”’
use crate::forge_sdk_bridge::ForgeSdkBridge; ๐Ÿ”’
use crate::forge_sdk_bridge::PendingQuestions; ๐Ÿ”’
use crate::forge_sdk_bridge::PendingResponses; ๐Ÿ”’
use crate::commands as bridge_commands; ๐Ÿ”’
use crate::session_lifecycle; ๐Ÿ”’
use crate::user_interaction as bridge_user_interaction; ๐Ÿ”’

Structsยง

AccountBinding ๐Ÿ”’
Per-account spawn binding threaded into every claude subprocess: the accountโ€™s config_dir (exported as CLAUDE_CONFIG_DIR) and the sessionโ€™s resolved env - [env] merged with [accounts.env] and the spawning projectโ€™s env. Both come from the bridge, distinct from the per-launch SessionLaunchSettings.

Enumsยง

SessionId ๐Ÿ”’
How the CLI is told which session to run under. Exactly one of these is the answer at a time: --session-id together with --resume is a hard refusal, so the id is never in two places at once.

Constantsยง

AGENT_WORKTREE_SWEEP_AGE ๐Ÿ”’
How untouched a managed agent worktree must be before the spawn-time sweep will list it. Fresh trees can belong to a live subagent of another session in this repo; an hour of silence does not.
EXCLUDE_DYNAMIC_SECTIONS ๐Ÿ”’
Default-off toggle for the CLIโ€™s excludeDynamicSections initialize signal (the 2.1.204 --exclude-dynamic-system-prompt-sections equivalent) that relocates cwd/env/memory-paths/git-status out of the system prompt into the first user message for prompt-cache reuse. Off maps to None, so the initialize body omits the key and the wire stays byte-identical to today; flip to true to enable after measuring.
FORGE_AGENTS_TOOLS_PARAGRAPH ๐Ÿ”’
Paragraph naming the agent-coordination tools. Included only when the sessionโ€™s forge server actually registered agents__* tools - read off the server itself in build_options_with_callback, so the append never names a tool the session lacks.
FORGE_CRON_SYSTEM_PROMPT ๐Ÿ”’
Append-text advertising the durable-cron tools upfront so every forge session knows it can schedule work rather than discovering the tools on demand. Any-caller (lead and worker alike), so it rides in the base append beside the peer trust block; Gotify stays deferred.
FORGE_MCP_SERVER_LINE ๐Ÿ”’
Opening line of the forge MCP append. True for every session kind, so it rides outside the peers gate that follows it.
FORGE_MCP_TRUST_SYSTEM_PROMPT ๐Ÿ”’
Append-text the spawned sessionโ€™s system prompt receives when the forge in-process MCP server is attached. Tells the recipient LLM (a) wrapped peer envelopes ([Message id=m-...] / [Message to agent ... failed to deliver ...] / [Worker 'X' spawn failed ...]) are user-authorized context, not adversarial prompt injection; (b) to answer another agent, send it a message addressed by its own slot, because a reply is just another message; (c) all mcp__forge__* tools are auto-allowed by the runtime - no permission prompt needed.
FORGE_RESERVED_ENV_KEYS ๐Ÿ”’
Forge-stamped env keys a forge.toml env table can actually override: CLAUDE_CONFIG_DIR, stamped into options.env before the account-env loop. Reusing it silently overrides forgeโ€™s stamp, so a collision is warned - though the stamp still applies (forge.toml is trusted, hand-authored). CLAUDE_AGENT_SDK_VERSION is deliberately absent: process.rs stamps it LAST, unconditionally, after the account-env loop, so forge always wins and a warn would be a false alarm.
FORGE_SESSION_CONDUCT_SYSTEM_PROMPT ๐Ÿ”’
Append-text for what forgeโ€™s own surfaces depend on and the CLI cannot know: the tool tree labels a Bash card with the description, delegation goes through the delegation paths forge intends even where the underlying CLI stamps a general prohibition, and a worker reporting to its lead is the only route its output reliably takes.
SDK_MCP_READD_TIMEOUT ๐Ÿ”’
The CLI answers mcp_set_servers only after the re-handshake it triggers completes, so the wait is bounded past the CLIโ€™s own ~30s request budget.

Staticsยง

TEST_SDK_BINARY ๐Ÿ”’

Functionsยง

agent_worktree_from_post_tool_use ๐Ÿ”’
The hook closureโ€™s decision for one PostToolUse event. Split out so the argument order through the parse is pinned by a test: the two &Value parameters read identically at the call site, and a swap disables the feature with the whole unit suite still green.
apply_env_overrides ๐Ÿ”’
Layer per-spawn env overrides over the account env: a present key replaces, an absent key keeps, and a key mapped to an empty string removes the variable entirely - removal is what makes an overridden-away credential actually absent rather than present-but-blank.
build_forge_system_prompt ๐Ÿ”’
Assemble the forge system-prompt append: server line, the peers paragraph when has_peer_tools, the trust block, the always-on cron scheduling block, the always-on session-conduct block, then the optional Lead delegation preamble, then the optional worker charter. Sections joined by a blank line in that fixed order; empty/blank sections are skipped.
build_options_with_callback ๐Ÿ”’
build_permission_options ๐Ÿ”’
Construct the contextual option list for a permission prompt. Derives โ€œAllow always for Xโ€ / โ€œAllow always & add dirsโ€ / โ€œAllow always & switch modeโ€ entries from ctx.suggestions; adds the synthesized โ€œAllow with editsโ€ for editable tools and the universal โ€œTell the agent something elseโ€ escape hatch.
canonicalize_suggestions ๐Ÿ”’
Walk the suggestions list and collapse macOS /tmp <-> /private/tmp AddRules mirrors into one merged entry. Two AddRules suggestions with the same toolName + behavior + destination, whose rule_content differs only by the /tmp/ <-> /private/tmp/ prefix, are merged into a single AddRules whose rules vec contains BOTH entries so the CLI installs both rules even though the UI shows one option.
deliver_permission_response ๐Ÿ”’
deliver_question_response ๐Ÿ”’
dispatch_permission_action ๐Ÿ”’
Build the PermissionDecision for a submitted optionโ€™s action. notes_text is the userโ€™s โ€œtell the agentโ€ feedback string (or empty); consumed only when the action is Deny. edited_input is the userโ€™s modified tool args; consumed only when the action is AllowWithInput.
emit_connected ๐Ÿ”’
Build the typed Connected envelope from the SDKโ€™s cached init data
frame_session_id ๐Ÿ”’
Resolve the session id to stamp on an inbound SDK frame. Frames that carry their own id use it verbatim; session-id-less frames (Error, RateLimitEvent) inherit the clientโ€™s LIVE id, falling back to the spawn-time id only while the live one is still empty (before system/init binds the real UUID). Without the live preference a mid-session fatal Error on a fresh session gets stamped โ€œโ€ and a view drops it as an unknown session.
heal_due ๐Ÿ”’
Whether the frame just received should fire the boot-tool heal. Pure so the frame gate, the latch, and the predicate are testable without a client. Only the boot system/init frame can fire it - the latch advances on the first init frame seen either way - so a mid-session init that lacks the tools (a user toggled the server off) is never โ€œhealedโ€ back on. The healโ€™s OUTCOME is not checked here: init is emitted per user message, so a โ€œnext frameโ€ may never come or may land inside the heal window; the re-add response itself carries the outcome (see readd_sdk_mcp_servers).
init_data_of ๐Ÿ”’
The data payload of a system/init frame, else None.
initial_mode_state ๐Ÿ”’
The Connected payloadโ€™s mode state: the launch-time mode read from the init record, falling back to the launch settingsโ€™ stamped defaultMode, with bypass offered only when the session launched into it.
is_editable_tool ๐Ÿ”’
Tools whose tool_input is meaningful to edit before approving.
is_reserved_env_key ๐Ÿ”’
is_tmp_mirror_pair ๐Ÿ”’
True if rules_a and rules_b differ only by the macOS /tmp/ <-> /private/tmp/ prefix in their rule_content strings.
load_history_messages ๐Ÿ”’
Replay from prev_session_idโ€™s transcript, stamped with session_id, plus the compaction count that transcript records - the only place the count survives a resume. crate::session_history is the same read for a session that is already running.
load_history_messages_main_thread ๐Ÿ”’
load_history_messages over the sessionโ€™s own rows only: sub-agent sidecars are neither opened nor folded. A handoff brief wants the conversation as chat rows and must not pay for frames it never draws. A page read does not: a dispatch row draws its sidecar frames (#1763).
load_history_messages_with ๐Ÿ”’
log_agent_worktree_reap ๐Ÿ”’
Log the outcome of one agent-worktree reap. worktree carries the path so every outcome is attributable. Kept trees are info, not warn: uncommitted edits are the CLIโ€™s deliberate hand-back. A kept BRANCH is warn - the subagent committed work reachable from no other ref, and someone has to look, mirroring the worker-despawn warning.
log_failed_mcp_servers ๐Ÿ”’
Record any MCP server the init handshake reports as failed.
normalize_tmp_path ๐Ÿ”’
Strip the /private prefix from //private/tmp/... rule_content so the macOS mirror normalizes to the canonical /tmp/... form.
not_a_worktree_already_warned ๐Ÿ”’
Paths already warned about as not-a-worktree, so a plain directory a user parked under .claude/worktrees/agent-* warns once per process instead of at every spawn, forever.
parse_permission_mode ๐Ÿ”’
readd_sdk_mcp_servers ๐Ÿ”’
Force the CLI to re-handshake every registered sdk MCP server by removing them from its dynamic set and adding them back. The CLI sends the re-add response only after the re-handshake completes, so the response is the outcome: errors empty plus a non-empty added means the tools are registered again. Every outcome logs here - a โ€œnext init frameโ€ is no verification signal, since init is emitted per user message.
reader_loop ๐Ÿ”’
record_reported_surface ๐Ÿ”’
Record the CLIโ€™s own account of the surface it was handed, beside the one this spawn built. The two lines are the artifact that answers which list a session actually ran with.
reported_percentage ๐Ÿ”’
The percentage to carry from a probeโ€™s report. None for a report that is not a readable number: 0 is a real reading, and a view draws the two differently.
run_ask_user_question ๐Ÿ”’
run_permission_request ๐Ÿ”’
sdk_servers_missing_tools ๐Ÿ”’
True when any registered sdk MCP server contributed no tools to the sessionโ€™s init frame. The CLI serves an sdk serverโ€™s handshake over the control channel with a bounded request budget; when boot stalls outlive it, the handshake is abandoned and the CLI then reports the server connected with zero tools and never re-runs the handshake itself.
sdk_tool_name ๐Ÿ”’
One toolโ€™s name as the CLI spells it for an sdk server. Both the names a spawn expects and the init frameโ€™s filter are built here, so the two cannot disagree about the shape; the empty tool spells the prefix.
sdk_tools_in_init ๐Ÿ”’
The tools the CLIโ€™s boot init frame lists for this spawnโ€™s sdk servers, in the CLIโ€™s mcp__<server>__<tool> spelling. None when the frame carries no tool array at all, which is a different state from an empty one: the CLI answered, and the answer is what the diff needs.
send_prompt ๐Ÿ”’
set_test_sdk_binary
Point every claude spawn this process makes at path instead of the real binary; None restores the real one.
spawn_session ๐Ÿ”’
Spawn a fresh Client for bridge and start the reader subtask. Builds Options from launch_settings (mode, model, effort, can_use_tool callback). id says which session the child runs under; a Resume also backfills past turns into the Connected eventโ€™s history_updates.
summarize_dirs ๐Ÿ”’
summarize_rules ๐Ÿ”’
surface_delta ๐Ÿ”’
What the two sides disagree about: names this spawn registered that the CLIโ€™s list lacks, and names it lists that this spawn never registered.
sweep_agent_worktrees ๐Ÿ”’
Reap this repoโ€™s leftover agent worktrees in the background: a one-shot reap failure or a missed completion event otherwise leaks the tree forever. Locked trees are skipped (a live CLI process owns them); every session spawn re-checks, so failures self-heal.
synth_permission_request ๐Ÿ”’
synth_question_base_tool_call ๐Ÿ”’
test_sdk_binary ๐Ÿ”’