Known Limitations in agentteams 1.0¶
The 1.0 release triage (@security and @adversarial, approved by the operator) classed the items below as documented limitations, not release blockers. Each entry says what the limitation is, who it affects, what to do about it, and where it is tracked. A fix may land in a 1.0.x or 1.1 release, and may change behaviour when it does (see the stability policy).
"Remediation log row N" refers to line N of references/agentteams-remediation-log.csv (the header is line 1).
Orchestrator-only writes (write_policy) and the runner¶
Turning write_policy off is not a clean rollback¶
- What: removing the switch from the brief and re-rendering (
--update --merge, then--reconcile-front-matter --reconcile-apply) restores tools, Goose extensions and the settings example. It leaves three things behind: - (R1) each granted agent's
mcpServersagentteams_runnerblock, because reconcile never removes a key the template no longer declares; - (R2) orphaned
write_policyfences (<!-- AGENTTEAMS:BEGIN write_policy v=1 -->…<!-- AGENTTEAMS:END write_policy -->) in agent files. The merge keeps orphaned fenced sections by design, so agents are told they can't write while they holdWriteagain. The pilot drill found about 44 such files. - (R3)
.agentteams/bin/agentteams-runner-mcp.pyand.agentteams-status/. The runner also leaves its run-time queue state:.agentteams-queue/,.agentteams/queue-results/,.agentteams/queue-claimed/,.agentteams/runner.lockand.agentteams/runner.heartbeat. - Who: any team that turns the switch off after running with it.
- Workaround: the manual cleanup in the Migration Guide,
"Rollback A". Do not delete
.agentteams/as a whole: it also holds state the team still uses. - Tracking: remediation log row 344.
Headless claude -p: a granted agent's runner server is not connected¶
- What: in a headless
claude -psession (Claude Code 2.1.296), a granted subagent's inlinemcpServersagentteams_runneris not connected. The agent has only its non-MCP tools, so it cannot write at all. The orchestrator's--apply-proposalfallback is correctly refused for granted agents. An interactive session (2.1.246) connected the server. The cause is unconfirmed: headless mode or the Claude Code version. - Who: Claude teams under the switch with
mcp_grants, run headless (CI, scripted runs, evaluation harnesses). - Workaround: run granted agents in an interactive session. Treat a granted agent in a headless run as unable to write.
- Tracking: remediation log row 359.
Grants are cooperative, not enforced, on Goose delegation, Codex outside its role gate, agents-md and Grok¶
- What: these surfaces do not enforce an agent's declared tools.
- Goose: a
delegateruns with the orchestrator's extensions, so a recipe's grant binds only on a directgoose run --recipe. Render refuseswrite_policyon Goose for this reason. The goose bridge stubs also still declare the wholedeveloperextension. - Codex:
sandbox_modeand the tool list are self-imposed, except under thewrite_policyrole gate. - agents-md (the framework-neutral
AGENTS.md): the surface has no grant channel. - Grok: grants are declared only. No live test has yet shown a read-only agent blocked from editing.
- Who: teams that render or project read-only agents onto these surfaces.
- Mitigation:
--interop-fromrefuses to project a narrower-than-default agent onto Codex (outside the role gate), Grok or agents-md unless it is named in--interop-allow-cooperative. Each such opt-in is recorded in the destination team's decision log, and the agent carries a "grant is cooperative on this surface" banner.- On Goose, run read-only agents directly.
- On Codex, use the launcher,
.codex/confined-run.example.sh. - Tracking:
STABILITY.mdsection 6. Remediation log rows 386 and 396 (Goose) and 432 (Grok).
Claude under the switch is refused on Linux¶
- What:
--check-wiringrefuses a Claude team under the switch on Linux. The runner's.agentteams-status/channel has not been measured under bwrap; a read-denied queue might be masked rather than refused. - Who: Linux operators of Claude teams under the switch.
- Workaround: run the switch on macOS. There is none on Linux for 1.0.
- Tracking: CHANGELOG
[1.0.0-rc.10], "the sandboxed Claude orchestrator reaches the runner". baseAgent request #4, targeted at 1.1.
Claude Code's built-in general-purpose subagent holds Bash¶
- What: if the orchestrator dispatches Claude Code's built-in general-purpose subagent, that subagent has Bash, and therefore the orchestrator's channel to the runner. agentteams does not render that subagent, so neither its audit nor a verified permission rule can narrow it.
- Who: Claude teams under the switch.
- Mitigation: dispatch only team agents.
- Tracking: CHANGELOG
[1.0.0-rc.10], the same entry, "Residual (documented)".
macOS: only git's helper directory is added for an xcrun launcher¶
- What: when a confined program's
execnames an Apple xcrun launcher in/usr/bin(git,make,clang), the runner's Seatbelt profile also allows the real tool (by exact path) and, for git only, its--exec-path. Another launched tool that runs helpers, or sibling tools, outside that is still denied, and its confined calls fail with exit 71. If xcrun is missing or fails, or a path isn't root-owned, nothing is added; the runner and--check-wiringwarn. The run is refused only when a write root also covers a package store. Otherwise a tool that misreads exit 71 can still act on it inside its write roots (mathAgents M6:lakedeleted.lake/packages/mathlib). - Who: macOS operators whose confined programs call developer tools through
/usr/bin. - Workaround: add the helper directory to
execyourself, and keep write roots off package stores (.lake/packages,node_modules,.venv, ...). The runner warns about those, but does not refuse them. - Tracking: CHANGELOG
[Unreleased], "xcrun launchers in the exec allowlist reach their real tool".
Decision log and the security gate¶
Clearances bind by id prefix; prose scope is not enforced¶
- What: a clearance binds to the newest decision row whose id starts with the action id. A clearance named differently never binds. An older, unconsumed single-use row can clear instead of the one you intended, so a stale clearance can be replayed when its scope lives only in prose.
- Who: every signing-governed team that records clearances by hand.
- Workaround: name clearances
<action>-<instance>, and restrict them with thescopecolumn. - Tracking: CHANGELOG
[1.0.0-rc.10], "a HALT blocks every action named in its id", "Documented known limitations". A near-miss warning is planned for 1.1.
A CONDITIONAL PASS needs conditions_verified to be exactly verified¶
- What: any other value, including
verifiedfollowed by notes, does not count. - Who: anyone recording CONDITIONAL PASS rows.
- Workaround: write exactly
verified, and put the evidence in theevidencecolumn. - Tracking: the same CHANGELOG entry. A
verified:<evidence>form is planned for 1.1.
A family retraction clears every prefix-matched HALT of its action¶
- What: a
HALT-RETRACTEDrow such asoverwrite-001clears every HALT that matches the actionoverwriteby prefix,overwrite-999included. - This is unchanged from rc.9, and disclosed.
- A HALT matched only by token (new in 1.0) is retracted only by its exact id.
- Who: teams with more than one open HALT for the same action.
- Workaround: before recording a retraction, list the open HALTs (
--verify-integrityreports them). Retract by the exact HALT id where a family retraction would clear more than you mean to. - Tracking: the same CHANGELOG entry, "Disclosed, unchanged".
Logs signed before 1.0 may verify through a legacy prefix¶
- What: before 1.0,
--sign-decisionnever filledprev_digest(defect 338). When signing is active, 1.0 accepts a leading run of rows with empty links, so those logs work again without re-signing. Within that prefix: - deletions cannot be detected, HALT rows included;
- the chain is unkeyed, so rows cut from the tail of any log are not detected either.
- Who: teams that signed decisions on rc.9 or earlier.
- Mitigation:
--verify-integrityreports the prefix length and every HALT inside it. Confirm those rows are still present as recorded. No re-chaining tool ships in 1.0. - Tracking: remediation log row 338 (fixed by #224). CHANGELOG
[1.0.0-rc.10], "1.0 must-fix set", "Disclosure".
The Rule 15(d) aggregate cap is not consulted for operator-signed relaxing decisions¶
- What:
exception_registry.assert_within_aggregate_capruns only on the adopt-merge path. Other Ed25519-signed relaxing decisions (cross-repo actions, grants) are bounded only by their own expiry. - Who: operators of signing-governed workspaces who sign relaxing decisions.
- Mitigation: give every signed relaxing decision a short expiry, and review the decision log's open relaxing
rows at each release or fleet round.
--audit-exceptionscovers onlyreferences/exception-registry.json, so it does not see these rows. - Tracking: remediation log row 337.
A full render can't be covered by a signed overwrite row, because it writes CLAUDE.md¶
- What: every render writes the managed block of
.claude/CLAUDE.md, and the effect classifier treats CLAUDE.md as a governance trust root. In a workspace where signing is active, a signedoverwriterow whose effect includes that write is refused as non-eligible, so no honest signed row covers a full render. - Who: teams with decision signing active that re-render a Claude team.
- Workaround: discard the render's change to
.claude/CLAUDE.mdand have the operator apply the managed-block diff by hand. Sign theoverwriterow for the remaining files. - Tracking: 1.0 known limitation (found by mathAgents, 2026-10-16). A render mode that leaves CLAUDE.md to the operator and prints the diff is planned for 1.0.x.
AGENTS.md is not classed as a governance trust root¶
- What: the effect classifier treats
CLAUDE.mdandcopilot-instructionsas governance trust roots, but notAGENTS.md, which is the instruction file for Codex, goose, Grok and agents-md. A clearance whose targets includeAGENTS.mdis therefore classed by its other effects, and an ordinary HMAC-signed clearance can authorize a write to it. - Who: signing-governed teams on those surfaces.
- Workaround: review any decision row whose
effect_targetsincludeAGENTS.mdas you would aCLAUDE.mdone, and sign it with Ed25519. - Tracking: 1.0 known limitation (found by baseAgent's @security). The fix is planned for 1.0.x, together with the render mode that leaves governance files to the operator, so renders stay signable.
Installing an existing operator key into a second team needs a manual step¶
- What:
references/authorized-verify-keys/provision-operator-signing-key.shrefuses when the private key already exists ("rotate with a new key-id"). So it cannot install an existing operator key's public half into another team'sreferences/authorized-verify-keys/, and Ed25519-signed decisions cannot be verified in that team. - Who: operators adding a team (or a second framework surface) under an existing key.
- Workaround: from an operator shell outside every agent session, derive the public key from the existing private key:
openssl pkey -in <private key> -pubout -out <team dir>/references/authorized-verify-keys/<key-id>.pub.pem
Do not overwrite an existing .pub.pem. Treat the step as a trust-root write, cleared by @security.
- Tracking: remediation log row 398. The proposed fix is an --add-team-dir helper mode.
The orchestrator's text misstates which allowlist source wins¶
- What: the rendered orchestrator "Applying Proposals" text implies that the operator's
confined_programsfile wins when both it and the brief's block exist. In fact both at once are refused (write_policy.py,proposal_policy.py). - Who: teams under the switch that configure confined programs.
- Workaround: use one source, either the operator file or the brief block, not both.
- Tracking: remediation log row 422.
The constitutional gate hook and network egress¶
The gate does not see WebFetch, MCP tools, Read or Task¶
- What: the hook matcher is
Write|Edit|NotebookEdit|MultiEdit|Bash. Calls where injected content enters (Read, WebFetch, Task, MCP tools) never reach it. The same goes for markdown image and link URLs rendered into chat output, which the client fetches with no tool call. Bash findings route toask, notdeny. - By default the hook keeps no per-case record.
- The opt-in gate decision log (
AGENTTEAMS_GATE_LOG, an absolute path outside the project and any git work tree) records each invocation. This closes the "no record" part only: the log is evidence, not the boundary. - Who: all Claude teams with the gate hook installed.
- Mitigation: deny or ask WebFetch and unneeded MCP servers in your settings' permission rules. Confine egress (next entry).
- Tracking: remediation log rows 388 and 413. Threat model v3, section 2a.
The project sandbox block does not confine network egress¶
- What:
- The emitted Claude sandbox block carries no network deny, so egress is unconfined by default (row 387).
- Claude Code ignores
sandbox.network.strictAllowlistin project settings; it is honoured only in user or managed settings (row 410). - Under a user-level strict allowlist, the project's own
allowedDomainsare inert (row 414). - So a repository cannot confine, or grant itself, egress.
- Who: every team that relies on the project settings for network confinement.
- Mitigation: set the allowlist in user or managed settings, or wrap the session in
sandbox/confine-run.sh --egress deny(orproxy).--egress proxyon Linux needs a network namespace thategress-netns-wiring.shcreates, and that script is not in the repository. - Tracking: remediation log rows 387, 410 and 414.
Codex (--framework codex)¶
--update --merge does not narrow an existing team's sandbox_mode¶
- What: turning
write_policy: orchestrator-onlyon for an existing Codex team and running--update --mergeleaves the specialists'sandbox_mode = "workspace-write"in place. Only a fresh build or--update --overwrite(with anoverwriteclearance) rewrites it. Agent files that agentteams did not generate are never touched, and fail the write-policy audit. - Who: Codex teams adopting the switch on an existing render.
- Workaround: run
--update --overwritewith a clearance, or rebuild. The merge prints this warning, and[WRITE-POLICY]names each file it did not generate. Under the launcher the role gate denies every spawned agent's writes whatever itssandbox_mode, so enforcement still holds. - Tracking: 1.0 known limitation. A three-way merge of
sandbox_modeis planned for 1.0.x.
A project's own .codex/hooks.json keeps the role gate out¶
- What: the Codex role gate is installed only when agentteams owns
.codex/hooks.json. If the project has its own hooks file there, the render leaves it alone and says so, and.codex/confined-run.example.shthen refuses to start, because the file is not the pinned copy. This fails closed. - Who: Codex projects with their own project hooks.
- Workaround: move your own hooks to
CODEX_HOME/hooks.json, then re-render. - Tracking: 1.0 known limitation. Merging the gate entry into an existing file is planned for 1.0.x.
New teams start a legacy six-column decision log¶
- What: a fresh render seeds
references/security-decisions.log.csvwith the older six-column header, on every framework. A signed row (for example a#210or Rule 15 row) needs the current schema. - Who: teams that will sign decisions.
- Workaround: run
--migrate-decision-logon the team before the first signed row (see Upgrading to 1.0, Step 2). - Tracking: 1.0 known limitation. Seeding the current header is planned for 1.0.x.
Adopted agents can't be put under orchestrator-only on Codex¶
- What:
write_policyinterop (--interop-from … --framework codexwith the switch on) is refused. 1.0 supports it for claude and goose imports only. So adopted (bespoke) agents can't be narrowed on Codex, and their earlier projected TOMLs keepsandbox_mode = "workspace-write". Under the launcher, the role gate still denies writes from every spawned agent, but the write-policy audit flags those files. - Who: Codex teams whose roster includes adopted or projected agents.
- Workaround: keep the Codex surface frozen (don't render or project it) for 1.0, or run Codex only through
.codex/confined-run.example.sh. - Tracking: 1.0 known limitation. Codex
write_policyinterop is planned as a 1.0.x backport (found by mathAgents).
The Codex role gate is not pinned to a codex-cli version¶
- What: the generated role gate reads Codex's hook input, including fields Codex does not document. A future codex-cli release could change those fields, and nothing in the gate or the launcher detects that.
- Who: Codex teams under
write_policy: orchestrator-only. - Workaround: pin codex-cli yourself, and re-run your launcher checks after any Codex upgrade.
- Tracking: 1.0 known limitation. A version pin that fails closed on an untested codex-cli is planned for 1.0.x (found by baseAgent).
Copilot (--framework copilot-vscode)¶
The orchestrator and security agents exceed GitHub's custom-agent size limit¶
- What: the generated copilot-vscode
orchestratorandsecurityagent bodies can exceed GitHub's 30,000-character custom-agent limit (about 70,000 and 49,000 characters on one project), so Copilot truncates or rejects them. - Who: copilot-vscode teams. Claude, Codex, Goose and Grok are unaffected.
- Workaround: rely on the Claude or Codex surface for those two roles, or trim their Project-Specific Notes.
- Tracking: 1.0 known limitation. Splitting long sections into references, with an emit-time size check, is planned for 1.0.x (found by masterTeacher).
Rendering¶
Repository scanning can add retrieval-integrator to the roster¶
- What: every surface shares one roster.
retrieval-integratoris included whenretrieval_integrationis set, either in the brief or by a scan of the repository. The scan runs whenever--projectis passed without--no-scan. Rendering some surfaces with--projectand others without can therefore give them different rosters. - Who: projects that render several surfaces with different flags, or whose brief leaves
retrieval_integrationunset. - Workaround: declare
retrieval_integrationin the brief, and pass the same--projectand--no-scansettings to every surface. - Tracking: 1.0 known limitation. A notice when an inferred contract changes the roster is planned for 1.0.x (found by masterTeacher).
Integrity manifest¶
A re-record after deleting an enforcement module erases its pin¶
- What:
--write-integrity-manifestrecords an empty digest for an enforcement module (outside the installed hook copies) that is missing on disk.verifythen passes, because empty equals empty. - Who: maintainers, and anyone who re-records
references/enforcement-integrity.json. - Workaround: review the manifest diff after every re-record, and reject any digest that became empty.
- Tracking: remediation log row 434.
An unreadable installed hook copy is skipped, not flagged¶
- What:
verify()treats an installed-copy path whose digest is empty as benignly absent, so a copy that is present but unreadable is skipped. - Who: maintainers. Projects with installed gate copies.
- Workaround: if
--verify-integritypasses but the gate misbehaves, check that the installed hook copies are readable. - Tracking: remediation log row 435.
Research tools¶
The research cache still writes under references/research-cache¶
- What: 1.0 removes
--screenshotfrompython -m agentteams.research browser(378), but the read-only retrieval grant (Bash(python -m agentteams.research:*)) still writes its cache underreferences/research-cache. - Who: teams whose agents hold the retrieval grant.
- Mitigation: treat
references/research-cacheas agent-writable. Do not keep anything authoritative there. - Tracking: CHANGELOG
[1.0.0-rc.10], "1.0 must-fix set", "Known residual".