---
type: Reference
title: "AGENTS.md — PMOS Portable Kit (file mode)"
timestamp: 2026-07-22
---

# AGENTS.md — PMOS Portable Kit (file mode)

> This file must be read at optimal altitude — concrete heuristics that guide behavior, not brittle if-else rules engines, and not vague mandates that assume shared context.

You are working in a product repo that vendors the **PMOS portable kit** (D54). PMOS here is a **control plane** for how work is specced, evaluated, and accepted: you build the product, PMOS disciplines the loop.

(PMOS itself is a control plane **and a factory** since D65 — but the factory half is not in this kit. What you have vendored is the governing half. If a future kit ships execution, it will arrive propose-only: opening pull requests, never holding push or merge rights.)

## Writing markdown — one paragraph, one line

**Do not hard-wrap prose.** A paragraph is one line, a list item is one line, however long. Leave a blank line above a list, a table, a fence and a heading.

A newline inside a paragraph is invisible in markdown — every renderer joins the lines — and it is a **line break** in Jira wiki markup, in a Google Doc, and in the markdown→ADF converters an agent uses to publish. A wrapped spec arrives at your tracker as ragged half-lines, and nothing on the far side can undo it: an accidental wrap and a deliberate break are the same byte in the source.

```bash
node pmos/scripts/md-newlines.js <path>          # what is wrong, and where
node pmos/scripts/md-newlines.js --fix <path>    # repair it — paragraphs only, never code or tables
```

It refuses frontmatter, fenced and indented code, table rows, headings, deliberate two-space hard breaks and setext underlines, and it is idempotent: running it twice changes nothing the second time.

## The containment rule (non-negotiable)

**This deployment is file-mode. Make NO network call to any PMOS backend — no MCP connection, no `get_skill`, no telemetry, nothing.** Every piece of PMOS state lives in this repo, under `pmos/`. This boundary exists because this product's data must not leave its own remote. If you find yourself wanting an MCP tool, use the state adapter below instead.

## The state adapter — every PMOS tool is a file convention

| Hosted tool (as skills name them) | File-mode equivalent |
|---|---|
| `create_initiative` | write `pmos/state/initiatives/<id>.md` (frontmatter: id, title, parent_kr, stage, status, **type** — the D56 lane, default `feature` — plus `intent` for the chore lane, prd_path, rubric_path, created; optionally `outcome_metric` + `outcome_horizon` — the D57 declaration for post-ship success criteria) |
| `save_spec` | the PRD file itself at `pmos/planning/prd/<id>-prd.md` (set `prd_path` on the initiative file) |
| `get_spec` / `get_planning_artifact` | read `pmos/planning/…` |
| `get_skill` | read `pmos/skills/<name>.skill` |
| `get_okf_concepts` | read `pmos/okf/…` (index-first, then the concept body — progressive disclosure) |
| `get_eval_rubric` | read `pmos/planning/evals/<id>-eval-rubric.md` |
| `log_agent_run` | append one JSON line to `pmos/state/runs.jsonl` (`{run_id, initiative_id, pre_run_contract, self_eval, human_corrections, date, model}`) — record `model` = the model that produced the run so metrics stay honest across model switches |
| `save_eval` | append one JSON line to `pmos/state/evals.jsonl` (`{run_id, initiative_id, rubric_path, score, verdict, dimensions, date, evaluator_model}`) — record `evaluator_model` = the model that rendered the verdict; κ is calibrated **per evaluator model** (a model swap does not inherit a prior κ) |
| `record_acceptance` | append one JSON line to `pmos/state/acceptances.jsonl` (`{run_id, initiative_id, accepted, pm_label, note, date}`) — **written only on the PM's explicit word** |
| `record_outcome_check` | append one JSON line to `pmos/state/outcome_checks.jsonl` (`{initiative_id, disposition, evidence, date}`) — the D57 post-acceptance outcome verdict (`validated | refuted | inconclusive`; evidence required for the first two). **PM's word only — never auto-dispose from metrics**; a re-judgment is a new line |
| `save_okf_doc` | write the doc under `pmos/okf/product/…` marked as a DRAFT; the PM approves it via PR review (the file-mode form of draft→approved) |
| `register_product_repo` | not applicable — this repo IS the product; skip the step |
| `log_friction` / `log_discovery` | append to `pmos/state/discoveries.md` (ISO date, `#friction` tag where applicable) |
| `get_agent_runs` / `get_friction` | read those same files (your episodic memory — consult before contracting) |

