This guide is for solo builders who have forked Agent OS and want to adapt it to a different product, a different agent lineup, or a different workflow. It documents the high-leverage customization entry points with working examples. If you are only trying to run Agent OS on your own repo with the default setup, read the README quickstart instead — this guide is for bending the system, not booting it.
Everything below is a "change this file, redispatch a task, done" pattern. No refactoring required.
- Fork checklist
- Agent routing
- Adding a new agent
- Task dispatch
- Prompt configuration
- Objectives & scoring
- Per-repo overrides
- Pre-commit & secret guard
- What NOT to fork
Before you customize anything, do these once:
# 1. Fork on GitHub, then clone your fork
git clone https://github.com/<you>/agent-os && cd agent-os
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# 2. Create your own config — config.yaml is .gitignored
cp example.config.yaml config.yaml
$EDITOR config.yaml
# 3. Enable the secret guard so you never commit config.yaml or a bot token
git config core.hooksPath hooks
# 4. Create an objective file for your repo
cp objectives/example.yaml objectives/myrepo.yaml # local-only; gitignored
$EDITOR objectives/myrepo.yamlFiles that stay local-only on your fork (already in .gitignore):
config.yaml, objectives/<repo>.yaml (except example.yaml),
runtime/, .agent_result.md.
Where: config.yaml → agent_fallbacks (read by queue.py:get_agent_chain at orchestrator/queue.py:2352).
The queue picks an agent by task_type, falls back down the list on failure,
and skips agents that the health gate has benched.
# config.yaml
default_agent: auto
default_task_type: implementation
agent_fallbacks:
implementation: [omp, codex, claude, gemini]
debugging: [omp, claude, codex, gemini] # omp first (fast), claude fallback for bugs
architecture: [omp, claude, codex] # planning needs context
research: [omp, claude, codex]
docs: [omp, claude, codex]
design: [omp, claude, codex]
content: [omp, claude, codex]
browser_automation: [omp, claude, codex, gemini]
# The strategic planner has its own narrow list (it is control-plane, not task-plane):
planner_agents: [omp, claude, codex]Valid agents today: omp, claude, codex, gemini
(VALID_ASSIGNABLE_AGENTS in orchestrator/queue.py).
OMP runs GLM-5.2 via OpenRouter as the fast first-attempt agent; the rest are fallbacks.
Per-agent timeout:
agent_timeout_minutes:
omp: 30
codex: 40
claude: 45
gemini: 35Pinning one task to one agent. Put this in the GitHub issue body:
## Agent Preference
claudeparse_issue_body (orchestrator/github_dispatcher.py:88) lifts it into
meta["agent"]; get_agent_chain then prepends it to the fallback list.
To wire a brand-new CLI agent (e.g. llama) into the pool:
Step A — add a runner branch in bin/agent_runner.sh:
elif [ "$AGENT" = "llama" ]; then
"${LLAMA_BIN:-llama}" --prompt-file "$PROMPT" --autoStep B — register it in orchestrator/queue.py:2349:
VALID_ASSIGNABLE_AGENTS = {"auto", "omp", "claude", "codex", "gemini", "llama"}Step C — add to a fallback chain in config.yaml:
agent_fallbacks:
implementation: [llama, codex, claude]
agent_timeout_minutes:
llama: 30Step D — give it pricing so cost tracking works (cost_tracking.agent_models in config.yaml):
cost_tracking:
agent_models:
llama: "meta/llama-3.1-70b"
model_overrides:
llama:
input_per_million_tokens: 0.5
output_per_million_tokens: 0.75Any agent you add must obey the .agent_result.md contract (see README → Key
Design Choices). If your CLI does not write that file, wrap it in a shell
script that extracts its output and emits the contract. Everything downstream
— scorer, PR monitor, log analyzer — keys off that file.
Where: orchestrator/github_dispatcher.py:parse_issue_body (line 88),
orchestrator/task_formatter.py:FORMAT_PROMPT (line 11).
Agent OS reads GitHub issue bodies for these sections:
## Goal
<what you want done>
## Success Criteria
- <measurable outcome>
## Task Type
implementation
## Agent Preference
auto
## Constraints
- Prefer minimal diffs
## Context
<optional background>
## Base Branch
main
## Branch
feature/custom-name # optional; auto-generated if omittedIf the body is free-form, task_formatter.format_task calls Claude Haiku
(configurable via FORMATTER_MODEL env var) to rewrite it into this shape
before dispatch.
Task types are just strings. The only place they are gated is the routing
table. To introduce cost_optimization:
- Add it to the
FORMAT_PROMPTwhitelist inorchestrator/task_formatter.py:29:task_type: one of implementation, debugging, architecture, research, docs, browser_automation, design, content, cost_optimization.
- Add a fallback chain in
config.yaml:agent_fallbacks: cost_optimization: [claude, codex]
- Write issues with
## Task Type\ncost_optimization.
That is it. No schema migrations, no registration step — task_type is metadata that flows through the queue unchanged.
priority_weights:
prio:high: 30
prio:normal: 10
prio:low: 0The dispatcher reads GitHub labels like prio:high and adds the matching
score when ranking the Ready queue.
Where: orchestrator/queue.py:write_prompt (line 1936) is the single
assembly point. It layers:
- Task metadata (YAML front-matter)
- Task body (from the issue)
- Repository Context — built by
repo_context.build_execution_context(orchestrator/repo_context.py:271). Reads, in order:README.md(the## Goalsection)NORTH_STAR.mdSTRATEGY.mdPLANNING_PRINCIPLES.mdRUBRIC.md(optional)- For research/debugging tasks:
PRODUCTION_FEEDBACK.md,PRODUCT_INSPECTION.md,PLANNING_RESEARCH.md
CODEBASE.md— auto-maintained memory of recent work- Dispatch Context — recent git state, objective alignment, sprint directives
No code change required. Drop your product vision into NORTH_STAR.md,
your sprint rules into STRATEGY.md, your quality bar into RUBRIC.md.
Every agent invocation picks them up on the next run:
# NORTH_STAR.md (yours)
We optimize for:
- Weekly active users (target: 10k by Q3)
- Free→paid conversion (baseline 2.1%, target 3.5%)
- Core web vitals: LCP < 2s on p75
Never: ship features that increase LCP by more than 100ms.To pipe a file the system does not know about (e.g. SECURITY_BASELINE.md)
into every prompt, add a reader in orchestrator/repo_context.py and list it
in build_execution_context:
# orchestrator/repo_context.py
def read_security_baseline(repo_path: Path) -> str:
return _read_file_section(repo_path / "SECURITY_BASELINE.md", max_chars=2000)
def build_execution_context(repo_path, task_type, body):
sections = [
("Product Goal (README.md)", read_readme_goal(repo_path)),
("North Star (NORTH_STAR.md)", read_north_star(repo_path)),
...
("Security Baseline (SECURITY_BASELINE.md)", read_security_baseline(repo_path)),
]
...Budget it — every section eats into the agent's context window. Keep custom sections under ~2 KB and show only the parts that change behavior.
write_prompt also embeds the required output schema (the STATUS: / DONE: / NEXT_STEP: block you see at the bottom of every prompt). Downstream code
(queue.parse_agent_result, pr_monitor, log_analyzer) parses these
fields. If you change the schema, you also change the parsers — generally
don't, unless you are adding a new field alongside the existing ones.
Where: objectives/<repo>.yaml, loaded by
orchestrator/objectives.py:load_repo_objective. One file per managed repo.
This is the hinge for evidence-driven planning. The strategic planner, the backlog groomer, and the outcome attribution loop all read the objective file to decide what "shipped something useful" means.
# objectives/myrepo.yaml (local-only, never committed)
version: 1
repo: "myorg/myrepo"
product_name: "My SaaS"
primary_outcome: "Grow profitable activations"
evaluation_window_days: 28
interpretation_scores:
improved: 1.0
unchanged: 0.0
regressed: -1.0
inconclusive: -0.35
metrics:
- id: "activations"
name: "Weekly activations"
weight: 0.5
direction: "increase"
source:
type: "file"
path: "~/.local/share/agent-os/evidence/myrepo/activations_latest.yaml"
provenance: "Mixpanel export written nightly by cron"
trust_level: "high"
outcome_check:
type: "file"
path: "~/.local/share/agent-os/evidence/myrepo/activations_post_merge.yaml"
measurement_window_days: 7
comparison_window: "7 days after merge vs 7 days before"
- id: "infra_cost"
name: "Cost per activation (USD)"
weight: 0.5
direction: "decrease"
source:
type: "file"
path: "~/.local/share/agent-os/evidence/myrepo/cost_latest.yaml"
provenance: "GCP billing export"
trust_level: "high"
outcome_check:
type: "file"
path: "~/.local/share/agent-os/evidence/myrepo/cost_post_merge.yaml"
measurement_window_days: 14
comparison_window: "14-day cost ratio post-merge vs prior 14 days"Rules of thumb:
- Weights should sum to ~1.0. They control how the scorer blends signals.
- Keep evidence outside the repo (
~/.local/share/agent-os/evidence/...) so metric snapshots never enter git. - If the objective file is missing, the planner and scorer fall back to raw execution metrics (task success rate, completion time). The system keeps running; it just gets less product-aware.
Where: config.yaml → github_projects.<key>.repos[].
Everything global can be overridden per repo. This is how you run Agent OS across multiple repos with different cadences, different agents, different objectives:
github_projects:
my-project:
repos:
- github_repo: "myorg/frontend"
path: "/srv/repos/frontend"
automation_mode: full
plan_size: 5
sprint_cadence_days: 7
agent_fallbacks:
implementation: [claude, codex] # frontend → claude-first
quality_harness:
enabled: true
suites: [unit, browser_e2e]
suite_commands:
unit: "npm test"
browser_e2e: "npx playwright test"
- github_repo: "myorg/backend"
path: "/srv/repos/backend"
automation_mode: dispatcher_only # manual Ready → dispatch → PR only
agent_fallbacks:
implementation: [codex, claude] # backend → codex-firstautomation_mode:
full— planner, groomer, log analyzer, PR monitor all activedispatcher_only— you manually move issues to Ready; everything else is off
Flip modes at runtime from Telegram: /repo mode frontend dispatcher
(see README → Pause & Resume).
Where: hooks/pre-commit. Enable with git config core.hooksPath hooks.
The hook blocks:
config.yaml(holds your bot token)objectives/*.yamlexceptobjectives/example.yaml.agent_result.md(worktree artifact, never part of a commit)- Any staged diff line matching the Telegram bot-token shape (
\d{9,10}:[A-Za-z0-9_-]{35})
To extend it (e.g. block .env), add a case arm to hooks/pre-commit:22:
.env|.env.local)
red "✖ refusing to commit $f — environment secrets."
fail=1
;;Bypass only for a verified false positive: git commit --no-verify.
Things that look customizable but usually aren't worth touching:
.agent_result.mdcontract — every downstream module parses it. Changing it means patching the scorer, PR monitor, log analyzer, and supervisor. Add fields, don't rename.CODEBASE.mdformat — written byorchestrator/codebase_memory.pyafter every merge. Rewriting its shape breaks the planner's "recent changes" prompt section.- Mailbox directory layout —
runtime/mailbox/<task_id>/{task.md,result/}is assumed by the queue and the supervisor. Move it withmailbox_dirinconfig.yaml, not by renaming folders. - GitHub Project status field names —
Ready,In Progress,Blocked,Doneare configurable viagithub_project_*_valueinconfig.yaml. Keep four states; the dispatcher, PR monitor, and groomer all encode this transition model.
If you find yourself wanting to change one of these, open an issue describing the use case before you patch — there is usually a less invasive entry point.
| Topic | File |
|---|---|
| Architecture, components, data flow | docs/architecture.md |
| Task execution, handoff contract, retry logic | docs/execution.md |
| Full configuration reference | docs/configuration.md |
| Deployment on a $5 VPS | docs/deployment-guide.md |
| Cost tracking model | docs/cost-tracking.md |
| Cron entrypoints | CRON.md |
Questions or patterns worth adding to this guide? Open a discussion.