Hosts and adapters

Guild Stack runs inside a host: the AI coding tool you already use. The 3.0.0 release was tested end to end on two, Claude Code and Codex, and ships release evidence for both. Fourteen more hosts are registered with honest, capability-by-capability support rows — never assumed.

Try it. The first line shows which host this run is bound to. The second renders your settings into each registered host's config shape, so you can see what every host would get:

/guild:config models inspect
/guild:config show --render

The Power layer covers the two integration surfaces you can see and tune: hooks and the MCP servers. The Architect layer covers how a host presents itself, the full capability matrix, and the degrade chain.

Hooks

Guild Stack registers 10 hook events with 24 entries in hooks/hooks.json. Each entry runs one script. Most hooks record telemetry or inject context. A few can block or ask. This page lists all of them.

Timeline of the 10 hook events across a Guild Stack session, from SessionStart through tool use and Stop.
Open full size

Three rules hold for almost every hook:

  • It exits 0 on its own failure. A hook crash does not wedge a prompt or end a session.
  • It stays silent when there is no active run. Off a Guild Stack run, most hooks print nothing.
  • Scripts compile to committed bundles under hooks/dist/ (and hooks/agent-team/dist/). You run Node, not a build.

The exceptions to "exits 0" are noted below: using-guild-bootstrap exits 2 when a Guild Stack root fails closed, and the agent-team TaskCreated and TaskCompleted hooks can reject.

Check that hooks fire

After a restart, start a Guild Stack run and look for .guild/runs/<run-id>/logs/v1.4-events.jsonl. It holds hook_event rows and, after tool use, tool_call rows. Older hosts may skip newer events such as PreToolUse and PreCompact; the handlers fall through without breaking the session. If the bootstrap card shows but no run files appear, run /guild:maintain audit and read the installed hooks/hooks.json.

SessionStart (5 entries)

ScriptWhat it does
bootstrap.shPrints a short Guild Stack status block and command list. Always exits 0.
detect-guild-version.jsDetects a .guild/ tree. For a v1, mixed, or corrupt tree it surfaces a migrate, dry-run, or skip prompt. It never changes the tree. A v2 tree or no tree is silent.
update-check.jsReads a machine cache and prints a one-line update signal. It never touches the network on the session path. A stale cache is refreshed by a detached background run. defaults.update.mode sets notify (default), auto, or off. Dev installs are silent.
using-guild-bootstrap.jsInjects the using-guild gateway as model context, so the model knows when to reach for Guild Stack. On a Guild Stack root it checks the storage layout and fails closed (exit 2) if plugin or compile outputs are missing.
session-reanchor.jsAfter a compact or resume, re-injects the run's identity, phase, next gate, and lead posture. It fires only on source of compact or resume, only for an open run, and only in the lead's session.

UserPromptSubmit (3 entries)

ScriptWhat it does
check-skill-coverage.shFirst runs the lifecycle gate (see below); if it blocks, nothing else prints. Otherwise it may print a one-line nudge when your prompt names a domain with no shipped specialist template. It nudges at most once per session.
capture-telemetry.jsAppends one event line for the prompt to the run's logs/v1.4-events.jsonl, and mirrors it to the legacy events.ndjson.
run-trace-start.jsEmits the run_started marker once per run. Later prompts are no-ops. Silent if no run id is resolvable.

PreToolUse (1 entry, matcher *)

ScriptWhat it does
pre-tool-use.jsWrites a pre-call sidecar entry that PostToolUse joins to compute latency. It is also where the security checks live: capability-scope enforcement, the security-event log, and the MCP description hash check. A pinned MCP tool whose live description hash differs from its pin gets an ask decision. It also hosts the backend-degradation guard, the tier-dispatch guard, and a per-tool lifecycle bound.

The MCP pin is described in MCP servers. The defaults.dispatch.block_unmarked_lanes key makes the backend-degradation guard strict.

PostToolUse (4 entries, two matchers)

Matcher * runs two entries. Matcher Write|Edit runs two more.

MatcherScriptWhat it does
*capture-telemetry.jsAppends to the legacy events.ndjson mirror. It skips the canonical file for PostToolUse, so tool calls are not logged twice.
*post-tool-use.jsPairs the call with its pre entry, computes latency_ms, and writes the tool_call event. It sweeps stale pre entries (over 5 minutes) as errors. It scrubs durable surfaces in place. It also drains the lead's queued write requests, but only for a verified single work-loop redirect or evolve-loop --apply command.
Write|Editcomms-format-lint.jsLints the file just written for the handoff receipt format. Advisory by default. With GUILD_COMMS_FORMAT_ENFORCE=1 it blocks (exit 2) only on a receipt with no, duplicate, or invalid guild.handoff.v2 block.
Write|Editgate-outcome-writer.jsWhen review.md or verify.md is written under a run, records a gate outcome in run.yaml. It writes nothing if the report is ambiguous.