State files are **append-only**: never rewrite or delete an existing line or initiative record; correct by appending. When a vendored skill names a hosted tool, apply this table — the skill bodies are single-sourced from the PMOS kernel and are not forked for file mode.

## Work-type lanes (D56)

Every initiative carries a `type` — the lane picks what the gate requires **before** work; the kernel (anchor — a KR **or** `health:<id>` from `planning/okrs/health-budgets.md` (D59) — eval-before-work in lane-appropriate form, human Acceptance, append-only state) holds in every lane. Unknown/missing type = `feature`, the strictest lane. The enum is closed — never invent a lane; a work shape with no lane escalates to the PM.

| type | spec artifact the gate requires | eval form |
|---|---|---|
| `feature` (default) | PRD + eval rubric before build | rubric + evaluator skill (unchanged) |
| `discovery` | spike brief at `prd_path` (question + timebox — `skills/discovery.skill`, template in `okf/core/templates/`) | disposition: learned / promoted / discarded; rubric optional |
| `chore` | a one-paragraph `intent:` in the initiative file | repo CI + PM spot-check at merge |
| `incident` | incident record at `prd_path`, **post-hoc within 48h** (template in `okf/core/templates/`) — the one sanctioned inversion of eval-first | retro in the record; follow-ups become their own initiatives |
| `content` | content brief at `prd_path` (audience, message, claims + sources — `skills/copywriting.skill`, template in `okf/core/templates/`) | copy rubric recommended, not required (D58); PM judges at the gate otherwise |
| `prototype` | quick prototype brief at `prd_path` (the judgement exercised + the specimen format — `skills/prototype.skill`, template in `okf/core/templates/`), **before** the PRD | **NO rubric, BY DESIGN** — grilled as dialogue, never scored (D60(d)); closes on a PM disposition: promote / iterate / kill |

> **Known gap in this kit build:** `scripts/gate-initiative.sh` does not yet parameterise the `prototype` lane — a prototype initiative falls through to the `feature` branch and is asked for a `rubric_path` it must not have. Until that is fixed, set the lane's `type: prototype` in the initiative file as normal and expect to carry the gate failure knowingly, or run the prototype before opening the record. Tracked upstream; not a licence to invent a rubric for leg A.

**Routing rule (D60, as amended 2026-07-24) — an idea the PM cannot judge from a DESCRIPTION goes to the `prototype` lane BEFORE anyone writes a PRD.** The entry rule is the **specimen test**: *can the PM judge this from a description, or do they need an instance of it in front of them?* It is **not gated on the work having a user interface** — *does this skill's output actually teach?* and *does this API feel right to call?* qualify exactly as an unseen screen does. The PM briefs; an agent produces **2–3 divergent specimens in whatever format fits** (lookable UI, a worked sample output, call/response transcripts, a schema sketch); a craft-grade grilling sharpens them as **dialogue, never a score**; a promote-disposition derives the feature-lane PRD + rubric from what was validated. The test must discriminate **both ways** — most work (a tool, a migration, a CI gate) *is* description-judgeable and routes straight to its PRD untaxed. Contrast `discovery`, which holds a **question** and delivers a **learning**; this lane holds a **candidate** and delivers a **validated direction**. Prototypes live in `prototype/` and never ship.

`no-initiative` label = anchor-free trivia ONLY (a typo, a broken link). A chore, spike, incident, or piece of content is real work and takes its lane — that is what keeps the record honest.

## Agent run protocol (every run, in order)

1. Read this file.
2. Read the relevant `pmos/skills/*.skill` for the task type (L1 index first; body when relevant). No skill fits → escalate to the PM; do not improvise a procedure.
3. Retrieve context: `pmos/okf/` for concepts, `pmos/okf/product/` for this product's knowledge, `pmos/state/runs.jsonl` + `discoveries.md` for what prior runs tried (episodic memory).
   - **Existing surface → the PM needs the understanding too.** That retrieval warms *you*. When the work touches something that already exists — the test is *would specifying this honestly require reading code that already exists?* — run `skills/explain-surface.skill` on that region **before** the brief/PRD is authored, in a fresh context and **not** by the agent that will write it. Standard for existing surfaces, skipped for greenfield. Without it, the agent acquires the understanding and the **PM approves a spec built on facts they never saw**.
