This document describes the protocol any agent must follow to participate in a
Marcus-coordinated project. It is the developer-facing complement to
prompts/Agent_prompt.md, which is the agent-facing
version of the same specification.
If you are building a runner (a system that spawns agents and connects them to Marcus), this document tells you what your agents must do.
If you are an agent author (writing the prompt your agent will follow),
use prompts/Agent_prompt.md directly — it is written
to be copied verbatim into your agent's system prompt.
Marcus is an MCP server. Any agent that can call MCP tools can participate in a Marcus project. The agent does not need to be Claude — it needs to speak Model Context Protocol.
Marcus exposes its interface at http://localhost:4298/mcp by default.
An agent must be capable of calling these tools:
| Tool | Purpose |
|---|---|
create_project |
Bootstrap the project: decompose a description into tasks on the board |
register_agent |
Announce agent identity to Marcus at startup |
request_next_task |
Pull the next available task from the board |
get_task_context |
Fetch dependency artifacts before starting work |
report_task_progress |
Report progress at milestones (25/50/75/100%) |
report_blocker |
Report a blocking problem and receive AI suggestions |
log_decision |
Record an architectural decision to the board |
log_artifact |
Store a file reference (spec, design doc, schema) |
Every run has two distinct roles. One agent plays project creator; the rest are workers. A single agent can play both roles (create, then immediately enter the work loop).
The creator calls create_project once to bootstrap the board:
create_project(
description: str, # natural language description of what to build
project_name: str, # name for the project board
options: dict # optional — e.g. {"num_agents": 3}
)
Returns recommended_agents, project_id, and the full task graph. When
create_project returns, tasks are on the board and immediately available.
Workers call register_agent and enter the work loop. They never call
create_project.
Every agent follows this sequence:
register_agent(
agent_id: str, # unique within this experiment
name: str, # display name
role: str, # e.g. "full-stack", "backend", "synthetic"
skills: list[str]
)
Call this exactly once. Do not re-register during a run.
a. request_next_task()
→ returns: task | {retry_after_seconds, retry_reason}
b. If no task: sleep retry_after_seconds, then goto (a)
Do not exit. "No task" is transient — leases may be recovering,
dependencies resolving, or the board completing. Stay alive.
c. If task received:
i. If task has dependencies: call get_task_context(task_id)
ii. Do the work
iii. Call log_decision for any significant architectural choice made
iv. Call log_artifact for any file produced (spec, schema, design doc, etc.)
v. Call report_task_progress at 25%, 50%, 75%, 100%
(if blocked: call report_blocker instead)
vi. Immediately call request_next_task (do not wait)
Exit when your runner signals completion. The board itself does not push
an exit signal to agents — that is the runner's responsibility. A common
pattern is to poll get_experiment_status in a separate monitor process
and shut agents down when the board is fully complete, but this is
runner-specific logic, not part of the agent protocol.
These are non-negotiable. Violating them breaks the coordination model:
-
Agents self-select work. Agents pull tasks via
request_next_task. Marcus never pushes. No runner should pre-assign tasks to specific agents. -
Agents make all implementation decisions. Marcus communicates WHAT to build and WHY. It never specifies HOW — no library choices, no file structure, no internal architecture. Two agents given the same task must be free to produce legitimately different implementations.
-
Agents communicate exclusively through the board. No agent-to-agent messages. No direct coordination. The board is the only channel.
- Task isolation. Only one agent holds a lease on any task at a time.
- Dependency ordering.
request_next_taskonly returns tasks whose dependencies are complete. Agents never need to check this themselves. - Lease recovery. If an agent dies mid-task, Marcus detects the expired lease and returns the task to the pool.
- Artifact routing.
get_task_contextreturns artifacts from dependency tasks. Agents do not need to know which task produced an artifact — Marcus walks the DAG. - Scope annotation. Dependency artifacts carry a
scope_annotationfield:in_scope(implement fully) orreference_only(do not implement, read for context only).
A runner is any system that:
- Starts one agent as project creator — it calls
create_projectvia Marcus and writesproject_info.jsonwhen complete. - Starts N agents as workers — they wait for
project_info.json, then enter the work loop above. - Optionally starts a monitor — polls
get_experiment_statusand callsend_experimentwhenis_runningflips to false.
The reference runner is
dev-tools/experiments/runners/spawn_agents.py,
which implements this pattern using Claude CLI agents in tmux panes.
To add support for a different agent runtime (LangGraph, AutoGen, Codex, etc.):
- Connect your agent to the Marcus MCP server at
http://localhost:4298/mcp. - Copy
prompts/Agent_prompt.mdinto your agent's system prompt (or translate it to your runtime's prompt format). - Ensure your runner implements the project creator / worker / monitor split described above.
- Validate against the synthetic agent test harness (see issue #383) — if a synthetic agent can complete an experiment end-to-end with your runner, your integration is conforming.
Creator: create_project(description, project_name) → board populated, tasks ready
Startup: register_agent
Work loop: request_next_task → [get_task_context] → work → log_decision/log_artifact → report_task_progress(100%) → repeat
No task: sleep retry_after_seconds → retry (do not exit)
Exit: runner-controlled
See prompts/Agent_prompt.md for the complete
agent-facing specification including artifact management, blocker workflow,
git conventions, and progress reporting format.