opencode-swarm
AGENTS.md517 starsChanged 3 days agoMITView on GitHub
This file is the root engineering contract for opencode-swarm. It applies to every contributor — human, Claude Code, OpenCode agent, or other automated tool. Read it fully before making any code change. It is intentionally short and operational; the long-form rationale and historical failure map live in docs/engineering-invariants.md. AGENTS.md and docs/engineering-invariants.md together are the single source of truth for repository invariants. When CLAUDE.md, contributing.md, TESTING.md, or any skill conflicts with this file, this file wins; that skill or doc is…
We split this AGENTS.md into the rules it gives an agent
17 rules. About 0 are common, 8 are distinctive, and 9 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈8
Distinctive · estimate
This author's own choices. Worth a look.
9
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 21
Before creating or rebuilding any repository-derived cache or index, agents must first search the current worktree and known sibling/source …
- Distinctive: line 22
Before initializing, rebuilding, or performing a full sync of a ZaxbyGraph issue/PR database, search the current worktree and known …
- Distinctive: line 34
Every PR that touches a relevant area must list which of these invariants it touched and how it verified them.
- Distinctive: line 57
Array-form spawn only: No shell-string commands unless the use case is independently justified and quoted.
- Very specific: line 70
No tool may create .swarm/ under src/, tests/, packages/*, or any arbitrary cwd.
- Very specific: line 80
Do not hand-edit plan.md as a source of truth.
- Very specific: line 85
Do not use the OpenCode test_runner tool with scope: 'all' or broad 'graph' / 'impact' scope for whole-repo validation.
- Very specific: line 92
All tests use bun:test only.
- Very specific: line 107
Use os.tmpdir() + path.join(...) for temp paths.
- Distinctive: line 114
Add cooldowns for repeated safety/advisory behavior (e.g.
- Distinctive: line 115
No cross-session pollution.
- Very specific: line 133
Do not emit diagnostic noise into chat-visible streams.
- Very specific: line 139
Run tests/unit/config/*.test.ts and /swarm doctor tools after any tool, agent-map, command, or help change.
- Very specific: line 155
Every PR that touches a relevant area must include a ## Invariant audit section in its description (see .claude/skills/commit-pr/SKILL.md).
- Distinctive: line 175
If you cannot prove a touched invariant from source and test output, do not push.
- Very specific: line 179
Ask, don't guess. The patterns in this file are the result of prior outages — every one has a release note in docs/releases/.
- Distinctive: line 180
Prefer the smallest patch that closes the issue without unwired functionality, untested branches, or hidden regressions.
≈ “Common” is an estimate: at least 1 in 5 of the 36,368 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
AGENTS.md — Engineering contract for opencode-swarm
This file is the root engineering contract for opencode-swarm. It applies to every contributor — human, Claude Code, OpenCode agent, or other automated tool. Read it fully before making any code change. It is intentionally short and operational; the long-form rationale and historical failure map live in
docs/engineering-invariants.md.
Required reading order
| When you are about to do… | Read these (in order) |
| Any code change | AGENTS.md (this file) → docs/engineering-invariants.md (skim, deep-dive on touched invariants) |
| Write or modify any test file | this file → .opencode/skills/writing-tests/SKILL.md (or .claude/skills/writing-tests/SKILL.md for Claude) |
| Commit / push / open a PR | this file → .claude/skills/commit-pr/SKILL.md |
| Trace, investigate, root-cause, fix, or resolve an issue or bug | this file → .opencode/skills/issue-tracer/SKILL.md (or .claude/skills/issue-tracer/SKILL.md for Claude) |
Change Epic Mode (src/epic/**, an Epic seam in a shared file, or the epic config block) | this file → src/epic/README.md (Epic maintainer guide: invariants, seams, tests, recipes) |
| Swarm-mode Claude work | this file → CLAUDE.md → .claude/session/swarm-mode.md (when present) |
| Architecture / plugin init / subprocess / tool-registration / plan-durability / .swarm storage / runtime-portability change | this file → docs/engineering-invariants.md → .opencode/skills/engineering-conventions/SKILL.md (or .claude/skills/engineering-conventions/SKILL.md) |
AGENTS.md and docs/engineering-invariants.md together are the single source of truth for repository invariants. When CLAUDE.md, contributing.md, TESTING.md, or any skill conflicts with this file, this file wins; that skill or doc is out of date and must be reconciled.
Repository-history cache reuse
- Before creating or rebuilding any repository-derived cache or index, agents must first search the current worktree and known sibling/source repository locations for an existing compatible artifact.
- Before initializing, rebuilding, or performing a full sync of a ZaxbyGraph issue/PR database, search the current worktree and known sibling/source repository locations for an existing cache first.
- Validate any discovered cache against the requested repository and inspect its sync metadata before reuse. When it is compatible, reuse it in place if writable or copy it into the current worktree and run only an incremental sync.
- Build a new cache from scratch only when no compatible, usable cache exists. Record that discovery result before starting the rebuild so agents do not repeat avoidable GitHub API work or consume rate limits unnecessarily.
Prime directive
Preserve the runtime contracts that keep the plugin loadable, portable, bounded, recoverable, and safe across Windows, macOS, Linux, GUI, TUI, Bun, and Node-hosted plugin contexts.
A plugin that loads on macOS but hangs Desktop Windows is a regression. A change that passes locally on one platform but corrupts state on another is a regression. Default to "what could go wrong on the host I am not testing on?"
Non-negotiable invariants
Every PR that touches a relevant area must list which of these invariants it touched and how it verified them. See "Invariant audit required in PRs" below.
1. Plugin initialization is fast, bounded, fail-open, side-effect-minimal
- Plugin registration must complete in bounded time on every supported platform. The OpenCode plugin host silently drops a plugin whose entry never resolves; users see "no agents in TUI/GUI" with no error.
- No unbounded filesystem scans, Git commands, network calls, package-manager calls, cache repair, repo-graph construction, or large JSON repair before returning the plugin manifest.
- Any init-path environmental work must be wrapped in
withTimeout(...)(or equivalent), log non-fatally, and continue. CompareloadSnapshot(...)insrc/index.ts(already wrapped) andensureSwarmGitExcluded(...)(post-fix wrapped). - Bounded is not free.
withTimeoutonly prevents an unbounded hang — the awaited work's latency still counts toward the cross-platformrepro-704init deadline (~400 ms). If init-path work does non-trivial I/O and nothing downstream depends on its completion beforeserver()resolves, register it with the wrapper-owned post-resolution task queue (therepoGraphHookprecedent insrc/index.ts) instead ofawaiting it or usingqueueMicrotaskinsideinitializeOpenCodeSwarm. Onlyawaitinit work that is genuinely fast (<~50 ms) and must complete before a later init step (e.g.ensureSwarmGitExcludedbefore.swarm/writes). Linuxrepro-704passing does not prove Windows; cold-FS latency is several× higher and the deadline is enforced on every platform in CI. - Any init-path subprocess must use explicit
cwd,stdin: 'ignore'(unless intentionally interactive),timeout, bounded or ignored stdout/stderr, and a best-effortproc.kill()infinally. - GUI and TUI must still receive the plugin manifest even if optional startup work fails.
- Reference prior failures: v7.0.3 (
#704repo-graph Desktop hang), v7.3.3 (#732Git-hygiene startup regression — the proximate cause of this AGENTS.md), and the PR #1920 merge-queue smoke failure where aqueueMicrotaskbegan cold-filesystem work during later init awaits. PR #1356 remains the deferred-work exemplar, now registered through the wrapper-owned post-resolution task queue: its bundled-skill sync stays off theserver()-resolution path after an inline-awaitrevision was corrected pre-merge (see the in-code comment near the bundled-skill registration and the "Bounded is not free" bullet above).
2. Runtime portability — Node-ESM-loadable + dual v1/v2 plugin shape
- The main plugin bundle (
dist/index.js) must remain Node-ESM-loadable. No top-levelbun:imports (see v6.86.8 /bundle-portability.test.ts). - No un-fenced
bun:-scheme runtime module resolution. A lazyrequire('bun:sqlite')/createRequire(...)('bun:...')/ dynamicimport('bun:...')passes the top-level-import check yet still throws under the OpenCode Desktop Node sidecar (Cannot find module 'bun:sqlite'). Allbun:runtime resolution must route through a single Node-fallback-providing loader —bun:sqlite→src/db/sqlite-loader.ts(nativebun:sqliteunder Bun, anode:sqliteadapter under Node; issue #1873). Type-onlyimport type … from 'bun:…'is fine (erased). Enforced statically bytests/unit/build/bundle-portability.test.ts(source scan + bundle-pairing, runs on every PR) and functionally byscripts/repro-1873.mjs(the merge-queue-gated CIsmokejob, real Node — not on plain PR pushes). Notenode:sqliteis stricter thanbun:sqlite: it rejects a bound parameter when the SQL has no placeholder (SQLITE_RANGE/ "column index out of range"), so keep every query's parameter count exact. - No direct
Bun.*calls outsidesrc/utils/bun-compat.ts. CLI-only modules that intentionally--target bunare the only exception. - The default export must remain the dual-shape plugin object
{ id, server, setup }(issue #3004 / ADR-0003,docs/decisions/0003-opencode-v2-plugin-api.md): v1 hosts (OpenCode 1,@opencode-ai/plugin1.x) callserver(); OpenCode 2 hosts (@opencode/plugin2.x) decode the default export against{ id, setup | effect }(excess keys ignored) and callsetup(ctx)— the v2 registration surface lives insrc/host/v2/with the full v1→v2 mapping indocs/host/v2-hook-inventory.md.server()stays byte-stable for v1; v1readV1Pluginnever consultssetup(verified against anomalyco/opencode v1.18.3 and v1.18.33; see v6.86.9 /bundle-plugin-shape.test.tsfor the v1 history). - Any change touching
src/index.ts, package exports,package.json#main,bun buildconfig, or plugin entry shape must run the bundle-portability and plugin-shape tests ANDnode --input-type=module -e "await import('./dist/index.js')". dist/is generated build output and is not committed (#1047). Do not stage it. CI builds it (unit,package-check,smokerunbun run build) andpackage-checkvalidates the packed npm tarball; release/publish builds from source.
3. Subprocesses — bounded, non-interactive, killable, portable
- Array-form spawn only. No shell-string commands unless the use case is independently justified and quoted.
- Set
cwdexplicitly, OR for Git CLI calls use an explicitgit -C <directory>argument. Do not rely on inherited process cwd. stdin: 'ignore'unless the spawn is intentionally interactive. A never-closed stdin pipe under Bun on Windows can block the child from exiting (see v7.3.3 fix).timeout: <ms>is required for git, package managers, test runners, language tooling, and any external binary. There is no platform on which "git is always fast" is a safe assumption.- Consume, bound, or ignore stdout/stderr. Never leave a piped stream unattended on a long-running child.
- Best-effort
proc.kill()infinally. An outerwithTimeoutalone is not enough — it lets the awaiter proceed but does not abort the child. - Windows is first-class. Handle
.cmdextensions for npm/bun binaries, PATH differences, and the fact thatchild_process.spawn('bin', ...)does not behave identically tocmd.exe.
4. Working directory and .swarm/ containment
- Runtime state lives only under the project root
.swarm/directory. Tools must usectx.directoryinjected viacreateSwarmTool(src/tools/create-tool.ts). process.cwd()is an explicitly documented direct-CLI / test fallback only. Do not introduce newprocess.cwd()callers in tools or hooks.- User-supplied
working_directorymust resolve to a project root, never an ordinary subdirectory. A nested directory is an independent root only when it directly contains a.gitfile/directory (repository, linked worktree, or submodule declaration) or a.opencode/directory (#2127). These are explicit local declarations: direct malformed/empty.gitmarkers still opt in, but marker symlinks/junctions and inaccessible markers do not.save_plan, the sharedresolveWorkingDirectoryhelper, every duplicate root guard, and all low-level plan/scope/evidence/evaluation mutation sinks must apply the same policy; ambiguous ancestor.swarmor project-indicator state and ancestor-depth exhaustion fail closed. See also v6.82.2 (#577). - No tool may create
.swarm/undersrc/,tests/,packages/*, or any arbitrarycwd. New-directory checks must be explicit. .swarm/must not become Git pollution. TheensureSwarmGitExcludedflow (v7.3.3) keeps the project's.git/info/excludehonest; do not bypass it.- Bundled skill ownership: plugin-shipped runtime protocols materialize only under
.swarm/bundled-skills/<slug>/. Native skill roots (.opencode/skills/,.claude/skills/,.agents/skills/) are project-owned and must never be a plugin sync destination. A repository skill may reuse a bundled slug without overwrite; architect MODE stubs must load the private runtime copy. - Exception — developer skill-sync tool:
drift:fix(issue #1781 E3) is a developer-invoked, explicitly-confirmed (SWARM_SKILL_SYNC_CONFIRM=1) tool that reconciles native-skill mirror pairs before push. It is NOT a plugin-runtime sync and is never invoked at plugin init or underDRIFT_CHECK_ENFORCE. It is exempt from the runtime-sync prohibition above. - Exception — version-controlled deliverables: documentation the docs agent authors into the project repo is output, not runtime state. The
docsagent already writes README/CHANGELOG; thedocs_designagent (issue #1080, opt-in) writes design docs underdesign_docs.out_dir(defaultdocs/). These are intentionally committed and are exempt from.swarm/containment. Their runtime drift signal (.swarm/doc-drift-phase-N.json) stays under.swarm/.
5. Plan durability — ledger is authoritative
.swarm/plan-ledger.jsonlis the authoritative source of plan state..swarm/plan.jsonand.swarm/plan.mdare derived projections.SWARM_PLAN.{json,md}files are checkpoint / export artifacts, scoped to.swarm/plan-export/(issue #852). Legacy reads from flat.swarm/and project root are supported with deprecation warnings; cleanup removes all three locations.- Do not hand-edit
plan.mdas a source of truth. Do not write toplan.jsonoutside the ledger replay path. - Any plan-schema or status-shape change must update all six of: ledger replay, projection, checkpoint import/export,
get_approved_plan, tests, and docs (docs/plan-durability.md).
6. Test execution — do not use broad test_runner for repo validation
- Do not use the OpenCode
test_runnertool withscope: 'all'or broad'graph'/'impact'scope for whole-repo validation.scope: 'all'is gated behind theSWARM_ALLOW_FULL_SUITE=1env var (intended for opt-in CI mirrors, not interactive use; there is noallow_full_suitearg). MAX_SAFE_TEST_FILES = 50(src/tools/test-runner.ts). Resolutions exceeding this returnoutcome: 'scope_exceeded'with a SKIP instruction; broad scopes can stall or kill OpenCode.- For repo validation, prefer shell commands (the per-file isolation loops in
contributing.md/TESTING.md). - For targeted agent validation, use
test_runnerwith explicitfiles: [...]or small targeted scopes.
7. Test writing — bun:test, mock isolation, DI over mock.module
- All tests use
bun:testonly. No Jest, Vitest, etc. Bun's vitest-compat layer has known isolation bugs. - Load the writing-tests skill (
.opencode/skills/writing-tests/SKILL.mdor.claude/skills/writing-tests/SKILL.md) before modifying tests. mock.module(...)leaks across test files in Bun's shared test-runner process. Spread the real module when mocking, or — preferred — use dependency injection via a small_internalsseam (seesrc/utils/gitignore-warning.ts:_internalsandsrc/hooks/diff-scope.ts:_internalsintroduced by the v7.3.4 fix). Restore the seam inafterEach. Current_internalsseams include:src/hooks/knowledge-migrator.ts:_internals(writeSentinel, mkdir, writeFile for evidence-spoofing tests),src/evidence/manager.ts:_internals(validateEvidence, saveEvidence for evidence integrity tests —src/tools/syntax-check.tscallssaveEvidencethrough this seam rather than a local snapshot, since a plain object-literal snapshot of an imported binding is captured at module-init time and will not observe a sibling test file's latermock.module()replacement — issue #1248 item 16),src/hooks/guardrails/index.ts:_internals,src/hooks/curator.ts:_internals, and others listed indocs/engineering-invariants.md._internalsis intentionally absent fromsrc/lang/backends/php.ts(removed in the #1145/#1194 hardening pass; siblingsgo.ts,python.ts,typescript.tsretain theirs). The PHP backend's tests use public-API testing (backend.selectFramework?.(tmpDir)) instead —selectFrameworkhad no external consumer needing DI, so the seam was dead weight. Do not re-add it without a concrete test need.- FR-006/007/008/009/010/011/012 enforcement: All new test files must be under 500 lines.
delegation-gate.test.ts(2835 lines) was split into 45 focused files for FR-006 SC-006.1. SME tests (FR-008) use parameterization for coverage. Lean Turbo behavioral tests (FR-009) cover acquire-locks, plan-lanes, review, runner-status, generate-mutants, set-qa-gates, and get-qa-gate-profile. FR-010/FR-011/FR-012 (Phase 3–4 structural debt) added 11 new hook test files covering previously untested hooks (conflict-resolution, curator-types, delegate-ack-collector, delegate-directive-injection, knowledge-reinforcement, normalize-tool-name, phase-complete-directive-gate, phase-directives, semantic-diff-injection) and consolidated knowledge-curator tests with shared fixtures. - Spread-real-exports pattern for
node:*mocks: When mocking Node built-ins (node:fs,node:child_process,node:fs/promises, etc.), always spread the real module's exports into the mock return object. This prevents test pollution and ensures only the intended functions are overridden. CI enforces this viabun run check:mock-cleanup(Check 2).import * as realFs from 'node:fs'; mock.module('node:fs', () => ({ ...realFs, // mandatory — preserves all other exports readFileSync: mockFn, // override only what you need })); mock.moduleallowlist growth ratchet (issue #1666):scripts/mock-allowlist.txtis closed against unapproved growth bybun run check:invariantsCheck 4. Adding a newmock.moduletarget requires a matching standalone marker line# APPROVED-NEW: <normalized-target>inscripts/mock-allowlist.txt(preserved across regen byscripts/generate-mock-allowlist.sh).MOCK_ALLOWLIST_ENFORCE=0soft-warns for a deliberate growth PR. Prefer_internalsDI for new code — the allowlist is a legacy-pattern debt surface, not a way to bypass the seam convention.- Family-migration destination-lock admission (issue #2577):
bun run check:invariantsCheck 8 fail-closes the memory and knowledge family-migration engines — a destination-lock acquisition failure mustthrow(typed contention/storage), never proceed unlocked. The check errors on a swallowed admission, a missing engine file (single-engine drift), or a drifted scan anchor. - Use
os.tmpdir()+path.join(...)for temp paths. No hardcoded/tmporC:\strings. mkdtempSyncmust be wrapped inrealpathSyncif the result ischdir'd on macOS.
8. Session and global state — keyed and bounded
- Anything session-scoped must be keyed by
sessionID(see v6.80.2recentToolCallsBySessionandlastSpiralTimestampBySessionfixes). - Module-level global state must have an explicit eviction strategy (
MAX_TRACKED_SESSIONS, FIFO eviction). - Add cooldowns for repeated safety/advisory behavior (e.g. spiral-detection 60 s cooldown).
- No cross-session pollution. Global arrays and maps that mix session data are bugs.
9. Guardrails / retry semantics
- Distinguish transient infrastructure / provider errors (HTTP 429 / 503 / 529, timeouts, "temporarily unavailable") from real agent-logic failures (see v6.86.14).
- Transient errors use bounded retry (
max_transient_retries, default 5) before counting towardconsecutiveErrors/ circuit-breaker accounting. transientRetryCountresets per invocation. Do not persist it across invocations.- Transient retry and model fallback are independent — neither subsumes the other.
- Failure classification is source-aware and structured. Provider, shell/spawn, filesystem, Git, policy/gate, validation, cancellation, and deadline channels must retain distinct categories and retry/repair ownership; bounded display evidence is never reclassified by downstream consumers. Provider quota patterns never run over arbitrary tool output, and deterministic policy/containment failures never enter provider fallback.
- Retry and circuit state is invocation-owned, bounded, and action-local, keyed by the exact session, invocation, semantic action digest, and failure category. Parser, structured command-unavailable, and sandbox-wrapper failures may open immediately; repeated permanent failures use their category threshold. An open circuit blocks only the matching action. Read/diagnose/rescope/repair/handoff/abort/Full-Auto-exit controls remain reachable, while a sandbox-wrapper circuit never permits unsandboxed shell execution.
- A corrected successful execution clears only the matching action's circuits. Failures that require external repair use an explicit audited reset bound to the exact active session/invocation/action; stale or foreign resets fail closed. Late results from an older invocation/generation cannot clear current state. The false-to-true transition emits one bounded structured advisory/telemetry event without raw prompt, secret, URL, command, or output text.
- Write authorization requires an exact active v2 scope binding correlated to the current session and Task call. Empty scope, v1 scope, stale plan/task identity, and another session's declaration fail closed with
SCOPE_NOT_DECLARED; no child state is published before this preflight passes. Exception: the coder write/edit/patch gate (src/hooks/scope-guard.ts) emits the more preciseSCOPE_WORKSPACE_MISMATCHinstead ofSCOPE_NOT_DECLAREDwhen it can positively identify the failure as a wrong-resolved-root case — an active, otherwise-plausible binding for this exact session/task exists but is rooted somewhere other than where the gate resolved (seedescribeScopeWorkspaceMismatch,src/scope/scope-binding.ts, issue #2002). This is diagnostic-only — it never widens or narrows what the preflight above would otherwise grant or deny, and does not fire for an unresolvable directory, apr_feedbackbinding, or a stale/uncorrelated one. The shell-write path (src/hooks/guardrails/tool-before.ts) is unaffected and always emitsSCOPE_NOT_DECLARED.
10. Chat / system-message hook contracts
- Preserve OpenCode's expected message shape end-to-end. Multiple
output.systementries are materialized into multiple{ role: 'system' }messages. - A hook
outputproperty must be mutated IN PLACE for the two chat transform chains. The host invokes each hook asM(input, output), discards the return value, and afterwards reads its own local array — sooutput.system = …/output.messages = …insideexperimental.chat.system.transformorexperimental.chat.messages.transformis a silent no-op. Usepush/splice/length = 0+ refill. (chat.params/chat.headersdo consume the returned object; do not over-generalize.) Two shipped fixes were dead for years because of this — see v6.85.1 below anddocs/engineering-invariants.md. Enforced bytests/unit/hooks/chat-transform-rebind-guard.test.ts. - Model-only guidance rides USER-role guidance carriers — never
role:'system'entries (issue #2526). The pinned host (@opencode-ai 1.18.3) message→request converter branches only onuserandassistantwith noelse, and dereferencesmsg.partsunconditionally: arole:'system'entry insideexperimental.chat.messages.transformoutput is silently discarded before the request is built, and a flat entry withoutpartsthrows a TypeError in the host prompt build. Every plugin injection must go throughsrc/hooks/system-guidance-carrier.ts— a carrier is{ info: { id: 'swarm-guidance:<kind>', role: 'user' }, parts: [{ type: 'text', text: fenced }] }, wherefencedwraps the body in the<swarm_system_directive source="opencode-swarm" kind="…">provenance fence; carrier detection is byinfo.idprefix, never text sniffing. Delivery/telemetry predicates assert the host-render contract (deliveredGuidanceDelta— non-empty delta + renderable carrier shape), not presence in the plugin's own array. The final structure-mutating handler runsmaterializeSystemGuidanceInPlace(the boundary that converts any remaining system entry to a carrier in place, or drops tool-result/whitespace ones), so the transformed array contains ZERO system entries — a stronger form of the old "one system message at index 0" local-model guarantee (#608/#628), since the host's own system content arrives only via the separateoutput.systemstring surface. Pinned bytests/unit/hooks/host-message-role-contract-2526.test.ts(version tripwire: the installed@opencode-ai/pluginand@opencode-ai/sdkversions must equal the fixture'sPINNED_HOST_PACKAGE_VERSION; on a lockfile bump OR a host release, re-verifytests/helpers/host-contract-v1_18_3.tsagainst the new host source — the weeklyhost-contract-check.ymlscheduled check (scripts/check-host-contract.ts) is the trigger and compares the converter's structural digest against the host source at npm-latest) andtests/unit/hooks/system-splice-ratchet-2526.test.ts(norole:'system'construction outside the role-filter system-string adapter and the materializer). The pre-#2526consolidateSystemMessagesmerge and the v6.85.1/#628output.systemcollapse are both gone. - Do not emit diagnostic noise into chat-visible streams. Use the debug-gated logger (
src/utils/log.ts) unless the message is an operational or security warning that must always be visible. - OpenCode 2 (v2) chat contracts are the dual of the v1 rules above (issue #3004 / ADR-0003). The v2 session
contexthook (src/host/v2/guidance.ts) receives a DeepMutableSessionContextevent and mutatesevent.system/event.messagesin place (same class of contract as the v1outputrule — the host reads its own arrays post-hook). v2 rendersevent.systemnatively, so the #2526 USER-role-carrier workaround is v1-only: on v2 the adapter re-homes the materializedswarm-guidance:*carrier text intoevent.systemas{ type: 'text' }parts (the user-to-system transport delta is recorded indocs/host/v2-hook-inventory.md); the provenance fence stays in the text. Each guidance unit has exactly one destination.event.agentis anAgent.Infoobject on v2 and must be normalized to a name before any v1-style agent comparison.
11. Tool registration + agent-map coherence
- A tool addition is incomplete until: (a) a
TOOL_METADATAentry insrc/tools/tool-metadata.ts(description +agents: [...]are required fields — a missing one is a compile error), (b) aTOOL_MANIFESThandler thunk insrc/tools/manifest.ts(registered viadefineHandlers<T extends Record<ToolName, () => ToolDefinition>>; the generic constraint makes the handler set exhaustive vs the metadata keys — a missing or stray handler is a compile error), (c) an export in thesrc/tools/index.tsbarrel (still a required surface for wiring and registration tests — enforced byscripts/check-tool-registration.ts), (d) help/documentation surfaces, (e) tests covering the new entry. Everything else is derived, not hand-maintained: the plugin object is built bybuildPluginToolObject(...)insrc/index.ts(there is no manualtool: {}block to edit), andTOOL_NAMES/TOOL_NAME_SET/AGENT_TOOL_MAPinvert automatically fromTOOL_METADATA.agents(feature-gated tools instead declare an opt-in map such asMEMORY_AGENT_TOOL_MAPinsrc/config/constants.ts). - Run
tests/unit/config/*.test.tsand/swarm doctor toolsafter any tool, agent-map, command, or help change. See v6.48.0. - CI drift check (issue #1497):
.github/workflows/drift-check.ymlrunsscripts/drift-check.ts --enforceon PR,merge_group, andpush: main. It detects drift across skills (.opencode↔.claudemirror contracts insrc/config/skill-mirrors.ts), bundled-skill completeness (the issue #1496 class:.opencode/skills/vsBUNDLED_PROJECT_SKILLSvspackage.json#files), tool registration (reusesscripts/check-tool-registration.ts), commands, agents, public numeric docs claims, and — whenSWARM_DEP_FRESHNESS_CHECK=1(set in CI) — dependency freshness (issue #1899: locked@opencode-ai/*resolution AND the vendored@opencode/pluginpin insrc/host/v2/types.tsvs npm-latest (issue #2910; fires past a five-minor-series gap), a fail-open advisorynoticethat never blocks even under enforce). The focusedrequired-check-contractdetector reconciles exact required contexts against freshness-bound captured GitHub evidence; its pre-promotionRULESET_DIVERGENCEnotice for intended-requireddriftis visible but non-blocking until the live ruleset is promoted. Blocking drift findings fail the CI job while annotations and the sticky PR comment preserve the full report; localbun run drift:checkremains soft-warn unless invoked with--enforce(orDRIFT_CHECK_ENFORCE=1). New cross-tree skill pairs must be classified insrc/config/skill-mirrors.tsor the check warns. Runbun run drift:check --enforcelocally before pushing skill/tool/command/agent/docs-claim changes. - Skill audience metadata: every tracked static
SKILL.mdin the three native trees declares anaudienceunder the Agent-Skills-specmetadata:map (issue #2907; a legacy top-levelaudience:still resolves during the transition, and a metadata-vs-top-level conflict fails closed).metadata.audience: swarm-pluginidentifies this repository's static plugin skills. Consumer repositories may use a domain tag and an optionalrunner:opencode|claude|codexconstraint (list values serialize as JSON array strings —metadatais string-typed per the spec; seedocs/skills.md"Frontmatter conventions"). Untagged runtime-generated skills remain backward-compatible match-all because the generator cannot invent a repository identity. Empty or malformed declarations fail closed, and explicitSKILLS:references are validated even when automatic propagation is disabled. - Parity assertions across sibling map entries are intentional. If a parity test fails, mirror the change to siblings (most common) or update the invariant test if the design intent has actually changed.
- Opt-in tool maps: Some tools are intentionally gated behind feature flags and use separate opt-in maps (e.g.,
MEMORY_AGENT_TOOL_MAPfor memory tools whenmemory.enabled === true;EPIC_AGENT_TOOL_MAPfor the architect-only Epic tools whenepic.mode.enabled === true, tested bytests/unit/agents/epic-tool-gating.test.ts). These tools must still have aTOOL_METADATAentry, aTOOL_MANIFESThandler, and a barrel export insrc/tools/index.ts, but they are excluded from (or included in) the derivedTOOL_METADATA.agentslists and merged into agent configs conditionally at build time. Opt-in maps must be validated by tests that verify: (a) tools do NOT appear when the feature is disabled, (b) tools DO appear when the feature is enabled, (c) the merged tool set is correct for each agent role. See the memory-tool merge path insrc/agents/index.tsandtests/unit/agents/memory-tool-gating.test.tsfor the current pattern. - Agent registration changes must test both legacy unprefixed AND multi-swarm prefixed agent names. v7.3.x regression: a schema default on
default_agent("architect") combined with strict equality demoted every*_architectto subagent in multi-swarm configs, so OpenCode showed the plugin as loaded but no swarm architect agents appeared. Tests that only used legacy unprefixedarchitectmissed the bug entirely. Any change to primary/subagent selection,default_agent, orgetAgentConfigsMUST include a multi-swarmswarms: { local: ..., mega: ... }test that asserts at least one prefixed agent ismode: 'primary'.
12. Release / cache hygiene
- A plugin fix is incomplete if users stay pinned to a stale OpenCode plugin cache. Install / update / cache changes must cover all known cache layouts, including the macOS / Windows variants documented in v6.86.9 (Layout 1
~/.cache/opencode/packages/opencode-swarm@latest, Layout 2~/.config/opencode/node_modules/opencode-swarm, Layout 3~/.cache/opencode/node_modules/opencode-swarm). - Cache-deletion code must use
isSafeCachePathfour-layer defense (depth, basename whitelist, recognized parent, canonical structure). - Never hand-edit
package.json#version,CHANGELOG.md, or.release-please-manifest.json. release-please owns those. - Every user-visible PR ships a
docs/releases/pending/<unique-slug>.mdfragment (mandatory — seecontributing.md). Do NOT compute a next version or createdocs/releases/vX.Y.Z.md; release-please owns the version, andscripts/release-notes-fragments.mjsaggregates pending fragments into the release PR / GitHub Release body. The aggregated text is the only narrative future users will see.
Invariant audit required in PRs
Every PR that touches a relevant area must include a ## Invariant audit section in its description (see .claude/skills/commit-pr/SKILL.md). The required format is:
## Invariant audit
- 1 (plugin init): touched / not touched — <evidence>
- 2 (runtime portability): touched / not touched — <evidence>
- 3 (subprocesses): touched / not touched — <evidence>
- 4 (.swarm containment): touched / not touched — <evidence>
- 5 (plan durability): touched / not touched — <evidence>
- 6 (test_runner safety): touched / not touched — <evidence>
- 7 (test writing): touched / not touched — <evidence>
- 8 (session state): touched / not touched — <evidence>
- 9 (guardrails/retry): touched / not touched — <evidence>
- 10 (chat/system msg): touched / not touched — <evidence>
- 11 (tool registration): touched / not touched — <evidence>
- 12 (release/cache): touched / not touched — <evidence>For every "touched" entry, the evidence must be a concrete artifact: a command run with its output, a test that proves the invariant, a grep showing no remaining anti-patterns, or a quoted spec citation. "Looks fine" is not evidence.
If you cannot prove a touched invariant from source and test output, do not push.
When in doubt
- Ask, don't guess. The patterns in this file are the result of prior outages — every one has a release note in
docs/releases/. - Prefer the smallest patch that closes the issue without unwired functionality, untested branches, or hidden regressions.
- Defense in depth is cheap; an unbounded await is not.
Other versions1 project uses a version of this.
Word for word · 1
- ZaxbyHub/opencode-swarm★ 517100% same
Edited · 0
None found.
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 28,693 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository66 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- .github/copilot-instructions.mdCopilot instructions
- ci-fix-monitorSkill
- codebase-review-swarmSkill
- commit-prSkill
- contributingSkill
- engineering-conventionsSkill
- fork-pr-operationsSkill
- guardrail-patternsSkill
- issue-tracerSkill
- mock-to-internals-migrationSkill
- pr-review-fixSkill
- qa-sweepSkill
- research-firstSkill
- running-testsSkill
- subprocess-safetySkill
- swarm-ci-monitorSkill
- swarm-implementSkill
- swarm-pr-feedbackSkill
- swarm-pr-reviewSkill
- swarm-pr-subscribeSkill
- swarmSkill
- tech-debt-ci-reviewSkill
- test-file-splitSkill
- unswarmSkill
- writing-testsSkill
- brainstormSkill
- clarifySkill
- clarify-specSkill
- codebase-review-swarmSkill
- commit-prSkill
- consultSkill
- councilSkill
- critic-gateSkill
- deep-diveSkill
- deep-researchSkill
- design-docsSkill
- discoverSkill
- durable-session-stateSkill
- editing-skillsSkill
- engineering-conventionsSkill
- executeSkill
- fork-pr-operationsSkill
- graphifySkill
- issue-ingestSkill
- issue-tracerSkill
- orchestrating-subagentsSkill
- phase-wrapSkill
- pre-phase-briefingSkill
- qa-sweepSkill
- research-firstSkill
- rust-crate-ciSkill
- specifySkill
- swarm-ci-monitorSkill
- swarm-implementSkill
- swarm-planSkill
- swarm-pr-feedbackSkill
- swarm-pr-reviewSkill
- swarm-pr-subscribeSkill
- swarm-resumeSkill
- The first 60 shown.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