4. Anchor: the initiative file must exist at stage `intake`/`spec` with its **lane's** spec/eval artifacts authored **before** build (eval-first, D13, generalized per lane — see the lane table above; `feature` = PRD + rubric; the incident lane alone records post-hoc). Contract per lane: `feature` = the rubric's sprint-contract section with the PM's explicit `PM_approved: true`; `discovery` = the PM-approved spike brief (question + timebox); `chore` = the stated intent; `content` = the PM-approved content brief; `prototype` = the PM-approved quick brief (the judgement exercised + the specimen format) — and **no rubric**, which is the decision, not an omission. No work before the lane's approval. Feature-lane `test_method`s SHOULD be executable checks written before build (D58) — wire `templates/product-ci-starter.yml` so tier 1 is never silently empty.
5. Branch `initiative/<id>`; execute at control altitude; one commit per artifact.
6. Self-evaluate against the contract — self-eval is unreliable; it is an input to the gates, never a verdict.
7. Close the gates: **Quality** = this repo's CI plus the kit gate (`pmos/scripts/gate-initiative.sh`
   + `pmos/planning/evals/check_rubrics.py`); **Review** = score against the rubric with the evaluator skill (adversarial, outcome-only, default-to-refute) and append to `evals.jsonl`; **Acceptance** = the human PM merges and disposes — final and non-delegable. Never merge your own PR.
8. Log the run (`runs.jsonl`) and any friction (`discoveries.md`, `#friction`).

## Human builders & multi-repo initiatives (D58)

- **A human building instead of an agent?** Same loop, plain terms: [AGENTS-HUMAN.md](AGENTS-HUMAN.md). State is written with `scripts/pmos-log.sh` (validated, append-only) — never hand-edited. Mixed human+agent teams write the same state through the same conventions.
- **An initiative spanning multiple repos?** ONE repo is its **home** — the initiative record, brief/PRD, rubric, and state live there. Every sibling repo's PR anchors by declaring `Initiative: <id>` in the PR body (the gate's body-key path); records land in the home state layer. There is no cross-repo registry — the body key IS the join. **Sibling repos either do not install the pmos gate, or mirror the initiative file locally** — a vendored sibling gate fails closed on a missing local record by design (S5 Review Gate finding).

## Decomposing an initiative into tickets (file mode has no board)

When an initiative is bigger than one run, break it into **tickets** — the plain-file equivalent of the hosted product's SDLC board. One file per ticket in `pmos/state/tickets/NNN-slug.md`, numbered in **dependency order**, from [templates/ticket-template.md](templates/ticket-template.md):

- Frontmatter: `id` (the NNN), `title`, `status` (`todo | doing | done`), and `blocked_by` — an explicit list of the ticket ids that must be `done` first. Blocking edges are **text**, not a hidden graph.
- **Sweep the state you derive from.** `node pmos/scripts/workflow-sweep.js` audits the records and the three ledgers everything else reads. Run it before a review and after any bulk edit. It reports **defects** (state that contradicts reality — a torn ledger line, a row naming no initiative, a duplicate `run_id`, a record pointing at a file nobody wrote) separately from **owed** work (a merged initiative with no run logged, an open Acceptance Gate). `--fix` repairs only the mechanical defects and writes no `.jsonl` and no judgement — closing your own loops is your job and the Acceptance Gate is yours alone (D12).
- **Work the frontier.** `pmos/scripts/frontier.sh` lists exactly the tickets that are not done and whose every blocker is done — the workable set. Start only from there. If it reports the frontier is *blocked*, there is a cycle or an undone blocker to resolve before more work is startable.
- Each ticket names **one deliverable**, behaviorally (interfaces/types/contracts, never file paths or line numbers — they stale while the ticket waits). Tickets decompose an initiative; they do not replace it — the initiative record, PRD, rubric, and the three gates are unchanged.

## New agent paths — the lethal-trifecta audit

Before creating any new agent path or widening a tool grant, verify it does **not** hold all three of: **private data access + untrusted-content exposure + external communication**. An agent holding all three can be tricked by content it reads into exfiltrating what it knows — remove a leg (preferably external communication) structurally; prompt-level defenses alone are bypassed by adaptive attacks. If a path genuinely needs all three, its PRD/brief must name the compensating gate; silence is not a pass. The evaluator is the standing example: it reads adversarial diffs, so it stays read-only with its verdict append as the only write.

