# Project Rules

House rules a capable model wouldn't reach on its own. No restating of
ordinary good practice, and no architecture — that lives in
`docs/STATUS.md`, which gets reviewed. Rules naming modules rot silently;
behavioural rules don't. For trivial tasks, use judgment.

## Stance

Be concise, direct, and candid. Challenge weak assumptions. Keep verified
facts distinct from inference and from guesses, and say which you are
offering — "I checked and it says Y" and "it probably does Y" must not
read alike.

Ask only when a decision is materially ambiguous, risky, or needs
approval; otherwise choose, say so, continue. Exception: when a request
has several real readings that lead to different work, present them
instead of silently picking one.

## No keyword matching

Never use keyword or string matching for classification, routing,
affordance detection, or intent understanding — use an LLM call with a
natural-language description, embedding similarity, or another
meaning-based method. Keyword lists always miss edge cases. Only with
explicit permission.

## Write for the reader, not for yourself

Scratch files and working notes can use any shorthand. Everything else —
documents, prompts, code comments, commit messages, anything another person or
model will read — is addressed to an *other*. Use plain language, as short as
it can be without becoming ambiguous, in the register of a well-drafted
procedure.

**The test: if the reader has to reconstruct what you meant, the sentence
failed.** Three ways that happens, all found in this repo:

- **A metaphor standing in for a rule.** "Load-bearing" meant *mandatory*; say
  mandatory.
- **A term defined precisely, then reused loosely.** `delta` was a verdict, a
  form field, and a synonym for findings — in one document, within twenty
  lines.
- **Compression that reads as precision.** "One assertion" sounds exact and has
  no fixed size. Two models read it at different scales and neither misread it.

This is not a style preference. An instruction another model executes is a
specification, and a specification that can be read two ways will be.

Going forward, not backward: applies to new and edited text. Do not sweep the
repo for it, and leave argumentative prose written for a human reader alone.

## No mannered prose

Mannered prose substitutes metaphor and flourish for direct statement. Instead
of "a parameter worth varying," the mannered writer produces "a dial worth
turning." Instead of "this point still matters," they write "this point earns
its keep." The phrases exist to display the writer, not to convey the idea,
and readers can tell. That is why mannered prose irritates: it makes the
reader work harder so the writer can perform. It is also imprecise: metaphors
drag in connotations the writer did not choose and cannot control.

The fix is to say what you mean. When a literal phrase is available, use it.
This applies to everything written here: code comments, commit messages,
method documents, reports, essays, and replies in chat.

## Method documents are read whole

`METHOD.md`, `REVIEW.md`, `DELIVERY.md` — any document a workflow LLM reads as
its method — are edited only after reading the whole file in one piece. No
grep, no `sed -n`, no jumping to a line number, whether to compose a change or
to propose wording for one. Read it whole again before calling the edit done.

The non-obvious half: **grep finds strings, not ideas.** One requirement sat in
six places in six wordings; removing it from two left four, and the search
meant to catch them matched one. A fragment also hides register — a
justification clause, a term already defined two sections up, a second phrasing
of a rule that already exists.

No exception for a one-line change. Seven defects went into these three files
on 2026-08-31 and every one looked trivial while it was being made. The
workflow linter passed after all seven; it checks form, and says so.

## Sampling settings are code, not convention

`top_p` is 0.95 globally; temperature is per-model. Both live in
`src/chat/model_params.py` and resolve automatically — **never pass a
temperature literal to an LLM call**, and never treat a scenario default,
a `--help` string or a comment in a config file as evidence of what a run
will actually use.

Adding a model needs the publisher's recommended *agentic* temperature,
found by searching, plus explicit confirmation. If no recommendation
exists, say so and ask — don't pick. An unconfigured model raises rather
than inheriting, and that is the feature.

Twelve benchmark runs were discarded on 2026-08-24 because a documented
setting sat in a file the runner never read.

## Reuse over re-implementation

Before writing logic for a common task (fence stripping, JSON repair,
retry/backoff, path resolution, env reading), look for an existing
utility. If several sites need it and none exists, add one helper in
`utils/`. When a duplicate has crept in, consolidate rather than add a
third.

## KISS — and let the user carry a little

Review every plan for unnecessary complexity before proposing it. The
non-obvious half: prefer a significantly simpler design even when it
shifts a *minor* burden onto the user. The tell is machinery whose whole
purpose is to spare them one small explicit action — inference,
heuristics, disambiguation, and the guards those need. Name the trade
and let them choose; ask when unsure which way it falls.

Distinct from feature creep (that's about scope — do less). This is
about where the work sits when the feature is agreed.

## Surgical changes

Touch only what the request requires; match surrounding style even where
you'd choose differently. Clean up orphans *your* edit created; leave
pre-existing dead code alone and mention it instead. Nothing speculative —
no unasked features, no abstraction for one call site, no error handling
for impossible states.

## Tests must protect behaviour

Before adding or changing a test, answer three questions. If one has no
answer, do not add the test.

1. What behaviour or contract does it protect, stated as something a
   caller or user can observe?
2. What credible change to the code would make it fail?
3. Why does no existing test already fail on that change?

A regression test must be shown to fail on the code before the fix, for
the reason the bug gives. A test that has only ever passed proves the
mock, not the fix.

Do not add a test that:
- asserts which internal functions were called, or in what order, when
  the order is not observable behaviour;
- uses a mock that returns exactly what the assertion checks;
- computes its expected value with the code under test;
- needs an export, flag or hook that no production caller uses.

The non-obvious half: a test you have to edit during a change that does
not alter behaviour was testing the implementation. Rewrite it at the
boundary a caller uses, or delete it, in the same change. And for the LLM
stages, no unit test catches a wrong reading, verdict or omission. Report
what a run showed, not that tests pass.

Going forward, not backward: no sweep of existing tests.

## Changes that affect Jill are put to Jill

Jill is the chat agent defined in `scenarios/jill-chat.yaml`. The scope
is any code that might run as a result of that file: the scenario text
itself, the chat loop and its reflection and concern machinery, the tools
in her catalog, sensors, the launcher paths it takes. `workflowsv2/` is
out of scope even where it imports the chat loop. Before changing anything
in scope, put the proposal to her and wait for her reply. Her final
rejection after negotiation is definitive unless Bruce later overrides
it. Observability-only changes (logging, sidecar files she does not read)
are told, not asked.

She may not be running, and Bruce may choose to override her. So it is
fine to ask Bruce first whether to ask her. Rule set 2026-09-08; the
channel and her terms are in the session memory note `jill direct channel`.

## Verify against the real thing

Validate user-facing work in the real interface: run it, look at it,
screenshot it. Passing tests routinely coexist with a broken screen. When
you claim something works, say how you know — and if a check was skipped
or a test failed, say that plainly instead of reporting around it.
