Files
istosandClaude Fable 5 8d08470530 Give act-pr agents a conflict-resolution path, additive only
Both adapters' act-pr stances gain git fetch and git merge so a
conflicted PR can be resolved by merging main into the branch. History
rewriting is denied outright rather than merely unlisted — the plain
'git push' allow would otherwise cover the force spellings — via deny
rules in the claude settings and last-match-wins deny globs in the
opencode config. git rebase stays absent and denied in both.

act-pr.md teaches the flow: merge origin/main, honour both sides'
intent, tests green, the resolution in its own commit naming files and
choices, covered explicitly in the report — and refuse semantic
conflicts (main made the branch's premise false) by aborting the merge
and naming the collision for a human. The adapters README records the
widened contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 07:57:27 +02:00

5.9 KiB

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 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.

The contract

An adapter is a directory with two executables:

run — execute one headless job to completion

  • env in: AGENT_PROMPT (the full prompt), AGENT_MODE (the launch intent, below), AGENT_COMMANDS (the project's allowed command 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.
  • 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.
  • 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.

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.
  • act-pr — the work stance, plus git push (the PR must update), reading the PR's reviews and line comments (gh pr view, gh pr 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).
  • 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 prefixesBOARD_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)

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).

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 — 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.

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.

Writing one

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.
  • Events beat perfection: start with session/end plus a generic command per tool call, refine kinds later — opencode/plugin.js starts exactly that coarse on purpose.