
Multi-agent dev team for opencode — 5 markdown contracts, plan → code → test → review → commit, zero
Multi-agent dev team for opencode — 5 markdown contracts, plan → code → test → review → commit, zero dependencies
·
A multi-agent development team built on pure opencode primitives.
6 markdown files. Zero dependencies. Zero vendor lock-in.
plan → code → test → review → commitAI coding agents are powerful but unpredictable when requirements live only in chat history. This team adds structure: agree on specs before code, write tests before implementation, review before merge — enforced through role contracts and tool permissions, not framework code.
Won an internal multi-agent bake-off (n=1) against two orchestration frameworks
on token economy, zero operator interventions, and deterministic judge score.
The current test battery is python3 scripts/validate.py (multiple
subprocesses, ~200 checks); see examples/RUN-BRIEF.md for the
run-brief template. Full run data available on request.
git clone https://github.com/dsaveliev/opencode-native-team.git
cd opencode-native-team
# Install into your project (agents + /team command + skills + example config)
./install.sh /path/to/your/project
# Or install globally — the team becomes available in every project
./install.sh --globalnpm install -g @fission-ai/openspec)scripts/validate.py)opencode.json with model routing (see examples/opencode.json.example)Three ways to run the team (pick one):
1. /team command (recommended) — open opencode in your project and type:
/team add a rate-limited events endpoint to the APIThe command runs the orchestrator with your text as the assignment. If
a TASK.md exists in the repo root, it takes precedence.
2. CLI one-liner — for scripts and experiments:
opencode run --agent orchestrator 'Read TASK.md and complete the assignment.'3. TASK.md flow — write the assignment into TASK.md, then use either
of the two methods above. The contract / deliverables / process
requirements for an example assignment live alongside the change.
The team then runs autonomously: openspec change → planner → coder → tester →
reviewer → commit, with a DECISIONS.md entry for every ambiguity it resolved.
If your repository already uses OpenSpec, run the team per feature on an already-written change:
/team-change add-user-ratelimitThe orchestrator validates the change, works on branch feat/<change-id>
(never main), and implements the existing proposal/design/tasks as-is —
corrections are limited to [x]-proofs and observability comments.
Merge/PR stays yours.
Adapt the installed config to the project:
golangci-lint run, make test,
docker compose ...). Keep the map a superset of the contract's
frontmatter rules — trimming silently strips protections (see the
complete-map rule above).go test ./... may not fit. Put package-scoped commands into
tasks (go test -race ./internal/billing/...) — faster and sharper for
coder briefs.DECISIONS.md
afterwards: it lists every point where the spec was ambiguous.For unattended, long-running runs, drop a RUN-BRIEF.md into the project
root (template: examples/RUN-BRIEF.md). Both
/team and /team-change detect it before starting and treat it as a
read-only execution-policy overlay: scope fences, reading and retry budgets,
gates, recovery policy, and the morning handoff.
The brief governs HOW the run proceeds — it may narrow execution, but never grants capabilities, never redefines acceptance criteria, and is never edited mid-run. TASK.md and OpenSpec artifacts keep owning WHAT. No brief — no behavior change. A minimal brief is ~15 lines (envelope, mission reference, decision authority, scope map, termination). Roadmap: recovery semantics (dispositions, dependency propagation) and observability (RUN-SUMMARY) arrive as follow-up changes.
Briefed runs also end with RUN-SUMMARY.md (terminal state, evidence table,
branch logs, one human action per blocker) and keep dispositions in
BLOCKERS.md; scripts/setup-sandbox.sh <dir> builds a disposable proving
ground for drills — see docs/recovery-experiment.md §3. Generate a brief
without typing canonical text: python3 scripts/gen-run-brief.py or
/team-brief (questions in, verbatim blocks out).
| Role | Mode | Can write? | Temp | Model | Description |
|---|---|---|---|---|---|
| orchestrator | primary | ✓ (commits only) | default | main | Team lead: openspec cycle, delegation, commits |
| planner | subagent | ✗ | default | fast | Decomposition, edge cases, dependency graph |
| coder | subagent | ✓ | default | main | TDD implementation |
| tester | subagent | ✓ | default | fast | Concurrency tests, entrypoint testability, test-contract mode |
| reviewer | subagent | ✗ | 0.1 | main | 5-axis review, verdict only |
| spec-reviewer | subagent | ✗ | 0.1 | fast | Adversarial completeness pass on openspec artifacts (10-dim matrix) |
Process (OpenSpec) → "in what order we agree"
Discipline (skills) → "how to do each type of work"
Team (these agents) → "who does what"The orchestrator contract embeds a skill mapping table (which skill at which OpenSpec stage) — deterministic dispatch, no ad-hoc selection.
| Layer | Enforcement |
|---|---|
| Subagents can't spawn subagents | task permission: deny-all, orchestrator allows exactly 4 roles |
| Subagents can't leave project dir | external_directory: deny |
| Read-only roles (planner, reviewer) can't write files | edit: deny (scalar — covers all paths) |
| Read-only roles can't browse the web | webfetch: deny |
| Coder/tester can't commit/push/reset | git command denies in bash permissions |
| Command injection mitigated | separator pattern denies (;, &&, |, `, $(, >, newline) |
⚠️ Separator patterns are mitigation, not a security boundary. The real boundary is:
edit: denyfor read-only roles, exact allow-patterns for test commands (no trailing*), andtask: denyfor all subagents. Coder/tester hold broad bash and are trusted accordingly.
Contracts are language-agnostic. Language-specific commands (test, vet, build)
live in your project's opencode.json:
{
"agent": {
"reviewer": {
"permission": {
"bash": {
"go test ./...": "allow",
"go vet ./...": "allow"
}
}
}
}
}An opencode.json.example with Go presets is installed by install.sh.
Complete-map rule (probed): if you override
permission.bashfor an agent inopencode.json, provide the complete map including its own"*": "deny"catch-all and repeat every allow the agent needs. A partial map is merged behind the contract's frontmatter denies — its allows get defeated (this exact bug shipped once: the reviewer'sgo testallow was silently overridden until a live run caught it).Autonomous (headless) runs work out of the box: the example config ships top-level
bash/editallows. They do not weaken subagent contracts — agent frontmatter denies take precedence over top-level allows (probed). Remove them if you prefer interactiveaskgating; trim the reviewer map only down to the contract's own rules —validate.pyenforces the superset.
Every run can carry a live dashboard: agents spawned, task progress, work log, commit timeline, token usage, ETA — served over localhost from the session DB + git + openspec tasks, zero dependencies.
http://127.0.0.1:<port>/ (fixed port, default 4731; binds
127.0.0.1 only). The page polls /state and repaints itself on data
change — no manual reload; if the server dies, the page shows a stopped
banner instead of quietly going stale. POST is reserved for the
control plane (/control/stop and /control/switch); see
docs/dashboard-control-support.md
for the supported/unavailable control matrix..opencode/team-dashboard.json over
installation ~/.config/opencode/team-dashboard.json (see
examples/team-dashboard.json): mode = ask (default — the team asks
once per run via the question tool; headless runs skip silently) |
always (starts every run, including --auto) | never, plus
refresh seconds, open_browser, port, coverage_ttl, inspect_ttl./team and /team-change start/stop the dashboard
automatically per mode (script resolved project-local first, then the
global copy). Out-of-band: team-dash (installed onto PATH by
install.sh) never asks questions and never touches the session itself,
so it is safe mid-run: with a directory argument it points the dashboard
there, without arguments it opens the freshest active run. One dashboard
per machine on the fixed port — switching projects happens in the
dashboard UI (View buttons in the Sessions & instances panel); if a run's
pre-flight finds the dashboard already serving another project, it is
re-pointed, not conflicted. A port held by a non-dashboard process still
fails loudly naming the holder. Manual: .opencode/scripts/team-dashboard.sh serve|stop|once|status <dir>.port
per project to run two dashboards side by side). After stop the final
HTML stays at tmp/team-dashboard.html. Concurrent serve calls on the
same port serialize through a per-port lock; one binds the socket, the
rest see the running server and re-point via switch_to.coverage_ttl (seconds; 0/off by
default — the tick must not race the tester agent). Observer reads
never run project tests; coverage is the explicit --export exception.enforced (h.2.1 grant). Session pause, session
cancel, and human takeover are explicitly labelled unavailable with
the reason (no runtime API in opencode, or — for takeover — pending a
revoke-before-human-write / reconciliation-before-resume implementation
in a separate change). The UI never claims a control the runtime
cannot enforce.Host only, an explicit
browser Origin policy (foreign origins are refused before the body is
read), JSON content-type required (HTML forms cannot trigger it), and
the request body is bounded (64 KiB max) with a 5-second read timeout,
strict integer PID, and a JSON-object body. The stop signal is
delivered only after the target's resolved executable is verified to
live under a trusted install prefix AND match the opencode binary name
AND the bound run manifest's start_ms minus a small slack (no recycled
PIDs from a previous run) AND the optional target_grant matches the
bound manifest's stop_grant field. A renamed or symlinked binary
(/bin/sleep as opencode) is refused. A copy of an authentic binary
run from /tmp is also refused. The confirmation names the blast
radius (all sessions of that process end); there is no automatic
SIGKILL escalation.~/.local/state/opencode-team/team-dashboard-<port>.json), written
atomically (temp + rename) under a per-port flock, so switching
A → B → A and stop from any directory work. Operation-specific
cleanup: a failing operation only removes the OWNER record it
created; a foreign OWNER on a different port is preserved.adopt_window state file (a bounded roll-back when a fresh server
meets an in-flight run). Model-authored text is escaped for script
context (< → \u003c) so it can never break out of the page.inspect_ttl > 0) shows the
opencode session list --format json output. It is display-only and
never flows into the control plane; it never authorizes a control.
Default: off. Effect preview: a table of recent sessions with id,
title, updated-ago, and directory.~ projection
(median of completed durations — an estimate, clearly marked). The task
currently being worked on is highlighted and shows live elapsed.Every run starts with a durable identity: the pre-flight runs
scripts/run-manifest.py init (writes RUN-MANIFEST.json with run_id,
git state, mission-source hashes, versions, budgets), and the first
delegation runs attach to bind the root and child sessions. The run id
is what every artifact and the dashboard label itself with.
Every run ends with a markdown run log exported by the same data path as
the dashboard (scripts/gen-team-dashboard.py --export <dir>), so the log
and the page can never disagree:
/team-change writes openspec/changes/<change>/RUN-LOG.md next to its
manifest; /team writes tmp/run-logs/<run_id>/RUN-LOG.md (gitignored,
run-scoped — same-day runs never collide).## Recommendations section: friction
observed during the run (slow tasks, retries, denials) translated into
concrete improvement suggestions for this framework — the feedback loop
that tunes prompts, permissions, and wave sizing.Team agents deny every external directory except the language toolchain
surface (Go module/build caches and ~/go/bin, npm, cargo, rustup, pip
caches) — go mod tidy, go get, npm install, cargo fetch work
inside subagents while foreign project directories stay denied. The
orchestrator carries an explicit bash ruleset: allow by default, deny
destructive/remote git (push, reset, revert, rebase, stash,
am, cherry-pick, checkout --, clean) — so its wave commits run
headless under --auto. examples/opencode.json.example mirrors the same
allowlist for project-mode installs. For your own (non-team) sessions on
this machine, extend ~/.config/opencode/opencode.json: add module
managers (go mod*, go get*, npm install*, npm ci*,
pip install -r*) to the bash allowlist and the same cache entries to
external_directory — then restart opencode.
Add technology-specific skills (golang, sql, k8s…) without touching contracts:
<project>/.opencode/team-skills.json (see
examples/team-skills.json) — each entry names a skill, a source path
(local pool) and the OpenSpec stages where it joins the dispatch table.scripts/sync-skills.sh <project> installs the skills into
.opencode/skills/ and pins every file in skills.lock (sha256 — same
integrity model as the vendor MANIFEST). Re-run verifies; --update
re-copies from sources.10 skills (12 files) from agent-skills
are vendored; every file — including references/*.md — is pinned with a
SHA-256 in vendor/MANIFEST.yaml. install.sh verifies full-length hashes and
rejects missing or unlisted files before copying.
install.sh (project mode) recommends two external OpenCode plugins —
context-mode (context/data plane:
sandboxed analysis, FTS5 retrieval, session continuity) and
Ponytail (implementation
discipline: YAGNI, minimal diffs) — by adding them idempotently to the
project's opencode.json plugin array. Nothing is vendored; role
permissions are untouched by the merge and the agent contracts deny the
ctx_* tools a read-only role must not reach (see the permission matrix in
docs/integrations.md). Opt out with
NATIVE_TEAM_PLUGINS=none ./install.sh ....
team-dash)team-dash (installed onto PATH by install.sh) opens the live
dashboard for a project. It is safe mid-run: never asks questions, never
touches running sessions.
Resolution rule: team-dash resolves scripts/team-dashboard.sh from
the repository that contains the current (or argument-provided)
directory via git rev-parse --show-toplevel. The launcher fails
loudly with a one-line error if the directory is not inside a git
repo, or the script is not in the repo's scripts/. The launcher
deliberately does NOT look at ~/.config/opencode/scripts/ — global
script copies are a historical artefact of an earlier design and
are ignored.
This change (since dashboard-launcher-reliability): install.sh no
longer copies the runtime scripts to ~/.config/opencode/scripts/.
The launcher is self-evidencing — every script is wherever the repo
says it is, and any git mv is immediately effective. Pre-existing
files in ~/.config/opencode/scripts/ from a prior install are
ignored; to clean them up:
rm -rf ~/.config/opencode/scripts/{,_}*.py \
~/.config/opencode/scripts/team-dashboard.shExamples:
# inside a project repo, or with a directory argument
team-dash .
team-dash /path/to/project
team-dash # picks the freshest recently-active runMIT
This repo does not document an install step we could read. Check the README on GitHub.
Paste this prompt into your agent. It reads this page and does the setup for you.
Read https://aiagentslisting.com/mcp/opencode-native-team to learn what the "Opencode Native Team" MCP server does and how to install it. Add it to my coding agent's MCP configuration as documented on that page, then confirm the server connects and list the tools it exposes.Agents can also browse this directory over MCP at https://aiagentslisting.com/api/mcp
This server doesn't publish a machine-readable tool manifest, and it doesn't expose a public endpoint we could ask. Connect the server locally to see what it exposes.
We could not run this server: it ships no npx package for our sandbox and no public endpoint. The four checks that need a live server are left out, and the grade tops out at B until one exists.
Maintain this server? Claiming proves you own the listing and earns the last 4 points.
Claim this listingPaste this line near the top of your repository's README. The badge always renders the current score.
[](https://aiagentslisting.com/mcp/opencode-native-team)Nothing comparable is listed yet.
No tagged releases on record.
Connect to the AI Agents Listing MCP server at https://aiagentslisting.com/api/mcp using the HTTP transport and complete the sign-in when it asks. Then call the submit_review tool for the listing "opencode-native-team" with a rating from 1 to 5 and a short note on what worked and what did not.No reviews yet
Be the first to share your experience with this tool.
One email a week. New agents, MCP servers and skills, and what is actually getting traction.