PreCompact (1 entry)

ScriptWhat it does
pre-compact.jsRecords a hook_event for the compaction. For an open run in the lead's session it prints plain-text instructions so the run's identity, phase, and next gate survive into the compact summary. It prints nothing without an open run.

SubagentStop (2 entries)

ScriptWhat it does
capture-telemetry.jsAppends a SubagentStop event with the specialist name. It runs first.
maybe-reflect.jsA dev-team reflection branch. It acts only when GUILD_ENABLE_DEVTEAM_REFLECT=1, at least 3 subagent stops are logged, and a spec exists. Otherwise it is a silent no-op.

Stop (5 entries)

Stop fires at the end of every assistant turn, not only at session end. The entries run in this order.

ScriptWhat it does
maybe-reflect.jsRuns the reflection gate. It passes only if a specialist was dispatched, a file was edited, and no error event exists. On pass it writes summary.md and prints GUILD_REFLECT run_id=<run-id> for the orchestrator.
run-trace-close.jsCloses the active run and emits the run_closed marker. If a closed run sees later tool activity, it re-closes so provenance stays current.
learning-backstop.jsGuarantees a guild.learning_checkpoint.v1 record exists for every phase in phases_log. It adds a no-op record, marked as a backstop, where the model forgot. It never overwrites an existing one.
lean-lead-guard.jsThe lean-lead advisory. See below.
lifecycle-gate.jsThe close-time lifecycle gate. See below.

Agent-team events (3 entries)

These three belong to the experimental tmux agent-team backend. TaskCreated, TaskCompleted, and TeammateIdle are gated on CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1. Without it they pass through.

EventScriptWhat it does
TaskCreatedtask-created.jsBlocks a task with no owner specialist, no output contract, or a depends-on: reference to a task id that is in no plan file.
TaskCompletedtask-completed.jsValidates the guild.handoff.v2 envelope, extracts learnings[] into the run, and checks the markdown receipt sections. It upserts the lane in run-state.json. It records a submission only. It does not dismiss the worker.
TeammateIdleteammate-idle.jsNudges an idle teammate whose task has no receipt. It never exits non-zero. It reports a liveness verdict from the heartbeat file.

The heartbeat timeout is tier-scaled: 180000 ms for cheap, 600000 for mid, 1200000 for powerful. An explicit defaults.heartbeat_timeout_ms always wins.

The two advisory guards

Both guards read the run's own telemetry. Neither bookkeeps anything new. Both are silent without an active run, and both fire once per threshold crossing, so your next prompt always goes through.

Lean-lead guardLifecycle gate
Question it asksIs the lead doing lane work inline while lanes are still open?Is the session doing any work outside the lifecycle?
EventStopUserPromptSubmit (through check-skill-coverage.sh) and Stop
CountsThe lead's own Edit and Write calls with open lanesThe lead's own ad-hoc Bash, Edit, and Write calls since the last lifecycle touch
AnchorLast compactionLast lifecycle skill call or specialist dispatch
EffectAdvisory context. Never denies a tool call.Blocks the prompt once, or at Stop blocks a build whose lanes finished with no review.md or verify.md
Enable keydefaults.lean_lead.enabled (default true)defaults.lifecycle_gate.enabled (default true)
Threshold keydefaults.lean_lead.hands_on_edit_threshold (default 8)defaults.lifecycle_gate.adhoc_activity_threshold (default 20)
Session overrideGUILD_LEAN_LEAD_OVERRIDE=1GUILD_LIFECYCLE_GATE_OVERRIDE=1
One-prompt overridenone[guild:gate-override] in the prompt

A threshold below 1 or a non-integer is ignored and the default applies. After a compaction the lean-lead guard fires on the first lead edit of the new cycle, then returns to its every-N-edits cadence. The lifecycle gate delivers its correction to the model on the next accepted prompt, because a block reaches you but not the model. The close-time Stop gate re-arms only when the set of missing artifacts changes, under a per-run fire cap.

NameUsed by
defaults.update.mode, defaults.update.cadence_hoursupdate-check.js
defaults.dispatch.block_unmarked_lanes, GUILD_BLOCK_UNMARKED_LANESpre-tool-use.js
defaults.heartbeat_timeout_msteammate-idle.js
GUILD_COMMS_FORMAT_ENFORCE=1comms-format-lint.js
GUILD_ENABLE_DEVTEAM_REFLECT=1maybe-reflect.js
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1the three agent-team hooks

