Skip to main content

The Doctrine

The behavioral contract in anchor/ANCHOR.md (scaffolded into projects as .anchor/ANCHOR.md), summarized. Every platform file, script, and MCP tool implements some slice of this.

Six Mythos behaviors

Discipline is a loop, not a list of good intentions:

  1. Clarify before acting — restate goal, constraints, acceptance criteria; ask one precise question if ambiguous, then stop.
  2. Plan before executing — explicit numbered plan; planning and doing never interleave.
  3. Decompose ruthlessly — each task fits one context window, touches one concern, verifies independently.
  4. Execute one step, then verify — tooling runs the checks; model claims are inputs, not evidence.
  5. Self-review as a separate pass — fresh-context critic against the original criteria.
  6. Know when to stop — two failed attempts at the same error = stop and escalate. Never a third.

Why external discipline beats better prompting

Small models drift, conflate planning with doing, declare unearned success, and fabricate under pressure. Telling them to "think carefully" doesn't fix this. What fixes it is structure outside the model:

  • Forced structure — templates with mandatory sections; a model that must fill ## Acceptance criteria cannot skip thinking about them. Outputs missing the required footer are rejected and retried by the pipeline, not forgiven. Only that footer crosses back into the coordinator's own context — fleet_metrics.py's extract_footer tolerantly pulls ## Result / ## How to verify / ## Deferred / concerns out of an executor's full reply (case/spacing drift, last-occurrence-wins), caps it so a transcript can't be smuggled inside ## Result, and orchestrate.py archives the raw reply to var/task-transcripts/<task-hash>.log for post-mortem instead of relaying it further.
  • One task per fresh context — context rot hits small models hardest; never run task chains in one conversation.
  • Declared budget + pre-flight gate — every task spec's ## Budget (context window, output ceiling) comes from tooling, not the model's guess; mythos-core rule 13 makes every executor print a fixed 6-item pass/fail block (goal, acceptance criteria, files-in-scope, budget, tier fit, task size) before doing any work, and stop on the first FAIL instead of plowing ahead.
  • Role separation — planner → executor → critic as three clean contexts outperforms one long chat, even on the same model. In the orchestrated path the split is harness-enforced by the scripts/roles.py capability map (planner writes only .plans/**; executor never .plans/** or its own spec; critic writes nothing), applied per phase by orchestrate.py and by the project-orchestrator MCP server's role-scoped toolsets. Role transitions are logged orchestrator events; single-model sessions keep the discipline by prompt alone.
  • Planned continuation instead of context rot — a task that outgrows its window degrades into a continuation, not a truncated answer. Near its declared ceiling the executor emits a structured handoff (templates/handoff.md: done + how each item was checked, remaining work as ready-to-dispatch sub-specs, decisions made, files touched, open concerns) and the orchestrator respawns a fresh context seeded with it — never a longer conversation. Mythos-core rule 15 requires the handoff; orchestrate.py decides when one is due from its own token accounting, rejects remaining work with no verify command, and refuses a continuation whose scope grew. Cap: 2 continuations, then back to the planner.
  • External verification — tests, linters, builds, and diff-scope checks decide done-ness. Fleet runs pair the model’s claim with actual verify exits in var/fleet-metrics/outcomes.jsonl; aggregate with fitness_report.py and prefer those rates when updating model-fitness prose.
  • Escalation paths — ambiguity, architecture, and twice-failed tasks go up a tier by rule, not by judgment. The twice-failed-task path is harness-enforced: fleet_metrics.py's should_stop reads the outcome ledger (not an in-memory count) and refuses a third dispatch to the same model on the same task. orchestrate.py consults it before every (re)dispatch — two recorded failures escalate one tier with both failures' evidence attached; two failures at the top available tier produce a structured human report even if the model never wrote one.

The templates

Five files in .anchor/templates/ (source: anchor/templates/) are the doctrine's working surface: plan.md (planner output; Value / Preferred models when using ./.plans — lane/lifecycle from path, not in-file Status/Lane), task-spec.md (the unit of dispatched work; its ## Budget section is what mythos-core rule 13's pre-flight check reads), handoff.md (what an executor emits instead of truncating when it approaches that budget — done / remaining sub-specs / decisions / files touched / open concerns, parsed by scripts/handoff.py into the next window's spec), review.md (critic pass), verification.md (tooling-filled done-ness table). The mythos-core.md system prompt binds any model to the six behaviors and the required output footer.

Tracked plans (./.plans)

Hard rule: docs describe current state, not plans

For every project following Anchor: documentation (README, docs/, CHANGELOG, blog, release notes, public prose) describes the project as it exists now — shipped code and public contracts. Never document the contents of .plans/ (especially drafts/, ready backlog, in-progress bodies, unfinished acceptance items) as product docs or roadmap. When a plan’s work ships, document the code and public contract — not the plan file. Allowed: documenting how the .plans/ workflow works when that is a shipped feature. Forbidden: “coming soon” from plan files; changelog/blog of unshipped backlog; citing plan slugs/paths as documentation.

In projects that use Anchor, git-tracked plans live under .plans/ (dotdir; do not ignore the whole tree). Optional private plans: <slug>.local.md (gitignored via .plans/.gitignore). The .local suffix is sticky on promote and agent lane moves — only a human rename (or /draft --shared at create) makes a plan tracked. Path is authoritative:

Ready lanes are bugs/ then features/ (within a lane by Priority P1→P2→P3, default P2, then Value, then oldest first); agents move claimed work to in-progress/ (only the claimer may continue — others ignore); may park half-baked or stuck work in ambiguous/ or blocked/; when Done when holds agents move work to review-needed/ for human sign-off (human runs /review: AI critic + survey — Approve merges feature/<slug>dev then → completed/, Needs Work → bugs|features/) — or, when the operator answers /work's end-of-run culmination question with merge to dev now and the branch clears the scoped-merge gate, straight to completed/ with a ## Handoff note recording the skipped review. Agents never self-certify to completed/ and never merge unasked; unattended runs always finish to review-needed/, and main is reached only through /review's promotion survey (see How work reaches dev); never execute drafts/, ambiguous/, blocked/, or review-needed/. Do not put Lane: or Status: inside plan files. Promotion from drafts: /draft --promote <slug> (user-authorized; agent infers bugs vs features from the plan) or a human move — never from /work or fleet pullers. Prefer /draft to create/list/load drafts, /work to execute ready plans, and /review for review-needed/ sign-off. Headless: scripts/work_once.py --once --tier mid --agent-id …. Multi-tier pollers: Fleet workers. Preferred orchestrator: anchor <dir> --set-orchestrator … (if unset, frontier/near-frontier may act as temporary coordinator; lesser models escalate).

Right-size before you start

Escalation isn't the only direction that matters — before spending an expensive tier's tokens, the model should ask whether the task actually needs them:

Boilerplate, formatting, a rename, or one well-specified function gets a first-line SUGGEST-DOWNGRADE: <cheaper> — <reason> (stop unless insisted), instead of silently burning frontier capacity. Too-hard work uses SUGGEST-ESCALATE; a specialty mismatch (e.g. general-chat for multi-file software) uses SUGGEST-REROUTE — lateral, not always stronger. See Model fitness. scripts/router.py classifies fleet roles; interactive agents use the first-line protocol, not silent model swap.

The registry itself gets right-sized the same way: no one gets the raw scripts/endpoints.yaml in context. router.summarize_endpoints() generates a capped one-line-per-endpoint summary (name, tier, context size, one capability phrase — no base_url, model name, or quirk values), which the orchestrator hands to coordinator/planner roles and to executor tasks only when the task itself is routing-related. Full endpoint detail is a deliberate on-demand lookup — the model-fleet MCP's lookup_endpoint(name) tool, or router.endpoint_detail() directly — never a default include; ANCHOR_API_KEY stays an environment read at request time, never a registry field.

Right-sizing is one of the reasons Savings can be so large — please consider donating to help support this project.

Surface the best-fit skill

A discipline is only useful if people use it, and most people never learn the slash-command that would have saved them the manual work. So before acting on a request, an Anchor agent judges whether a skill or command already available in the session would do it faster or more correctly — and if one clearly fits, says so in a single line and offers to use it, then proceeds anyway:

The guardrails keep it from becoming noise: only skills actually loaded in the session are ever named (never an invented command), a given skill is surfaced at most once per session, and the tip fires only when the concrete efficiency win can be named. It is a suggestion, never a gate — the agent does the requested work the same turn. The effect is passive discovery: users meet cutting-edge features through the work they were already doing, instead of having to know the command exists first.

When the tier you want is rationed

Subscription caps — session, rolling-window, weekly — are a scheduling problem, not a failure. The order is: reroute to the next model in priority order that clears the task's fitness floor, else wait for a near reset, else stop and report with a checkpoint. The trap is the middle column of model fitness: rerouting boilerplate down a tier is free, rerouting architecture or security work down a tier buys confident wrong answers. Never let a quota reset set the quality bar, and never let a harness downgrade you silently. Full doctrine: capacity routing.

Code quality defaults

SOLID principles apply by default, and composition follows whatever the target language calls idiomatic — traits (Rust), Protocols/narrow ABCs (Python), interfaces (TypeScript/Go/Java/C#), modules (Ruby) — never a deep inheritance tree. Dead code, unreachable branches, and spaghetti control flow don't get left behind; a shortcut taken under pressure is named in ## Deferred / concerns, never buried. scripts/anchor.py detects a scaffolded project's language from marker files (composer.json, package.json, Cargo.toml, …) and writes the resolved idiom to ANCHOR-CONVENTIONS.md; co-located package.json yields to a backend marker when both are present. When detection fails, it asks, proposing the saved config.sh language default if one exists.