# 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-<role>.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/<role>.md`).
7. `_autogen_verbs_layer(prompts_path, role)` — per-role verb-signature table.
8. `_team_layer(prompts_path, team)` — team prompt (`teams/<team>.md`).
9. Identity file (`identities/<slug>.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). |