AGENTS.md · git:20260917.31326ac · 2026-09-17 · sha256 af54036e94eb3cf5
AGENTS.md git:20260917.31326acA
Immutable. This exact content never changes and is served at /api/v1/blob/af54036e94eb3cf5.
# AGENTS.md
## Working conventions
Never commit on the default branch: branch first, named
`type/short-description` (`fix/stack-env-reset`, `docs/reports-guide`).
Commit messages, PR titles, and issue titles all follow
[Conventional Commits](https://www.conventionalcommits.org/) —
`type(scope): lowercase imperative description`. The PR title becomes
the squash commit and the release note, and drives the version
(`feat:` → minor, `fix:`/others → patch): **never add a `!` or
`BREAKING CHANGE` marker without discussing it first** — it triggers a
major release. One logical change per PR. **Every PR references an
existing issue** (`Closes #N` in the body) — no exceptions: the issue
carries the problem and its discussion, the PR carries the change;
open the issue first when none exists. **When the implementation
deviates from what the issue specified** — a rule dropped, a
threshold changed, a scope narrowed, a design replaced — record each
amended choice as a comment on that issue, what changed and why,
before opening the PR: the issue is the decision record a later
reader opens first. The full contributor
workflow lives in [CONTRIBUTING.md](CONTRIBUTING.md); where this file
and CONTRIBUTING.md speak of the same thing, they say the same thing.
## English only
Every committed artifact is written in English, whatever language the
conversation uses: code identifiers, comments, docstrings, docs,
commit messages, PR and issue text, labels, release notes. Translate
user-provided content instead of copying it verbatim.
## No secrets, anywhere
Never write tokens, credentials, cookies, connection strings, or real
endpoints into anything committed or published: code, tests,
configuration, `.odd/` reports, issues, PR text. Refer to access
material by variable or secret name only. Placeholder values must be
obviously fake.
The same rule covers real identifiers and account/login names copied
from a live system, even when they carry no access on their own:
subscription/tenant/resource-group/workspace names and GUIDs (Azure,
AWS, GCP, ...), account or login names, every value persisted under a
remote stack's `stack_config` (a log group, a profile name — regions
excepted; the field's name in angle brackets stands in for it), and
anything else that identifies a real customer, tenant, or
environment. A bug repro or log
excerpt pasted straight from a live `odd_config_get`/CLI output is the
likeliest place for one to slip in — replace it with an obviously fake
placeholder (`Contoso`, a patterned or zeroed GUID, `example-user`)
before writing it down.
## Run what CI runs before a PR
Before opening or updating a PR, run the checks CI will run, scoped by
the paths touched (commands and pins are owned by
[CONTRIBUTING.md](CONTRIBUTING.md#building-and-testing);
`.github/workflows/` is canonical if they ever disagree):
- `src/mcp-server/`, `tests/`, or `integration-tests/` changed → CI
runs lint, unit, **and** integration unconditionally (and, on this
repository's own PRs, the `odd-status` verdict through the
oddyssey-actions - not runnable locally, not a required check): unit tests
(`uv run --project src/mcp-server pytest -c src/mcp-server/pyproject.toml tests/mcp-server -v`),
lint/format at the CI-pinned ruff
(`uvx ruff@0.16.4 check` / `format --check` on `src/mcp-server tests/mcp-server`),
and the integration tests too when Docker is available
(`bash integration-tests/mcp-server/run.sh`).
- `.apm/skills/*/scripts/`, `.apm/hooks/`, `tests/skills/` or
`tests/hooks/` changed → CI lints and tests the scripts:
`uvx ruff@0.16.4 check` / `format --check` and
`uv run --no-project --with pytest pytest -v` on the matching
`.apm/.../scripts` and `tests/...` directories; for hooks CI also
deploys them with apm and builds the marketplace
(`bash scripts/build-marketplace.sh`, then revert the generated
trees).
- `.apm/` or `apm.yml` changed → validate the package like CI does:
`uvx --from apm-cli==0.30.0 apm install --target claude && uvx --from apm-cli==0.30.0 apm audit`,
and `python3 .apm/skills/observability-cli-guides/scripts/check_stack_reference.py`
when a stack reference changed.
The install deploys the package into the working tree and edits
tracked files. Record `git status --porcelain` and `git diff` before
running; once the check completes, delete the untracked files the
command created and revert its edits to tracked files — leave
anything that existed before untouched. The deployed hook loads into
the running session: remove `.claude/settings.json` and
`.claude/hooks/` together, never the script alone.
A PR pushed red costs a review round-trip; run the checks first.
## A separate reviewer sub-agent checks the branch before its PR
No branch reaches a PR on its author's judgment alone. Before a PR is
opened, and again before every push that updates one, the coding
agent that made the change dispatches a **separate reviewer
sub-agent** — a general-purpose sub-agent with a fresh context, not
one of the package's agents, on the most capable model the host
offers, never the author re-reading its own diff — over the branch's
whole diff against the default branch, with the issue it closes as
the spec. The reviewer's findings go back to the author; each round
of fixes goes back to the **same reviewer**, which re-checks the
branch, until it returns no finding — green means the reviewer said
so, not the author. The PR body records the outcome: what the review
found and what changed for it. Two things the review never replaces:
the CI checks above, which run first, and the **explicit go** of the
person directing the work — the push, the PR and the merge each wait
for it, and a go with a question attached is not a go. On the path
of `/odd-observe`, `/odd-verify` or `/odd-status`, the reviewer is
briefed with the bound-review rule of "Plugin harnessing" below:
blocking findings only, every proposed fix costed and re-measured.
## Tests follow the MCP server
Coverage moves with the code, in the same change — running the
existing suites (above) is not enough:
- Any behavior change in `src/mcp-server/` updates `tests/mcp-server/`
— test-first.
- Any change to a tool's **wire surface** — result shape, arguments,
the exposed tool set, stack lifecycle behavior — also adapts or
extends `integration-tests/mcp-server/`: it is the only layer that
proves the contract through a real MCP client against real Docker.
- A server change shipping without a matching test change states why
in the PR (legitimate cases exist: a pure refactor, error-message
wording — a wire-surface change is never one).
- A script bundled with a skill (`.apm/skills/*/scripts/`) or a hook
(`.apm/hooks/scripts/`) follows the same rule: its tests live in
`tests/skills/` or `tests/hooks/`, test-first, and CI runs them
(see above).
## The MCP server's three hard constraints
Owned by the
[instrumentation spec](docs/superpowers/specs/2026-08-22-mcp-otel-instrumentation-design.md)
§2 "Hard constraints" (failure semantics detailed in its §6) — the
spec wins if this summary ever drifts:
- **stdout is the JSON-RPC wire.** Nothing may ever print to stdout —
not a log line, not a stray byte.
- **Telemetry never breaks a tool.** Export failure while the stack is
down is the normal state, never an error surfaced to the client.
- **Tool registration must not change.** The exposed tool set is
frozen — the unit tests assert the exact tools.
## The `.odd/` memory is append-only
The committed reports under `.odd/` are the ODD loop's memory: never
modify or delete a stored report — a new run writes a new file, and
the diff lives in the new report. An `.odd/`-only change never counts
as a fix. The formats and the rules a reviewer can enforce are in
[docs/guide/reports.md](docs/guide/reports.md); on a host that runs
the package's hooks, a hook refuses such an edit before it runs, and a
hook checks a report's filename and frontmatter against them after the
file is written, before it is persisted.
The rule governs the report stores — `.odd/observe-run-reports/`,
`.odd/otel-instrumentation-reports/`, `.odd/decisions.md`, and
`.odd/entry-classifications.md`. It does
not reach `.odd/benchmarks/` or `.odd/observability-stacks/`: a
benchmark or a custom stack is living source, not a run record,
and its `odd-memory` reference updates it in place through reviewed
diffs like any other committed code.
## Plugin harnessing — non-negotiable
These rules decide the package's performance, and they are not a style
preference. A run's cost is what the model has to **compose** before it
can do the work; prose that describes a procedure makes every model
re-derive it, differently, on every run. The package's job is to leave
the run free where judgment is needed and to leave it nothing to invent
where it is not.
**Determinism ships as a command, never as prose.** When the inputs
already fix the output — service names and a window fix the queries, a
manifest fixes the k6 command, a repository fixes what a preflight reads
off the machine — that step is a script in the owning skill's
`scripts/`, and the contract tells the run to execute it. Prose that
spells out such a procedure is a defect: it costs turns before anything
runs, and two runs of it are never the same command.
**Never make an agent author a shell script for a step the package
ships one for**, and say so in the contract. When a run writes its own
wrapper anyway, the script is missing a real shape of the work — a
scenario outlasting a tool call needs a detached mode, not a wrapper —
so fix the script rather than the instruction.
**State a script's whole flag surface where it is invoked**, with a
copy-pasteable invocation for the normal case, and say that it is the
whole surface. A run that cannot see the parameters spends turns on
`--help` or reads the file; both are pure loss, and reading the file
invites it to reimplement what it just read.
**What a check will refuse is said where the draft is written.** A
script that prints a skeleton for the run to fill prints, after it,
the shapes its checker enforces — one line — so the run reads neither
the reference nor the script's source to learn them. Measured on one
mission (2026-09-11): with the shapes stated in the reference alone,
both runs opened the script's code by ranges right after the skeleton;
with the line printed, one of two.
**An answer already obtained travels; it is never re-derived.** What a
preflight read is handed to whatever runs next, in the handoff, and the
receiving contract says to read it there. Re-listing a repository, its
containers or its ports because the answer stayed upstream is a measured
cost, not a hypothetical one.
**Read only what the current step uses.** A section needed at report
time is read at report time. Setup that loads the whole method pays for
it on every turn in between.
**An expensive operation is a separate decision with a stated reason,
never the side effect of a general rule.** A recreation, a reset, a
rebuild belongs behind an explicit "only when" with the case named in
the run record — a rule phrased so that its expensive branch is always
taken is the same as having no rule.
**Scripts stay generic through the contract, not through one binary.**
What is backend-agnostic (the machine preflight) is shared; what depends
on a query surface (a service probe) is per-backend, and the contract
reads "when the backend's reference ships one, run it instead of
composing the queries". Never fold a stack's specifics into a shared
script, and never leave a run to compose what a stack could have
shipped.
**A contract names only what it owns.** The agents and the prompts are
backend-agnostic: they route to "the backend's reference" and to "the
scripts it ships", and never name a backend's reference file, its CLI,
one of its scripts, or a fact about it (which series it derives, which
signal it lacks, what its profiles carry) — a backend's specifics live
in that backend's reference and nowhere else. Naming a backend is
routing, not specifics, exactly where routing needs the name: the list
of the stacks the package supports, a configuration value and its
example (`stack: grafana`), and the configuration prompt whose job is
switching between them. The same holds between skills: each stays
inside its responsibility (the scenario skill knows nothing of a
backend, the memory skill describes a shipped query script generically,
the local-stack skill points at the backend reference it is built on
instead of restating it), and a skill's scripts are enumerated by that
skill's own reference, never by another skill's file. A generic contract
that names a backend's script has fused two files that change at
different times, and the next backend added would have to edit a file
that should not know it exists.
**A reference is a list, never an essay** — a stack's, a report
kind's, a benchmark's alike. It states each shipped invocation with its
whole flag surface, one line per fact a script cannot absorb (a trap, a
verified limit, a rule the output cannot say, a fix that is the
user's), and nothing that narrates what the script prints — the output
says that itself, once, at run time. A paragraph explaining a script is
prose the run reads on every turn and a second contract the script can
drift from; a reference grows by a bullet, never by a paragraph.
**An agent or a prompt routes to a skill and restates none of its
mechanics.** An invocation, a flag surface, a wait, a file layout
belong to the skill that ships them and are stated there, once — "where
it is invoked", above, is that contract, the one that owns the
invocation; an agent that routes to it is not an invocation site. The
agent and the prompt name the skill and the section. Measured on five
preflights of one model (2026-09-09): copying two invocations into the
prompt and the agent removed two or three `--help` calls, about twenty
seconds, inside the run-to-run noise, at the price of two more contracts
to keep true — not worth it. The lever that removes them without a copy
is a script composing the lines from the preflight's values, when the
number justifies it.
**Contracts that agree are run; contracts that disagree are read.**
Two files stating the same rule differently do not average out — the
run opens both to arbitrate, and pays for the reading and for the
decision. Measured on one mission: while a record example still showed
a command composed by hand and a section still called an expensive
operation the default, the runs opened four skill contracts to settle
it; once the three texts said the same thing, none of those files was
opened at all and the reads per run halved. **An example is a
statement**, and the most copied one: a worked example showing the old
way overrides the rule forbidding it, however plainly that rule is
written. When you change a rule, change every place that states it —
its own file, the contract that routes to it, the router that points
there, and every example that shows it in use.
**Judge these by what the package controls.** Wall-clock time is
dominated by per-turn model latency, which the package does not own;
what it owns is the number of commands and the work the model must
compose. Move those, and state the before and after — a harnessing
change that cannot name the commands it removed is not evidence of
anything.
**A review of a change on that path is bound by the same rule.** The
instructions given to a reviewer of a branch touching the path of
`/odd-observe`, `/odd-verify` or `/odd-status` state it in so many
words: the branch's harnessing gain was measured and is the thing
under protection; a finding is raised only when it is genuinely
blocking (a wrong result, a crash, a lost or corrupted record, a
security defect), never for a robustness or completeness improvement
that adds a rule, a flag, a branch or a sentence to what a run reads
or runs; and every fix a reviewer does propose on that path names its
cost in commands or contract lines and is re-measured before it is
kept. A review that comes back with a list of "could also" items on a
measured branch has not read this file: two rounds of such fixes
(2026-09-12) put a branch's drive side back at main's level and its
observe side above it, and were reverted whole.
**A change on the path of `/odd-observe`, `/odd-verify` or
`/odd-status` is measured before its PR** — directly (the prompt, the
agent it dispatches, a skill or a script the run executes) or
indirectly (a reference the run reads, a script's output shape, a hook
on the path). The measurement is the `test-plugin-harnessing` skill's
(`.claude/skills/test-plugin-harnessing/`: its `SKILL.md` is the
procedure, its two scripts drive and analyse a run), on the CLI the
change is about — opencode, claude or copilot, the three the package
is benchmarked on — against main measured just before the work with
the same mission, two samples per side; the PR states the four axes —
turns, tokens, cost, wall clock — per phase. The bar depends on why
the change was made: a change made *for* the harness owes a
substantial gain (the skill's judging rules say what counts); a change
on the path made for another reason owes no degradation. A change that
cannot show its bar is reworked, not argued. A host that does not load
that skill natively reads its `SKILL.md` as a file and runs its scripts
the same way. `launch-llms-benchmark`
(`.claude/commands/`) is the kit's other half, the one that grades a
model's findings; it is never a substitute for this measurement.
## Marketplace is generated — never edit it by hand
`marketplace/`, `.claude-plugin/`, and `.agents/plugins/` are build
artifacts: the release workflow regenerates them
from `.apm/` via `scripts/build-marketplace.sh`. Author every change in
`.apm/` (agents, skills, prompts) and `apm.yml` only, and leave the
generated trees alone.
## Guides and the README are user documentation, not specs
`README.md` and `docs/guide/*.md` explain how and why to use the
package — never how it works inside. The contracts live in `.apm/`
(prompts, agents, skills) and `docs/superpowers/specs/`; a guide links
to them and never duplicates their rules. Keep a guide to what a user
needs to run the loop: the invocation, what they must supply, what
comes out and where, what the prompt will ask or refuse — one sentence
each — and a pointer to the contract for the rest. The "keep in sync"
sections below mean the guide's statements stay true and its examples
keep matching the contracts, not that every contract clause earns a
sentence in the guide. A guide paragraph that restates an internal
mechanism — a build order, a matching rule, a validation step, a
commit discipline — is a review finding, like a missing test.
## Keep the prompts guide in sync
`docs/guide/prompts.md` catalogs the packaged prompts with example
invocations. Update it in the same change whenever a prompt is added or
removed, or a prompt's behavior/arguments change — examples and
field-mapping annotations must keep matching the `.apm/prompts/`
contracts. Update the `README.md` too: the How to steps and the
Miscellaneous prompts subsection both reference prompts — and the
`Prompts` table of `docs/guide/plugin.md` lists every prompt.
The editorial rule above applies: keep the guide true, never
exhaustive.
## Keep the plugin guide in sync
`docs/guide/plugin.md` maps who invokes what across prompts, agents,
skills, hooks and MCP tools. Update it in the same change whenever a
prompt, agent, skill, hook, or MCP tool is added or removed, or a
dependency between them changes — every edge must match an actual
invocation in the `.apm/` sources (no aspirational edges). Its
`Prompts`, `Agents`, `Skills` and `Hooks` tables and its `The MCP
server` section list every component with its role and its edges —
they are the package's component catalog, the README carries none: a
tool's parameters, the container and its ports, and what the server
exports about itself all live there.
The editorial rule above applies: keep the guide true, never
exhaustive.
## Keep the reports guide in sync
`docs/guide/reports.md` documents the `.odd/` report formats:
filename conventions, frontmatter fields and values, and body
structure for both report kinds. Update it in the same change whenever
the file contracts of `odd-memory` (its `SKILL.md` or its two report
references), or the report sections of the
`observe-run` or `otel-instrumentation-expert` agents, change — the
guide documents what a reader needs of those contracts and must never
lag them.
The editorial rule above applies: keep the guide true, never
exhaustive.
## Keep the backends guide in sync
`docs/guide/backends.md` documents, per backend, the CLI and how to
install it, the resource that must already exist on that backend
before it has anything to query ("nothing" stated explicitly where
that's true), the example switch prompt, and the `stack_config` values
(if any) the switch persists. Update it in the same change whenever a
backend's contract to call it changes — a new parameter becomes
required to query it, the CLI or its install command changes, the
resource prerequisite changes — or a backend is added to or removed
from `STACKS` — the guide must keep matching
`.apm/skills/observability-cli-guides/references/*.md`, the one place a
built-in stack's knowledge lives (its query surface, its `## Configuration
display`, its `## What to persist`); `references/builtin-stacks.md`
there must list exactly the `STACKS` values (a unit test asserts it).
This applies to adding a new stack to `STACKS` and to modifying an
existing one — a new or changed `stack_config` field, a changed
targeting requirement, a changed CLI command or flag in any section of
the stack's `observability-cli-guides` reference. **A built-in stack is
authored and adapted the way a custom one is**: through
`/odd-instrument-stack` and the `stack-instrumentation-expert` agent —
the one that carries the harnessing rules for a stack and knows how to
build its query surface as scripts, verified live — as a custom stack
under `.odd/observability-stacks/<working name>/` first, under a
working name that is not the `STACKS` value (the checker refuses a
built-in's name), then migrated by hand into the
`observability-cli-guides` reference and its `scripts/`, with the tests
a built-in stack must carry (fake CLIs replaying captured, masked
fixtures under `tests/skills/`). What the agent owns is authoring and
live-verifying the query surface, never the built-in file; a built-in
reference or script written by hand from scratch, without that pass,
skips the harnessing the agent enforces.
**Non-negotiable**: every such change
must be verified live, through the backend's own CLI, against a real
account carrying real data — not from documentation, memory, or a
mocked response. Verification means actually querying every signal
that account has data for (metrics, logs, traces, profiles — whichever
apply to that backend) and confirming what the CLI really returns. A
change landed without this is unverified, whatever the diff claims.
The editorial rule above applies: keep the guide true, never
exhaustive.
## Keep the benchmarks guide in sync
`docs/guide/benchmarks.md` documents the benchmark lifecycle —
authoring today, running and verifying once those land. Update it in
the same change whenever `/odd-instrument-bench`, `k6-benchmark-expert`
or `odd-memory`'s `benchmark` reference's contract changes, and
expand its Run/Verify sections in the same change that implements
`/odd-observe`'s `benchmark:` field or `/odd-verify`'s benchmark replay
— the guide must never describe a contract that doesn't exist yet, or
lag one that does.
The editorial rule above applies: keep the guide true, never
exhaustive.
## Keep the custom backends guide in sync
`docs/guide/custom-backends.md` documents how a user creates, edits,
shares and fixes a custom stack from what the runs report. Update it in
the same change whenever `/odd-instrument-stack`'s create, complete,
link or fix-from-report shapes, `odd-memory`'s `observability-stack`
reference, the contract check script's invocation, or the observation
report's stack-friction section change — the guide must never
describe a contract that doesn't exist yet, or lag one that does. The README's "Every backend" section names the guide
and the built-in list; keep both true.
The editorial rule above applies: keep the guide true, never
exhaustive.
## Title and label every issue
GitHub issue titles follow the Conventional Commits form
`type(scope): summary`, exactly like commit messages and PR titles —
e.g. `feat(prompts): ...`, `fix(mcp): ...`, `fix(skill): ...`,
`feat(actions): ...`, `fix(ci): ...`, `docs(readme): ...`,
`docs(guide): ...`, `docs(agents): ...`. Pick the type and scope from
the existing issue titles.
When creating a GitHub issue, always set: a type label (`bug`,
`enhancement`, `documentation`), a `priority: low|medium|high` label,
and — when the issue concerns a specific observability stack — that
stack's label (`datadog`, `local`, ...; create the label if it does
not exist yet). Add a topic label too when the issue concerns one
of the package's cross-cutting themes — `k6` (load-testing
benchmarks), `otel` (instrumentation), `perf` (mission wall-clock
time and query cost), `harness` (moving rules from prompt prose to
hooks, seams and contracts), `github` (the GitHub integration),
`cicd` (the CI/CD workflows), ... — the repository's labels are the
authority on which themes exist: pick one of them rather than
inventing a theme. Add `community` too when the issue is about
discoverability across external directories, marketplaces, or
community lists (submitting or updating a listing, tracking its
review).
When closing an issue as not planned, add the `wontfix` label and
close with a comment stating the rationale — the decision must be
readable from the issue itself.