2026-07-29 17:57:07 +02:00
|
|
|
# Agent adapters
|
|
|
|
|
|
|
|
|
|
An adapter makes the task manager work with a particular coding agent.
|
|
|
|
|
Core never speaks any vendor's language — it launches jobs and ingests
|
2026-07-29 19:46:23 +02:00
|
|
|
normalized events; adapters translate at the edge. Two ship as reference
|
|
|
|
|
implementations: `claude/` (the default) and `opencode/`. Select with
|
|
|
|
|
`BOARD_AGENT_ADAPTER` in `local/.env`; a directory of the same name under
|
|
|
|
|
`local/adapters/` overrides the core one.
|
2026-07-29 17:57:07 +02:00
|
|
|
|
|
|
|
|
## The contract
|
|
|
|
|
|
|
|
|
|
An adapter is a directory with two executables:
|
|
|
|
|
|
|
|
|
|
### `run` — execute one headless job to completion
|
|
|
|
|
|
2026-07-29 19:46:23 +02:00
|
|
|
- env in: `AGENT_PROMPT` (the full prompt), `AGENT_MODE` (the launch
|
|
|
|
|
intent, below), `AGENT_COMMANDS` (the project's allowed command
|
2026-07-30 07:37:39 +02:00
|
|
|
prefixes, below), `AGENT_MODEL` (optional, below), `AGENT_CWD`, and
|
|
|
|
|
the `BOARD_*` passthrough (`BOARD_AGENT_ID`, `BOARD_TASK`,
|
|
|
|
|
`BOARD_PORT`) which your event bridge must forward with every event.
|
2026-07-29 17:57:07 +02:00
|
|
|
- stdout is captured by the board as the job log. The prompts instruct the
|
|
|
|
|
agent to end with marker lines (`NOT READY:`, `RELEVANCE REVIEW:`,
|
|
|
|
|
`PR REVIEW:`, `ADDRESSED:`) — the board parses them from this output, so
|
|
|
|
|
the agent's final text must reach stdout.
|
|
|
|
|
- exit 0 = completed; anything else = failed.
|
2026-07-30 07:35:28 +02:00
|
|
|
- The workflow brief the prompts point agents at is `AGENTS.md` at the
|
|
|
|
|
repo root (`CLAUDE.md` beside it is only a compatibility pointer).
|
|
|
|
|
Vendors that read `AGENTS.md` from the working directory's tree
|
|
|
|
|
natively — opencode does, as does current Claude Code — pick it up in
|
|
|
|
|
every worktree with no adapter work; `run` never needs to inject it.
|
2026-07-29 17:57:07 +02:00
|
|
|
|
2026-07-29 19:46:23 +02:00
|
|
|
### Launch intents (`AGENT_MODE`)
|
|
|
|
|
|
|
|
|
|
Core signals *intent*; every adapter maps it to its vendor's permission
|
|
|
|
|
mechanism. Headless runs have no human at a permission prompt, so
|
|
|
|
|
anything not auto-approved is denied — grant each intent exactly the
|
|
|
|
|
side effects its prompt demands, and never a blanket allow-everything
|
|
|
|
|
(the worktree is isolated, the shell is not):
|
|
|
|
|
|
|
|
|
|
- `work` — implement, test, commit in an isolated worktree. May edit
|
|
|
|
|
files, run local git bookkeeping (`git add/commit/status/diff`) and
|
|
|
|
|
the project's `AGENT_COMMANDS`. No push.
|
2026-07-30 07:57:27 +02:00
|
|
|
- `act-pr` — the work stance, plus `git push` (the PR must update),
|
2026-07-29 19:46:23 +02:00
|
|
|
reading the PR's reviews and line comments (`gh pr view`, `gh pr
|
2026-07-30 07:57:27 +02:00
|
|
|
diff`, `gh api`), and `git fetch`/`git merge` so a conflicted PR can
|
|
|
|
|
be resolved by merging main into the branch. Resolution is additive
|
|
|
|
|
only — the branch is public — so `git rebase` and the force-push
|
|
|
|
|
spellings must be denied, not merely unlisted (a plain `git push`
|
|
|
|
|
allow would otherwise cover them).
|
2026-07-29 19:46:23 +02:00
|
|
|
- `review` — read-only on the working tree: no edit tools, no commits.
|
|
|
|
|
May read a PR (`gh pr view`, `gh pr diff`, read-only git) and post the
|
|
|
|
|
verdict (`gh pr review`, `gh pr comment`).
|
|
|
|
|
|
|
|
|
|
### The project's allowed commands (`AGENT_COMMANDS`)
|
|
|
|
|
|
|
|
|
|
The git/`gh` grants above are universal; which test/check commands a
|
|
|
|
|
project's agents run is project knowledge. It arrives as comma-separated
|
|
|
|
|
plain command *prefixes* — `BOARD_AGENT_COMMANDS` in `local/.env`, e.g.
|
|
|
|
|
`python3 -m unittest,npm test` — never in any vendor's rule syntax. Each
|
|
|
|
|
adapter renders them natively; both shipped rule languages are
|
|
|
|
|
prefix-pattern based, so the translation is mechanical:
|
|
|
|
|
|
|
|
|
|
- claude → `Bash(git commit:*)`-style allow-rules in the generated
|
|
|
|
|
settings JSON (`claude/hook_settings.py`)
|
|
|
|
|
- opencode → `"permission": {"bash": {"*": "deny", "git commit *":
|
|
|
|
|
"allow"}}` in a generated config, wildcard rules, last match wins
|
|
|
|
|
(`opencode/permission_config.py`)
|
|
|
|
|
|
2026-07-30 07:37:39 +02:00
|
|
|
### The model (`AGENT_MODEL`) — optional
|
|
|
|
|
|
|
|
|
|
Absent = the vendor's own default: launch without any model argument and
|
|
|
|
|
let your CLI resolve it however it normally would. When set, it is an
|
|
|
|
|
opaque vendor-native model name — a claude alias, an opencode
|
|
|
|
|
`provider/model-id` — that core never validates or interprets; pass it
|
|
|
|
|
through untranslated (claude → `--model "$AGENT_MODEL"`, opencode → the
|
|
|
|
|
generated config's `model` key). Never send your vendor an empty value:
|
|
|
|
|
the board only sets the variable when a model is actually configured
|
|
|
|
|
(`BOARD_AGENT_MODEL` and its per-intent overrides in `local/.env`).
|
|
|
|
|
|
2026-07-29 17:57:07 +02:00
|
|
|
### `wire` — wire live-session visibility into the host project
|
|
|
|
|
|
|
|
|
|
Called by `install.py` with the project root as argv[1] (plus `--dry-run`).
|
|
|
|
|
Idempotently make the project's own interactive sessions report events —
|
2026-07-29 19:46:23 +02:00
|
|
|
however your platform allows (Claude Code: hooks in `.claude/settings.json`;
|
|
|
|
|
opencode: a plugin shim in `.opencode/plugin/` subscribing to its event
|
|
|
|
|
bus). Print a report; exit 0 on ok/fixed. If the platform has no way to
|
|
|
|
|
observe sessions, be a no-op with an honest message: the board still runs
|
|
|
|
|
headless jobs via `run`, you just lose the live play-by-play.
|
2026-07-29 17:57:07 +02:00
|
|
|
|
|
|
|
|
### Events — the normalized schema (v1)
|
|
|
|
|
|
|
|
|
|
POST to `http://127.0.0.1:$BOARD_PORT/api/events`:
|
|
|
|
|
|
|
|
|
|
{"v": 1, "session": str, "kind": str, "summary": str,
|
|
|
|
|
"file"?: str, "cmd"?: str, "detail"?: str, "ok"?: bool,
|
|
|
|
|
"running"?: bool, "agent"?: $BOARD_AGENT_ID, "task"?: $BOARD_TASK}
|
|
|
|
|
|
|
|
|
|
kinds: `session end idle edit read search command test check git plan
|
|
|
|
|
subagent web other`. `running: true` marks an in-flight action (shown as
|
|
|
|
|
the live line, not appended to the timeline); follow it with the completed
|
|
|
|
|
event. `kind: idle` = finished responding; `kind: end` = session over.
|
|
|
|
|
Classification happens in YOUR emitter — core never sees vendor payloads.
|
|
|
|
|
|
2026-07-29 19:46:23 +02:00
|
|
|
## Writing one
|
2026-07-29 17:57:07 +02:00
|
|
|
|
2026-07-29 19:46:23 +02:00
|
|
|
Read the two shipped adapters side by side — they are small and map the
|
|
|
|
|
same three intents onto very different vendor mechanisms. The essentials:
|
|
|
|
|
|
|
|
|
|
- `run`: launch your agent headlessly with permissions generated from
|
|
|
|
|
`AGENT_MODE` + `AGENT_COMMANDS`; make sure the final output lands on
|
|
|
|
|
stdout and the exit code passes through.
|
|
|
|
|
- `wire`: install your platform's observer (hook, plugin) into the
|
|
|
|
|
project so sessions POST the normalized schema with the `BOARD_*` env
|
|
|
|
|
forwarded.
|
2026-07-29 17:57:07 +02:00
|
|
|
- Events beat perfection: start with `session`/`end` plus a generic
|
2026-07-29 19:46:23 +02:00
|
|
|
`command` per tool call, refine kinds later — `opencode/plugin.js`
|
|
|
|
|
starts exactly that coarse on purpose.
|