See Config reference for every key.

MCP servers

Guild Stack ships two optional MCP servers. Both are read-only, both run over stdio, and Guild Stack works end to end without them. Together they give specialists ranked access to the wiki and a queryable view of past runs.

ServerMCP idReadsTools
guild-memorywiki.guild/wiki/**wiki_search, wiki_get, wiki_list
guild-telemetrytrace.guild/runs/**trace_list_runs, trace_summary, trace_query, trace_cost_rollup

Both are ids of one binary. The plugin starts them as node ${CLAUDE_PLUGIN_ROOT}/runtime/guild-mcp.js wiki and node ${CLAUDE_PLUGIN_ROOT}/runtime/guild-mcp.js trace. The binary is a committed build, so you only need Node.

Try it

With the plugin loaded, ask the host to list your runs and then summarize one. The host calls trace_list_runs, then trace_summary with a run_id from the result. trace_list_runs is the place to start. trace_summary needs a run id; trace_query and trace_cost_rollup take an optional run_id and span all runs without it.

guild-memory tools

Every tool takes an optional cwd. See Root resolution.

ToolParametersOutput
wiki_searchquery (string), category, limit, cwd{ results: [...] }. Each result has path, category, type, frontmatter_category, score, excerpt, confidence, source_refs.
wiki_getpath (string, relative to the wiki root), cwd{ path, frontmatter, body }. A path outside the wiki root is rejected.
wiki_listcategory, updated_since (ISO-8601 date or datetime), cwdEvery page, sorted by path. Fields: path, category, type, frontmatter_category, title, updated, confidence.

Search notes:

  • Ranking is BM25. Title tokens count double. The title comes from frontmatter title, else the first H1, else the filename.
  • category is always the wiki directory segment, such as decisions. A page's own frontmatter category is returned separately as frontmatter_category.
  • limit defaults to 20 and is capped at 200.
  • updated_since reads updated_at (falling back to updated). Pages with neither field are excluded when the filter is on.

guild-telemetry tools

ToolParametersOutput
trace_list_runssince, limit (1 to 1000), cwd{ runs, total }. Each run has run_id, event_count, started_at, ended_at. since filters on ended_at, falling back to started_at.
trace_summaryrun_id (required), cwd{ run_id, source, summary }. source is file when a stored summary.md exists, or synthesized when it is rebuilt in memory with no write.
trace_queryrun_id, event, specialist, since, limit (1 to 10000), cwd{ events, total }. Each event is annotated with its run_id. Sorted by run_id, then ts. Omit run_id to search all runs.
trace_cost_rolluprun_id, since, cwdRun-wide totals plus by_tier, by_model, and by_specialist. Each row is { key, input, output, cached, total }, sorted by total descending. llm_event_count says how many events carried tokens. Omit run_id to roll up all runs.

The server reads logs/v1.4-events.jsonl first and falls back to the legacy events.ndjson. Fields are normalized so every tool works on both shapes. Malformed lines are skipped.

Root resolution

Each call picks its root in this order. The first match wins.

Stepguild-memoryguild-telemetry
1. The cwd argument<cwd>/.guild/wiki/<cwd>/.guild/runs/
2. An env varGUILD_MEMORY_WIKI_ROOTGUILD_TELEMETRY_CWD
3. The server process cwd<cwd>/.guild/wiki/<cwd>/.guild/runs/

The per-call cwd wins so that one long-lived server can answer for several federated child repos. The env var is used mainly by tests, when no cwd is given.

--no-cwd-fallback

Some hosts launch the server from the plugin install, not your project. The generated Codex manifest does this: it declares cwd: ".", which is the plugin payload root, and passes --no-cwd-fallback. Step 3 would then serve Guild Stack's own bundled data instead of yours. In this mode:

  • Step 3 is removed. There is no process-cwd default.
  • The per-call cwd and the env var must be absolute paths.
  • A call with no usable root fails closed with isError: true and a message that says what to pass.

Without the flag, as in Claude Code and a dev checkout, behavior is unchanged.

Read-only, declared and checked

Both servers declare a capability block in .mcp.json:

Fieldguild-memoryguild-telemetry
read_onlytruetrue
read_paths.guild/wiki/**.guild/runs/**
write_pathsnonenone
networknonenone

The declaration is a definition-file field. The audit compares it with observed behavior, and undeclared egress or writes are a violation. The read-only claim is also checked mechanically: the sources contain no writeFile or appendFile calls. The servers also detect the storage layout on start and never upgrade it. A future layout fails closed.

Tool-description hash pinning

A compromised MCP server could change a tool's description after you trusted it. Guild Stack guards against that by pinning a SHA-256 of each description. On every MCP tool call, PreToolUse recomputes the live hash and compares it with the pin. A mismatch returns an ask decision, so you confirm before the call proceeds.

Two kinds of pin exist:

  • Shipped pins. runtime/mcp-descriptions.pins.json holds a hash for each of the 7 Guild Stack tools. The compile step writes it together with the binary, so there is no re-pin ritual. Pins are keyed by server. A third-party server that names a tool wiki_search does not inherit Guild Stack's pin.
  • Your pins. mcp.tool_description_hashes maps a tool name to its hash, for third-party tools. Re-pin with /guild:config update-mcp-hashes.

If a pinned tool's live description cannot be read, the result is "unverifiable". For a Guild Stack tool the verified binary stands in for the description and the call is allowed with a recorded event. If no such voucher applies, a Guild Stack tool asks. A third-party tool proceeds and the event is recorded.

See Config reference for mcp.* keys, and Hooks for PreToolUse.

Host adapters

Guild Stack v3 runs on two live hosts: Claude Code and Codex. The v3.0.0 conformance evidence covers both, at 31/31 activated-host captures each.

Host adapter stack: host and surface lookup to rung and receipt.
Open full size

The surface each host sees

The command surface folds from 22 to 13 active commands. Host-indexed skills drop from 110 to 17.

On stable 3.0.0, eleven print-only alias files remain. Each prints its new spelling and exits. The upcoming beta (next) releases delete them, leaving exactly the 13 dispatchers; every retired spelling keeps a sub-verb route on a surviving command.

Claude CodeCodex
Manifest.claude-plugin/plugin.json .codex-plugin/plugin.json
Skill pathnot pinned in the matrix.agents/skills/guild/**
Activation prooflauncher-declared beta receipt probe on guild:guild-quality

Both manifests belong to one version surface. You edit .claude-plugin/plugin.json and run npm run sync:claude-install. That regenerates every version-bearing file, the Codex manifest included.

Activation, per host

  • Claude Code. The capture launcher declares GUILD_ACTIVATION_CHANNEL=beta from the attested release. The SessionStart hook mints a beta receipt only under that declaration. The verifier no longer accepts the retired check that guessed the channel from the version string alone.
  • Codex. The activation probe targets guild:guild-quality and tolerates exactly one trailing newline.

Source identity

On either host, an activated capture stages a blob-verified plugin-source twin at the attested commit (guild.plugin_source_identity.v1), and verify enforces it.

The host matrix

Guild Stack routes on capability columns, never on a host name. Every host has one registry row (guild.host_registry.v1) that embeds a capability row (guild.host_capabilities.v1). This page is the reference for both. For how the two live hosts present themselves, read Host adapters. For the rules an extension must meet, read Extension points.

Matrix of the 16 registered Guild Stack hosts against capability columns, with Claude Code and Codex marked live-verified and the rest marked inferred.
Open full size

The 16 host ids

Only two hosts are live-verified: claude-code-cli (Claude Code) and codex-cli (Codex). The other 14 are in INFERRED_HOSTS, which means at least one rung or column is not yet confirmed on a live host.

Host idFamilySurfaceInstallabilityResult adapterDispatch selectableLive-verified
claude-code-cliclaudeclinativenoyesyes
codex-clicodexclitargetyesyesyes
pi-clipiclitargetnoyesno
antigravity-cliantigravityclitargetnoyesno
cursorcursorclitargetnoyesno
github-copilotcopilotclitargetnoyesno
opencodeopencodeclitargetnoyesno
rovo-devrovoclitargetnoyesno
agents-fileagentsfiletargetnonono
kiroagentsfiletargetnonono
qoderagentsfiletargetnonono
traeagentsfiletargetnonono
claude-code-appclaudeappnonenonono
claude-code-webclaudeappnonenonono
codex-appcodexappnonenonono
claude-ai-connectorclaudeappnonenonono

Three columns are independent. installability (native, target, none) says whether a package can be installed. result_adapter says whether a cross-review adapter exists. dispatch_selectable says whether a lane can run there. A host can be dispatch-selectable without a result adapter.

agents-file is a render target, not a launcher. Its adapter's dispatch returns degraded with no command. kiro, qoder and trae bind to it (adapter_binding: "agents-file") and share the agents family. trae-cn is not a separate id; it folds into trae. Legacy aliases normalize into this set and are never authoritative ids.

The registry also carries a per-row provenance of verified or inferred. Separately, six rows read verified for their columns: the two live hosts, pi-cli, antigravity-cli, github-copilot and opencode. For pi-cli and antigravity-cli the source notes "3 columns + detection live-checked" with the browser rung still inferred. Use INFERRED_HOSTS as the live-verification line.

Capability rows

A guild.host_capabilities.v1 row has these blocks, after host_kind, family and surface_kind:

BlockWhat it records
packageinstallable, installability (verified or target), manifest_format, update behavior. A host with no apply path carries apply: "none" and degrades to notify. In the upcoming beta (next) releases, every registered host's update row is pinned and exercised end to end against simulated registries — command posture, auto gating, cache cadence and offline silence; live-host soak stays a separate, operator-run check.
bootstrap, commands, skills, agentsHow Guild Stack is loaded and how its surface renders.
injectionPer-dispatch definition and skill-bundle injection facts; support is verified, target or absent.
hooksBooleans for ten events: session_start, user_prompt_submit, pre_tool_use, post_tool_use, stop, pre_compact, subagent_stop, task_created, task_completed, teammate_idle.
permissions, dispatchPermission modes and dispatch behavior.
interaction, sessions, structured_output, artifactsPrompting, resume, structured output, artifacts.
toolsPer-tool strength: native, bridge, emulated or none.
mcp, modelsMCP transports and model tiers.

validateHostCapabilitiesV1 validates a row. The shipped constants are CLAUDE_CAPABILITIES, CODEX_CAPABILITIES and AGENTS_FILE_CAPABILITIES. Rows for other hosts are derived from the registry. A probed runtime row is planned for .guild/hosts/<host-id>/capability.json; the source calls it a later phase.

The degrade chain

A capability a host cannot advertise degrades through a fixed ladder, strongest first:

native > wrapped > bridged > emulated > degraded

Loss rank runs from 0 (native) to 4 (degraded). The ladder is resolved per adapter surface: interaction, session, semantic_tool, browser. Examples from the shipped table:

SurfaceClaude CodeCodexagents-file
interactionnativewrappedbridged
sessionnativewrappedemulated
browserbridgedbridgeddegraded

Every resolution produces a guild.degradation_receipt.v1 with the surface, host, chosen rung, a degraded flag (true below native) and a reason. An unknown host resolves to degraded and records that. Guild Stack never assumes a capability. The four app-style hosts (claude-code-app, claude-code-web, codex-app, claude-ai-connector) are degraded on interaction, session and browser.

The INFERRED honesty gate

A rung or column that has not been checked on a live host is marked INFERRED in source. isHostInferred(host) is true for every host in INFERRED_HOSTS. A host leaves that set only when all its cells are live-verified. New-CLI hosts take a wrapped-CLI posture from a minimal inferred capability row: not installable, every uncertain block degraded. New IDE hosts copy the agents-file rungs. Do not present an inferred host as supported beyond its row.

config show --render

config show --render renders the resolved config and the phase-permission block into the native config shape of every registered host: all 16 HOST_IDS, keyed by host id: 8 CLI surfaces, 4 file surfaces (agents-file, kiro, qoder, trae), and 4 app/connector refuse hosts. It is read-only and reads capability columns from the registry, not host names. A field a host cannot express is stubbed and listed in _unsupported.

It fails closed on leaks:

  • A value whose source is workspace-local or project-local (gitignored) is withheld from the shared render and recorded in _redactions with reason local-scope.
  • Every emitted string is scanned against the canonical secret patterns plus your secrets_policy.redaction_patterns. A match becomes <SECRET-REDACTED:label>.
  • With the durable-artifact default of closed, a leak sets ok: false and blocked: true, meaning a real shared write would be refused. The inspection command flags BLOCKED and still exits 0.

Adapter-author notes

  • The contract version is guild.host_adapter.v1.0.0. An adapter implements ten operations: capabilities, bootstrap, preflight, dispatch, collect, renderCommandSurface, renderPackage, renderPermissionDecision, resolveModelParams, memory.
  • Each call returns a guild.host_adapter_receipt.v1 with status ok, degraded or unavailable, a reason, and an optional degradation receipt.
  • The baseline adapter fails closed. It reports the registry profile and returns explicit degraded results, never launching a binary or claiming parity.
  • A new pane-dispatchable host needs a HostKind member, a HOST_REGISTRY_ROWS row, a bespoke PaneAdapter or the generic WrappedCliPaneAdapter, and a capability row (host-types.ts). A host with adapter_binding: "agents-file" gets no HostKind and is never pane-dispatched.
  • Start the row as inferred. Flip it only after a live-host check, and drop it from INFERRED_HOSTS only when every cell is verified.
  • Where a host lacks pre_tool_use_ask, Guild Stack turns a PreToolUse ask into a deny. See Security and trust.