frameworks — AgentTeamsModule¶
Per-framework adapter classes that control how rendered agent content is adjusted for a specific target framework.
See also: convert and bridge drive these adapters for team migration and runtime bridging; the interoperability guide covers the CAI normalization path.
Source:
agentteams/frameworks/
FrameworkAdapter (Abstract Base Class)¶
Source:
agentteams/frameworks/base.py
Abstract interface for per-framework agent file generation. All concrete adapters inherit from this class.
Abstract Properties¶
framework_id¶
Short identifier for this framework (e.g., 'copilot-vscode').
Type: str
Abstract Methods¶
render_agent_file(content, agent_slug, manifest)¶
Post-process rendered agent content for this framework.
This is the adapter step that turns the framework-agnostic output of render.render_all() into the final framework-specific file body.
Args:
content(str) — Rendered agent file content (placeholders already resolved).agent_slug(str) — Agent slug derived from the filename.manifest(dict[str, Any]) — Team manifest fromanalyze.build_manifest().
Returns: str — Framework-adjusted agent content.
render_instructions_file(content, manifest)¶
Post-process rendered copilot-instructions content.
Args:
content(str) — Rendered instructions content.manifest(dict[str, Any]) — Team manifest.
Returns: str — Framework-adjusted instructions content.
get_file_extension(file_type)¶
Return the file extension for a given file type.
Args:
file_type(str) —'agent','instructions', or'builder'.
Returns: str — Extension string including the dot (e.g., '.agent.md').
supports_handoffs()¶
Whether this framework supports YAML handoff blocks in agent files.
Returns: bool
get_agents_dir(project_path)¶
Return the default agent file directory for a given project path.
Args:
project_path(Path) — Root of the target project.
Returns: Path
Concrete Methods¶
finalize_output_path(rel_path, file_type)¶
Adjust an output path's extension for this framework. Default implementation: when file_type is agent or builder, rewrites the extension to this framework's get_file_extension(file_type) if it differs — e.g. this is what turns CopilotCLIAdapter's planned .agent.md paths into plain .md. Other file types pass through unchanged by default; ClaudeAdapter, AgentsMdAdapter, CodexAdapter (repo-root AGENTS.md two levels above .codex/agents), GrokAdapter (the same, above .grok/agents), and GooseAdapter override this further to relocate the instructions file to their framework-native root file (CLAUDE.md, AGENTS.md).
Args:
rel_path(str) — Relative output path.file_type(str) — Logical file type (agent,builder,instructions, etc.).
Returns: str — Path with adjusted extension.
render_builder_file(content, manifest)¶
Post-process the rendered team-builder meta-agent. Default implementation: identity (returns content unchanged), so the Copilot adapters emit the builder as a Markdown agent file. The others override it: GooseAdapter wraps the builder as a runnable recipe, so it is not a stray .md in the agents directory; CodexAdapter emits it as a Codex custom-agent TOML; AgentsMdAdapter emits it as a plain-Markdown .agents detail file through render_agent_file; and ClaudeAdapter overrides it to map a canonical tools: [...] front-matter line to Claude tool names (it appears only under write_policy, whose narrowing replaces the builder's Claude grant); a Claude-shaped builder passes through unchanged.
Args:
content(str) — Rendered builder template body.manifest(dict) — Team manifest.
Returns: str — Framework-shaped builder content.
extra_output_files(manifest)¶
Return additional (rel_path, content) files the framework emits that are not derived from a template. Default implementation: empty list. GooseAdapter overrides this to emit three files unconditionally: the repo-root .goosehints integrator alongside AGENTS.md, a references/goose-capabilities-reference.md reference doc, and a repo-root scripts/goose-run-resilient.py resilient-runner script (a dead-turn-detecting wrapper around goose run, read from this package's own scripts/ at generation time so the shipped copy can't drift). These files are emitted by the generate path and by convert_team, so a converted Goose team gets all three too. ClaudeAdapter also overrides this, shipping the constitutional PreToolUse hook with every generated Claude team: ../hooks/constitutional-gate.py (the hook itself) and, when the shipped example asset is present, ../settings.hooks.example.json (the block an operator merges into their own settings.json — never written directly, to avoid clobbering user config).
Args:
manifest(dict[str, Any]) — Team manifest.
Returns: list[tuple[str, str]] — (rel_path, content) pairs, relative to the agents directory.
has_skill_concept()¶
Whether this framework has a first-class skill concept. Default implementation: False. ClaudeAdapter overrides it to return True (Claude Code's skills/<slug>/SKILL.md directories); every other framework emits operational tool docs as reference documents instead. CodexAdapter also returns True (Codex skills at .agents/skills/<slug>/SKILL.md), and GrokAdapter inherits Claude's True (.grok/skills/<slug>/SKILL.md). Skill placement comes from two hooks: skill_output_rel_path(slug) (agents-dir-relative emit path; default ../ + tool_doc_rel_path) and skills_dir(agents_dir) (the skills root; default the skills sibling of the agents dir, which Codex overrides to <root>/.agents/skills).
Returns: bool
render_skill_file(content, slug, manifest)¶
Post-process a rendered operational tool-doc emitted as a skill. Default implementation: identity (returns content unchanged). Only frameworks with a first-class skill concept invoke this path; ClaudeAdapter overrides it to strip stray front matter/handoffs and prepend a minimal skill front-matter block (name + description), writing the body to .claude/skills/<slug>/SKILL.md.
Args:
content(str) — Rendered operational tool-doc body.slug(str) — Tool-doc slug.manifest(dict) — Team manifest.
Returns: str — Framework-shaped skill content.
tool_doc_rel_path(slug)¶
Project-root-relative placement path for an operational tool doc. Default implementation: references/ref-{base}-reference.md (stripping a leading tool- prefix from slug). ClaudeAdapter overrides it to skills/{slug}/SKILL.md, since Claude Code discovers a skill only as a directory containing SKILL.md.
Args:
slug(str) — Tool-doc slug.
Returns: str
framework_root_prefix()¶
Project-root-relative prefix of this framework's root dir. Default implementation: empty string (emitted paths are already project-root relative). ClaudeAdapter overrides it to .claude, used to build display paths like .claude/skills/<slug>/SKILL.md from tool_doc_rel_path; GrokAdapter overrides it to .grok.
Returns: str
required_front_matter_keys()¶
Front-matter keys every agent file of this framework must declare. Default implementation: empty tuple — this framework asserts no front-matter contract. Every adapter overrides this explicitly so "examined, and the answer is none" is distinguishable from "not yet examined": CopilotVSCodeAdapter → (name, description, user-invocable, tools, model); ClaudeAdapter → (name, description, tools); CopilotCLIAdapter, GooseAdapter, and AgentsMdAdapter → () (no front matter at all). CodexAdapter inherits AgentsMdAdapter's override. GrokAdapter → (name, description, tools, injectDefaultTools, mcpInheritance): the last two make tools: the declared exact limit (no live negative test yet; see STABILITY section 6).
Returns: tuple[str, ...]
handoff_delivery_mode()¶
Describe how the framework receives handoff semantics.
nativekeeps handoffs inline in the emitted agent file.manifeststrips inline handoff syntax from the visible prompt and, when extracted handoffs exist, preserves routing metadata inreferences/runtime-handoffs.json.nonemeans no handoff delivery mechanism is emitted.
Default implementation: "native" when supports_handoffs() is True, otherwise "none". ClaudeAdapter, CopilotCLIAdapter, and AgentsMdAdapter override this to return "manifest" explicitly (CodexAdapter overrides it back to "native": its handoffs travel inside each custom agent's developer_instructions), since they strip inline handoffs but still preserve extracted routing metadata.
Returns: str
extract_handoffs(content)¶
Extract handoff metadata from rendered agent content before adapter-specific stripping occurs. Default implementation: a concrete parser (~75 lines, not overridden by any built-in adapter) that reads YAML handoffs: entries from the front matter and the conventional ## Handoff Instructions body section, then dedupes by (agent, prompt).
Args:
content(str) — Rendered agent file content before framework stripping.
Returns: list[dict[str, Any]]
parse_agent_source(content)¶
Framework-native source parse for CAI export. Default implementation: returns None, telling interop.export_to_cai to use its standard Markdown path (front-matter name/description, wrapper-stripped body, capability/handoff extraction per framework). GooseAdapter overrides this to parse a recipe YAML directly into CAI export fields (name/description/body, mapped capability tokens, sub_recipes/load(...) handoffs).
Args:
content(str) — Rendered agent file content.
Returns: dict[str, Any] | None
framework_extensions_from_sources(parsed_sources)¶
Project-level framework-owned config captured on CAI export. Default implementation: returns {} (no project-level config). GooseAdapter overrides this to aggregate every parse_agent_source result's recipe config into the CAI document's framework_extensions.goose bucket (builtin extension names, recipe_parameters/recipe_response/recipe_retry).
Args:
parsed_sources(list[dict[str, Any]]) — Every non-Noneparse_agent_sourceresult collected during discovery.
Returns: dict[str, Any]
apply_framework_extensions(manifest, cai)¶
Merge this framework's CAI framework_extensions bucket into the import manifest stub. Default implementation: no-op (no framework-owned config). GooseAdapter overrides this to restore recipe configuration (recipe_parameters, recipe_response, recipe_retry, recipe_extensions, recipe_extensions_mode) at render time.
Args:
manifest(dict[str, Any]) — Import manifest stub being built.cai(dict[str, Any]) — The CAI document being imported.
Returns: None
CopilotVSCodeAdapter¶
Source:
agentteams/frameworks/copilot_vscode.py
Adapter for GitHub Copilot in VS Code.
- framework_id:
'copilot-vscode' - Output format:
.agent.mdwith YAML front matter - Handoffs: Native inline YAML
- Agents dir:
<project>/.github/agents/
Validates and normalizes YAML front matter; preserves all fields defined in the template.
Current behavior notes:
- Normalizes front matter while filtering
agentsreferences to generated team members. - Supports both
agents:flow-list and block-list syntax. - Filters handoff targets to generated team members.
- Preserves original formatting when membership is unchanged to avoid no-op cosmetic drift in generated outputs.
CopilotCLIAdapter¶
Source:
agentteams/frameworks/copilot_cli.py
Adapter for Copilot CLI.
- framework_id:
'copilot-cli' - Output format: Plain
.mdsystem prompts - Handoffs: Runtime manifest when handoffs are present (
references/runtime-handoffs.json) - Agents dir:
<project>/.github/copilot/
Strips YAML front matter and inline handoff blocks to produce plain Markdown system prompts compatible with the Copilot CLI, while preserving extracted handoff metadata in references/runtime-handoffs.json when any handoffs are present.
Current behavior notes:
- Handoff extraction happens before stripping so routing metadata can be persisted for runtime use.
handoff_delivery_mode()ismanifest, meaning routing metadata is delivered viareferences/runtime-handoffs.jsonrather than inline YAML.
ClaudeAdapter¶
Source:
agentteams/frameworks/claude.py
render_builder_filemaps a canonical buildertools:line (written bywrite_policynarrowing) to Claude names, e.g.Read, Grep, Glob. Claude Code can't launch a sub-agent whose tool names don't resolve.
Adapter for Claude Projects.
- framework_id:
'claude' - Output format: Claude front matter
.md(CLAUDE.md-compatible) - Handoffs: Runtime manifest when handoffs are present (
references/runtime-handoffs.json) - Agents dir:
<project>/.claude/agents/
Strips VS Code YAML and inline handoff blocks, then injects Claude-compatible front matter and preserves Markdown body content. Extracted handoff metadata is emitted separately in references/runtime-handoffs.json when any handoffs are present.
Current behavior notes:
- Uses manifest-based handoff delivery (
references/runtime-handoffs.json) for routing semantics. - Performs framework-specific output shaping while preserving rendered prompt body intent.
GooseAdapter¶
Source:
agentteams/frameworks/goose.py
Beta
The GooseAdapter is in beta: generate, convert, bridge, and interop-to-Goose are all supported and validated against the Goose CLI (see interop.md for the CAI-to-Goose import path — handoffs render as sub_recipes, tool scopes as recipe_extensions), but convert from claude/copilot-cli sources currently yields flat recipes. Its API and emitted-artifact shapes are not yet covered by the stability policy and may change in a minor release.
Adapter for Block / AAIF Goose recipes.
- framework_id:
'goose' - Output format: Goose recipe YAML (
.goose/recipes/*.yaml), schema version1.0.0 - Handoffs: Native, encoded inline in the recipes — orchestrator handoffs become
sub_recipes(with thesummonplatform extension); every deeper edge becomes asummonload("<slug>")reference (Goose forbids nested delegation). Noreferences/runtime-handoffs.jsonsidecar. - Agents dir:
<project>/.goose/recipes/ - Instructions: the team brief is written to the repo-root
AGENTS.md(viafinalize_output_path), and a.goosehintsintegrator (@AGENTS.md+ operational notes) is emitted viaextra_output_files.
Transforms each rendered Markdown agent into a recipe (title/description/instructions/extensions/optional sub_recipes). The team-builder is wrapped as a runnable team-builder.yaml recipe via the render_builder_file hook. get_file_extension('agent') and 'builder' both return .yaml.
Current behavior notes:
supports_handoffs()isTrue;handoff_delivery_mode()is'native'.- One delegation layer only (Goose constraint); deeper structure is preserved as in-context
summonload(...)references, not nested delegations. - AGENTS.md ownership (
guard_rendered_files→ the pinned_agents_md_ownership.goose_agents_md_decision).render_instructions_fileopens the file with a generated notice (GOOSE_AGENTS_MD_MARKER), so a later run classifies it asgoose. Native generation, convert and interop all go through the guard:
Existing AGENTS.md |
What goose does |
|---|---|
| Absent, or goose-generated | Writes the render (through --merge / --overwrite as usual), carrying the other targets' existing co-owner fences over in their on-disk order (grok_runtime, and Codex's codex_write_policy: CO_OWNER_FENCE_IDS); a duplicated or unbalanced one makes it skip the file with a warning |
| agentteams-fenced with no marker and a fence other than a co-owner fence (a legacy goose render) | Adopts it, with a notice |
Hand-written, a legacy unfenced goose render (indistinguishable), or fenced only by co-owner fences (grok_runtime, codex_write_policy) |
Never written; a warning says to remove AGENTS.md and re-render to hand it to goose |
| Codex, Grok or agents-md generated | Never written; same warning |
A symlink (itself or .goose/.goose/recipes), unreadable, malformed fences, or outside the project |
Never written |
AgentsMdAdapter¶
Source:
agentteams/frameworks/agents_md.py
Adapter for the cross-tool AGENTS.md standard.
- framework_id:
'agents-md' - Output format: Agent files:
.agents/<slug>.md(plain Markdown, no front matter); Instructions: repo-rootAGENTS.md - Handoffs: Inline handoffs removed from the body; routing preserved in
references/runtime-handoffs.json(handoff_delivery_mode()is'manifest') - Agents dir:
<project>/.agents/
AGENTS.md is an emerging cross-tool standard (Agentic AI Foundation / Linux Foundation, formed Dec 2025) read by many AI coding tools (Continue, Cursor, Cline, OpenAI Codex, Zed, Aider, Gemini CLI, Jules, and more). --framework agents-md emits one well-formed, framework-neutral AGENTS.md — the team brief: overview, conventions, agent roster + orchestrator routing — as the canonical entry those tools consume, plus the full per-specialist team under .agents/ for humans and agentteams' own tooling. The standard consumers read only AGENTS.md; .agents/<slug>.md files are detail files for humans/version-control, so AGENTS.md is kept self-sufficient rather than a directory of pointers.
Current behavior notes:
render_agent_filestrips the VS Code YAML front matter and handoff blocks, rewrites.github/agentspath references to.agents, and prepends a# {Name}heading; idempotent on re-render (no compounding duplicate headers).render_instructions_fileneutralizes the Copilot-authored instructions template — strips the leading SECTION-MANIFEST comment, retitles off "Copilot Instructions", rewrites paths, rewords Copilot-specific phrasing — while preserving the AGENTTEAMS fence markers.finalize_output_pathmaps the planned instructions path to the repo-rootAGENTS.md.required_front_matter_keys()is(), stated explicitly rather than left as an unexamined default.- Shared-namespace note:
--framework goosealso writes a repo-rootAGENTS.md; if a project uses both, the emitted file carries a one-line generated notice to that effect (seereferences/bridge-refresh-safety.md). - No ownership guard (known gap):
guard_rendered_filesis the base no-op, so this adapter overwrites a Codex-, Grok- or goose-generatedAGENTS.md, unlike those three adapters (remediationGROK-FU-R4-SIDE-B). - Generate-only: not a
--convert-from/--interop-from/--bridge-fromtarget — the CLI rejects those combinations.
CodexAdapter¶
Source:
agentteams/frameworks/codex.py
Adapter for the OpenAI Codex CLI. Emits each agent as a Codex custom agent, a TOML file under .codex/agents/. Subclasses AgentsMdAdapter only for the framework-neutral AGENTS.md rendering.
- framework_id:
'codex' - Output format: Agent files:
.codex/agents/<slug>.toml, with keysname(the slug; authoritative over the file name),description,developer_instructions(a TOML multi-line string), andsandbox_mode("workspace-write"when the canonical tools declareedit, otherwise"read-only"; omitted for a non-canonical or missing tool list).sandbox_modeis a declaration, not enforced (see below). Nomodel, provider or approval keys. Instructions: repo-rootAGENTS.md, written in full only when absent or already Codex-generated (underwrite_policy: "orchestrator-only", a goose-generated one gets Codex'scodex_write_policyblock; see below). - Handoffs:
native. Translated into a "Hand off to" list inside a fencedcodex_translationblock indeveloper_instructions, and parsed back byparse_agent_source. No sidecar. - Agents dir:
<project>/.codex/agents/(normalize_output_pathappends it to a project-root--output)
Current behavior notes:
tools:is stated as a self-imposed limit, carried verbatim (Codex does not enforce tool grants). Codex ignores a spawned agent'ssandbox_mode(codex-cli 0.160.1 drops it when it loads the role), so the session's sandbox, or agentteams' launcher when Codex is run through.codex/confined-run.example.sh, governs every agent. The key is still emitted as a declaration, labelled not enforced, because theAR_WRITE_POLICYaudit reads it. Per-role limits on Codex are instruction-level only, except underwrite_policy: "orchestrator-only"(see Role gate below).- An agent whose canonical tools include
readorsearchbut neitherexecutenorretrieval(those already run commands) also gets a Reading on Codex section. Codex has no separate read tool, so reading goes through the shell. The section allows read-only commands only (cat,head,tail, print-onlysed -n,ls,rg,grep,find,wc), allows no shell operator but|between them, and names the forms that write or run code:sed -i/-I/-f/w/e,rg --pre/--hostname-bin/-z,find -exec/-delete/-fprint*,tee, and any other program. It also limits reads to the workspace and names the secret stores never to read (~/.config/agentteams/,~/.ssh/,.env, credential stores). Because Codex does not enforcesandbox_mode, the list is the guard. - An agent whose canonical tools include
executeorretrieval(it already runs commands, so it reads through the shell too) gets a Secrets on Codex section with the same secret-store line: read only inside the workspace, and never~/.config/agentteams/,~/.ssh/,.envor credential stores. - Exactly one title H1: the body's own H1 is kept, and
# {name}is added only when the body has none. guard_rendered_files(and the interop import) skip an existing repo-rootAGENTS.mdthat was not generated for Codex, with a notice.AGENTS.override.mdis never emitted.- Write-policy co-ownership (
_agents_md_ownership.codex_write_policy_decision). Underwrite_policy: "orchestrator-only"with Codex in scope (inwrite_policy_frameworks, or unscoped), the top-level Codex session reads its dispatch and proposal instructions ("Write Policy: You Are the Orchestrator") fromAGENTS.md:
Existing AGENTS.md |
What Codex does under the switch |
|---|---|
| Absent, or Codex-generated | Writes the render, section included |
| goose-generated | Adds or refreshes only the codex_write_policy fence, via upsert_fenced_block (with --merge or --overwrite); every byte outside it stays goose's, and goose's re-render carries the fence over |
| Hand-written, Grok, agents-md, legacy-fenced or unsafe | Never written; a MISSING notice names the absent section, and the block is written to .codex/examples/AGENTS.codex-write-policy-block.md to paste in (not when .codex/examples is a symlink) |
With the switch off, nothing changes: a non-Codex AGENTS.md is left untouched, and a stale codex_write_policy block in a goose file is named in the notice.
- An interop import keeps .github/agents/... paths as they are, because interop does not copy references. Native generation rewrites them to .codex/agents/....
- Codex loads .codex/agents/**/*.toml only, so reference docs under .codex/agents/references/ are ignored by Codex.
- codex exec --ephemeral breaks custom-agent spawning in codex-cli 0.160.1 ("collab spawn failed: ... no rollout found for thread id"): spawned agents need the session's rollout. Run multi-agent teams without --ephemeral. Observed in mathAgents' live check, 2026-10-07.
- MCP servers are configured separately in .codex/config.toml, emitted by codex_mcp_emit when the codex:mcp host-feature token is enabled and the project declares mcp_servers[].
Launcher mode (codex:sandbox, Phase 1a):
- A
confined/exclusiveprivilege_profileexpands to thecodex:sandboxhost feature.extra_output_filesthen adds.codex/confined-run.example.shon Linux and macOS, after the framework-neutral launchersandbox/confine-run.shthatsuper()emits. The runner is an inert, operator-run example; agentteams never runs it. Before launch it validatesCODEX_HOME(strictly under~/.config/agentteams/codex-home/or outside~/.config/agentteams; never$HOME, inside the project, or an ancestor of either), createsconfig.toml,hooks.json,AGENTS.md,AGENTS.override.mdand therules/,prompts/,skills/,plugins/dirs empty if absent, and protects them (plugins/holds installed plugin code), so a confined agent can't plant config Codex honours on its next run. Anexclusiveteam'sprotected_read_pathsbecome--exclude, through_write_roots.runner_exclude_flags, shared with the Goose runner. A~/path is spelled"$HOME"/…, because the launcher doesn't expand~. A bare~or a~user/…path is skipped, with a runtime note. Itsworkspace_write_rootsbecome--writable. - Codex's own sandbox cannot nest inside the launcher, so the runner starts
codex --sandbox danger-full-accessinsidesandbox/confine-run.sh, and the launcher is the boundary. Writes reach only the project root andCODEX_HOME. The agentteams control plane, the prompt roots,.agentteams/andCODEX_HOME's stubbed config, hooks, instructions andplugins/(listed above) are read-only, and credential directories, including~/.config/agentteams/keys, are masked. CODEX_HOMEmust be writable, so it defaults to a per-project directory outside the repo,$HOME/.config/agentteams/codex-home/<project>(override:AGENTTEAMS_CODEX_HOME). The runner refuses aCODEX_HOMEunder the key directory, inside the project, or at$HOMEor above. Authenticate it once outside the launcher (CODEX_HOME=<dir> codex login).- Egress defaults to
host, because Codex needs its API (CODEX_CONFINE_EGRESSoverrides it; the launcher's--egress proxy --proxy ADDR:PORTgives a loopback proxy instead). - Every agent command inherits the launcher's write access,
CODEX_HOMEincluded. The launcher bounds the whole session, not one role. Generation can't verify that Codex is actually launched through the runner. - Self-probe. The runner launches
bash -c "$SELF_PROBE" … codex …inside the launcher, and the probeexecs Codex, so both run under the same profile. Before Codex starts, the probe checks from the inside that the signing-key dir lists empty or is unreadable, that a legacy*.pemkey is unreadable, that.agentteams/can't be written, that.codex/andCODEX_HOME'sconfig.toml/hooks.jsoncan't be written, and, under the role gate, that.codex/hooks.jsonand the gate itself can't be written. Any failure exits 3 without starting Codex. Whether those paths exist is read outside the launcher, because inside, a denied path can look absent. write_policy: "orchestrator-only"(andwrite_policy_frameworks: ["codex"]) is accepted on codex whencodex:sandboxis in effect, which takes an explicitconfined/exclusiveprofile. Its key custody relies on the launcher masking the key directory, so it holds only when Codex runs through the runner. Generation prints that it can't verify the launch.
Role gate (write_policy: "orchestrator-only", Phase 1b):
- Codex passes a PreToolUse hook the calling agent's
agent_typeandagent_idfor every call a spawned agent makes, and neither for the top-level session (probed live on codex-cli 0.160.1). Under the policy,extra_output_filesalso emits.agentteams/bin/codex-role-gate.py(pinned package data), the read-only file server Goose uses under the policy (.agentteams/bin/goose-readfs-mcp.py), and.codex/hooks.json, which runs the gate before every tool call as"$AGENTTEAMS_PYTHON" -I -S "$AGENTTEAMS_ROOT/…" || exit 2with a 60-secondtimeout. The gate's files come from_codex_role_gate_emit(integrity-pinned). - Live probes found that every spawned-agent tool reaches PreToolUse tagged with the agent's role: shell,
apply_patch, web search, spawn and send-message. A generic subagent is taggeddefault. The gate allows the top-level session (noagent_type, noagent_id) everything. Any call with anagent_typeoragent_idmay use onlymcp__agentteams_readfs__{read_file,list_dir,find,grep,stat}. Any event other than PreToolUse, or a call withoutAGENTTEAMS_CODEX_CONFINED=1(set by the runner), is denied. Codex blocks on exit 2 and lets a call through on exit 1, so every deny, malformed input and internal error exits 2, and|| exit 2covers a missing interpreter or file. - Before launch, the runner refuses to start unless the gate, the server and
.codex/hooks.jsonmatch their pinned sha256 (and aren't symlinks). It resolves an absolutepython3, orAGENTTEAMS_CODEX_PYTHON, outside the launcher, refuses one inside the project orCODEX_HOME, and passes it asAGENTTEAMS_PYTHON. It also refuses a project.codex/config.tomlthat mentionsagentteams_readfsin any form, and any nestedsubdir/.codex/hooks.jsonorconfig.toml, matched case-insensitively and including symlinks and a symlinked.codexdir, because that config would apply to a session started below the root.pluginskeys inCODEX_HOME/config.tomljoin the advisory security-relevant-keys warning. It writes the server's[mcp_servers.agentteams_readfs]entry into an emptyCODEX_HOME/config.toml, or requires it in an operator's own config verbatim, exactly once and as one block. It also passes--dangerously-bypass-hook-trust, since Codex otherwise skips untrusted project hooks and the runner keepsconfig.toml, where trust is recorded, read-only. The hook files are read-only inside the launcher (.codex/is a prompt root;.agentteams/is protected). - Non-orchestrator agents get a Reading on Codex (role gate) section naming those tools instead of the shell allowlist.
guard_rendered_filesnever overwrites an operator's own.codex/hooks.json, meaning a symlink, non-JSON, or any hook that doesn't run the gate. It prints a notice instead, and the runner then refuses to start.- Label. This is harness-level, like Claude's tool grants. It holds while Codex runs the hook, which only the runner arranges, and it relies on Codex's undocumented hook input. Residual: Codex lets a call through when a hook times out (probed live), so the gate is kept fast (stdlib, capped stdin, no other I/O). The OS boundary is still the launcher.
- Derived outputs. A checking command's generated outputs (for example
lean/.lake, gate reports) are writable whenever the session, or the launcher's workspace root, is writable, and blocked for every agent in a read-only session. Codex 0.160 has no per-agent write grant, so these outputs can't be opened to one role only. - Nested-directory
AGENTS.mdplacement (subdirectory-scoped refinements) is documented but not yet built.
GrokAdapter¶
Source:
agentteams/frameworks/grok.py(helpers:_grok_tool_scoping.py,_agents_md_ownership.py, both integrity-pinned)
Adapter for xAI Grok Build (the grok CLI). Subclasses ClaudeAdapter, because Grok Build reads Claude Code's agent format. Beta.
- framework_id:
'grok' - Output format:
- Agent files:
.grok/agents/<slug>.md, with YAML front mattername,description,tools, an optionaldisallowedTools, theninjectDefaultTools: falseandmcpInheritance: none, then the body with handoffs removed. - Instructions: the shared repo-root
AGENTS.md, with a Grok ownership notice and a fencedgrok_runtimeblock. - Builder:
.grok/agents/team-builder.md, frombuilder/team-builder-grok.template.md. - Handoffs:
manifest(inherited from Claude;references/runtime-handoffs.json). - Agents dir:
<project>/.grok/agents/(normalize_output_pathappends it to a project-root--output). - Skills:
.grok/skills/<slug>/SKILL.md.
Current behavior notes:
- Tools are translated fail-closed (
_grok_tool_scoping.translate_grok_tools;to_grok_toolsreturns just the value): Claude names pass through,TaskbecomesAgent, andTodoWriteis dropped. A scoped entry (Bash(cmd:*),Agent(type), anyName(...)) is never emitted, because Grok Build 1.0.52 ignores the scope and widens the agent: a scoped allow entry (andMCPTool, which makes grok discard the whole list) is dropped, a scoped deny becomes the whole tool, and every deny is applied by leaving the tool out oftools:because grok ignoresdisallowedTools, each with a[GROK-SCOPE]notice on stderr, in interop notices and in the build log (grok_tool_notices). An unknown name, an allow-list that translates to nothing (Grok reads an empty list as "inherit every tool"), a deny-list that removes every allowed tool, or an untranslatabledisallowedToolsentry raisesValueError.guard_rendered_filesalso names any existing.grok/agentsfile that still carries a scoped entry.modelandpermissionModeare not emitted. name:values are byte-identical to the Claude render (operator decision D4). Grok always loads.claude/agents/too; which body runs when two agents share aname:is not confirmed at runtime (G15), so both teams are regenerated together.guard_rendered_filesprints a notice for:- a name mismatch with the on-disk Claude twin
- Claude agents with no Grok twin
- a slug that shadows a Grok built-in type (
general-purpose,explore,plan) - a gitignored
AGENTS.md - AGENTS.md co-ownership (operator decision D2;
_agents_md_ownership.classify_agents_md):
Existing AGENTS.md |
What Grok does |
|---|---|
| Absent, or Grok-generated | Keeps the full render |
| Codex, goose, agents-md, or agentteams-fenced | Adds or refreshes only the grok_runtime fence, via upsert_fenced_block; every byte outside it is untouched |
| Hand-written, unfenced | Never written; the block is emitted to .grok/examples/AGENTS.grok-block.md instead |
A symlink (itself or a parent such as .grok/.grok/examples), unreadable, malformed fences, or outside the project |
Never written |
CodexAdapter uses the same classifier, and it leaves a Grok- or goose-generated AGENTS.md alone, except for its own codex_write_policy block in a goose file under write_policy: "orchestrator-only".
- extra_output_files:
- always: the framework-neutral baseline plus .grok/hooks/README.md, which keeps the launcher-protected dir present
- when confinement is requested (grok:sandbox, or a confined/exclusive profile) on Linux/macOS: _grok_sandbox_emit.grok_sandbox_output_files adds .grok/sandbox.toml, .grok/confined-run.example.sh and the advisory hook gate (agentteams-gate.json + agentteams-gate-shim.py + constitutional-gate.py)
- Claude's own hook, settings example and runner files are never emitted under .grok/
- the guard refuses any of these through a symlink, and never overwrites an existing profile, runner, hook JSON or shim that lacks the agentteams-managed marker, or a gate copy that lacks the gate template's own opening docstring
- Refused combinations: write_policy: "orchestrator-only" (rendering a runner grant raises), and convert and bridge (as target; grok is never a bridge source). Interop target/source and absorb source are supported (Phase 3, codex posture); interop writes the repo-root AGENTS.md only through guard_rendered_files (agentteams/_interop_grok.py).
- Control plane: .grok is protected alongside the other team dirs (team key grok):
- the write roots, plus ~/.grok as a protected home path
- the prompt roots (.grok, .agents/skills)
- the Claude block's sibling Edit denies
- the goose Seatbelt profile and the launcher's TEAM_DIRS_REL, CONTROL_PLANE_REL (.grok/config.toml, .grok/sandbox.toml and .grok/confined-run.example.sh are protect-if-present) and CONTROL_PLANE_DIRS_REL (the whole .grok dir, Phase 2)
- signing_switch and projection_marker
- Version pin: FORMAT_SPECS["grok"].pinned_version, a new FormatSpec field. It is the only place the version appears.