## Escalation (stop and ask the PM)

An architectural choice with no recorded decision; a wrong or unsatisfiable contract; scope growth; any destructive/irreversible action; anything that would send product data outside this repo's remote. Confirm-first regardless of framing.

## Evaluator leniency warning

The evaluator defaults to generous scoring. Treat the Review Gate as **uncalibrated** in this deployment until this repo has accumulated its own PM-vs-evaluator record (append overrides to `pmos/planning/evals/eval-calibration.md`; the D40 classifier method applies locally). `pmos/scripts/metrics.py` computes the north star (human corrections per accepted run), pass rates, and the Goodhart tripwire from `pmos/state/`.

**Model-awareness (any provider).** PMOS is provider/model-agnostic — any capable coding agent can run this loop — but calibration is a property of the **evaluator model**, not of "the Review Gate" abstractly. Record the model on every run/eval (the `model` / `evaluator_model` fields above); a model swap **forks** the calibration record — you do not inherit a κ another model earned, and `metrics.py` warns when labeled evals pool more than one evaluator model. The high-judgment roles (the adversarial evaluator, `explain-run`) assume a **frontier-tier reasoner**: a weaker model degrades *silently* to leniency and grep-clean while its output still looks well-formed — the per-model κ is what makes that degradation visible.

## PM protocol (weekly)

Scan `discoveries.md`; a friction theme seen twice with no resolving decision must be promoted to a decision. Read run records since last review; log overrides to `eval-calibration.md`. Run `pmos/scripts/metrics.py` and read the red first. **Scan the lane mix (D56):** count initiatives per `type` — a chore share climbing review over review, discovery spikes that never close their disposition, or `no-initiative` PRs that should have been chores are drift signals; a lane misused twice is promoted to a decision like any recurring friction. **Scan the anchor mix (D59):** feature-vs-health share of the period's initiatives; a budget over its cap or health-shaped work with a KR anchor is a drift signal; quarterly, review the caps. **Scan outcome checks (D57):** metrics.py lists due outcomes (accepted, past horizon, undisposed) — dispose each (`validated | refuted | inconclusive`, evidence for the first two) or consciously defer; a check deferred twice, or a horizon moved twice, is promoted like recurring friction. An accepted initiative with a declared outcome is NOT finished until disposed. **Read the run's explainer where one exists** — the `explain-run` skill writes an independently-authored literate explainer + quiz to `pmos/planning/evals/explainers/<run_id>-explainer.md`; start there instead of the raw transcript, and for a high-risk run pass its quiz as a comprehension self-check before you accept. Recommended discipline, not an enforced gate — accepting runs you cannot explain is the PM's own watermelon.

**Anti-atrophy forcing functions (complacency is structural, not a willpower problem):**

- **Name one hand-verified fact at every Acceptance** — the `record_acceptance` note states something *you* checked directly (ran it, read the hunk, reproduced it), not the evaluator's verdict restated.
- **Watch the acceptance-friction meta-watermelon** — a run of zero-comment, fast acceptances is itself a flag; mandated oversight decays into rubber-stamping that legitimizes the system.
- **Guard the four satisficing heuristics**: plan-as-proxy, tests-as-guarantee (CI-green ≠ correct — the Quality Gate is not the Acceptance Gate), eyeballing-suffices, deference-in-unfamiliar-domains.
- **Adaptive autonomy** — drop an initiative to every-diff review on a failed eval, a reverted merge, a gate-touching change, or a subsystem you haven't personally touched in ~3 months.
- **Monthly**, do one hands-on rep — personally diagnose one defect or re-derive one diff from its spec before reading the agent's version (diagnosis-shaped; comprehension decays faster than typing). **Quarterly**, ask *"what would I no longer know how to fix by hand?"* — supervision cost rises as runs improve.

## Kit maintenance

`pmos/KIT-VERSION` records the kernel source. Update the kernel with `pmos/scripts/kit-update.sh <path-to-pmos-checkout>` — it re-vendors ONLY `skills/`, `okf/core/`, `scripts/`, `AGENTS.md`, `README.md` and never touches `state/`, `planning/`, or `okf/product/`. Review the update as a normal PR.
