# Agent Doctrine Layers How `compose_prompt` (`roboco/agents/factories/_base.py`) assembles an agent's system prompt at spawn, and where the optional doctrine layers sit. Doctrine is prompt-only — no hooks, no grok-path changes — and gated by a single flag. ## Layer order `compose_prompt(role, team, agent_slug, base_path=None, ambient=None)` concatenates these layers with `---` separators, skipping any that resolve to empty/`None`: 1. `_tool_load_directive_layer(role)` — top-of-prompt "your tools are ready" block. 2. `_lifecycle_layer(prompts_path, role)` — autogenerated verb-surface fragment (`_generated/lifecycle-.md`). 3. `base.md` — universal base rules (identity separation, envelope shapes, ground rules). 4. `fable_doctrine_layer(prompts_path)` — vendored Fable behavioral doctrine (0.18.0). 5. `ponytail_doctrine_layer(prompts_path, role)` — vendored Ponytail build-laziness doctrine (0.19.0), role-scoped, bundled with Fable. 6. `_role_layer(prompts_path, role)` — role prompt (`roles/.md`). 7. `_autogen_verbs_layer(prompts_path, role)` — per-role verb-signature table. 8. `_team_layer(prompts_path, team)` — team prompt (`teams/.md`). 9. Identity file (`identities/.md`). 10. `ambient` — optional architectural-conventions ambient block (`conventions_ambient_layer`). The order is load-bearing: lifecycle precedes base so the agent reads its allowed verb surface before any other instruction. Reordering changes model attention priority. ## Fable doctrine (0.18.0) `fable_doctrine_layer(prompts_path)` returns the vendored `agents/prompts/doctrine/fable.md` text, or `None` when `settings.fable_mode_enabled` is off / the file is missing. Source: `github.com/rennf93/opus-fable-playbook` (MIT), YAML frontmatter stripped. Covers communication, turn-discipline, autonomy-calibration, honesty, code-discipline, delegation, precedence. Slotted right after `base.md`. When the flag is on, the Fable hooks (`docker/scripts/fable-*.sh`) are also installed at spawn on the Claude runtime (and a non-denying honesty-nudge hook on grok) — those hooks are out of scope for this chunk; this file is about the composed prompt only. ## Ponytail doctrine (0.19.0) `ponytail_doctrine_layer(prompts_path, role)` returns the vendored Ponytail build-laziness doctrine, gated on the **same** `fable_mode_enabled` flag (no separate flag — ponytail is Fable's complementary build-doctrine). Slotted into `compose_prompt` immediately after `fable_doctrine_layer`. Role-scoped: - **Developers** (`AgentRole.DEVELOPER`) → `agents/prompts/doctrine/ponytail.md`: the full ladder (YAGNI → reuse-in-this-codebase → stdlib → native-platform → installed-dep → one-line → minimal), the rules, the `ponytail:` comment convention, an Intensity table, and a 5-point RoboCo preamble that makes the ladder yield to the Architectural Conventions Standard (placement), the 80% coverage gate + QA review + self-verification, the per-team design bar, task hygiene, and reviewer feedback. The layer appends a one-line `**Operative intensity: {settings.ponytail_intensity}.**` directive that the Intensity table resolves into concrete behavior. - **Every other role** → `agents/prompts/doctrine/ponytail-ethos.md`: the ethos-only cut — the code-mechanics rungs (the ladder) and the Intensity table are removed so they can't leak into prose artifacts (task plans, review notes, docs). The 6th preamble point guards free-text field obligations (`ac_verdicts`, `findings`, `dev_notes`, `qa_notes`). No intensity directive (ethos runs a fixed restrained stance regardless of `ponytail_intensity`). Source: ponytail plugin (MIT, Copyright (c) 2026 DietrichGebert), vendored trimmed, YAML frontmatter stripped. Both doctrine files are static markdown with no DB/project dependency — the layer resolves synchronously inside `compose_prompt`, like `fable_doctrine_layer`. `None` when the flag is off or the file is missing, so a flag-off spawn is byte-for-byte unchanged. ## Intensity knob `ROBOCO_PONYTAIL_INTENSITY` (env) / `settings.ponytail_intensity` (`roboco/config.py`, `Literal["lite","full","ultra"]`, default `full`). A **string value**, not a feature flag — no `FEATURE_FLAGS` entry, no panel toggle. pydantic validates the `Literal` at Settings instantiation, so an invalid env value raises at startup. | Level | Developer behavior | |-------|--------------------| | `lite` | Build what's asked; name the lazier alternative in a `ponytail:` note, don't impose it. | | `full` | The ladder enforced. Stdlib and native first, shortest working diff, shortest explanation. Default. | | `ultra` | YAGNI extremist: deletion before addition, challenge the requirement before the rung. Still bounded by the preamble (reviewer feedback and explicit requests win). | Non-developers get no dial — `ultra` is wrong for prose artifacts, so the ethos runs a fixed restrained stance regardless. ## Files | File | Purpose | |------|---------| | `agents/prompts/doctrine/fable.md` | Fable behavioral doctrine (0.18.0). | | `agents/prompts/doctrine/ponytail.md` | Ponytail full doctrine — developers (0.19.0). | | `agents/prompts/doctrine/ponytail-ethos.md` | Ponytail ethos-only — non-developers (0.19.0). | | `roboco/agents/factories/_base.py` | `compose_prompt`, `fable_doctrine_layer`, `ponytail_doctrine_layer`. | | `roboco/config.py` | `fable_mode_enabled` (gates both), `ponytail_intensity` (string value). |