Agent contract¶
The coding agent is any shell command that can:
- Read the task from
FORGEO_TASK. - Work on the repository in the current directory.
- Report its outcome via exit code.
agent_command: "claude -p \"$FORGEO_TASK\""
Environment¶
Launched with the repo as cwd and:
| Variable | Value |
|---|---|
FORGEO_TASK |
Full instruction — project context (if task_context), title, description, and "Acceptance criteria:" when present. |
FORGEO_REPO |
Absolute repo path. |
FORGEO_BRANCH |
Target branch (default main). |
agent_env keys |
Extra vars from config. |
| Inherited env | Daemon's own environment. |
FORGEO_* always wins. For refactoring runs (empty backlog), FORGEO_TASK carries the refactor_prompt (task ID REFACTOR).
When task_context is set, its file contents are prepended under # Project context before # Task. Re-read each run; missing file is a warning.
Exit codes¶
| Code | Outcome | What happens |
|---|---|---|
0 |
SUCCESS | git add -A && git commit as <title> (#<id>), push if remote set, task → COMPLETED. |
no_changes_exit_code (default 3) |
SUCCESS, no changes | Task → COMPLETED without commit. Tree must be clean. |
blocked_exit_code (default 2) |
BLOCKED | Partial work committed as <title> [partial], blocker_reason saved, notifications sent, task → BLOCKED, BLOCKER.md rendered next cycle. |
| anything else | ERROR | Changes discarded (git reset --hard + clean -fd), task → FAILED, failure_reason saved. |
FAILED stays FAILED until human reopens (or auto-retried via failed_retry_max). BLOCKED is never auto-retried.
The no-change contract¶
Forgeo cannot distinguish "deliberately no changes" from "did nothing":
- Exit
0with unchanged tree → retriedno_changes_retry_maxtimes in the same cycle, thenBLOCKED(neverFAILEDor silentCOMPLETED). - To complete without code change, exit
no_changes_exit_codewith a clean tree. Reporting no-change while leaving uncommitted work →FAILED.
Refactoring passes are exempt — a refactor finding nothing to improve is a normal success.
Timeouts¶
agent_timeout_seconds kills the agent after N seconds (error: timed out after <n>s). Unset = no timeout. A run overrunning interval_minutes is never killed — the next cycle is skipped. Tip: leave unset for long agents and rely on skip-on-overlap.
Output¶
Stdout/stderr are prefixed [stdout]/[stderr] and the last 1000 lines are kept. On BLOCKED, the agent's questions (or output) become blocker_reason and the BLOCKER.md section (last 10 lines). Per-run tails go to runs.jsonl (run_output_lines); per-task agent_response is bounded by agent_response_lines.
Git contract¶
The agent should not run git itself — Forgeo handles it:
- Make changes in the repo.
- Do not
git add/commit/push/reset. - Exit
0for commit,no_changes_exit_codefor intentional no-op,blocked_exit_codefor human input, anything else for discard.
Embed the contract in your prompt:
agent_command: >
opencode run --auto "Work on the repository at the current working directory.
Make the requested changes and nothing else. Do NOT run git commit/push/add.
Verify with tests. Read AGENTS.md and CONTEXT.md if present; update them
if your change affects the overview.
$FORGEO_TASK"
Concurrency¶
One agent per Forgeo: backlog.lock prevents a second start/once, and backlog.run makes a waking daemon skip while a run is active.