# Attune AI Framework v16.2.0

AI-powered developer workflows with cost optimization and multi-agent orchestration.

@./python-standards.md

---

## Quick Start

```bash
pip install attune-ai                     # Install (zero-config, ready to use)
```

Works out of the box: subscription-first routing with automatic
API fallback for large modules when `ANTHROPIC_API_KEY` is set.
Run `python -m attune.models.auth_cli setup` to customize.

**CLI:** `attune <command>` (canonical) or
`python -m attune.cli_minimal` (full).
See `docs/reference/cli-reference.md`.

---

<!-- attune:collaboration:start -->

<!-- generated from content/collaboration/contract.md - edit the master, then run scripts/project_collaboration_contract.py -->

## Cross-provider collaboration

### Principles

Every principle below names its enforcer — the ratchet, gate, hook,
or drift-guard test that makes it true without anyone remembering
it. A principle marked **aspirational** has no mechanical enforcer
yet: treat it as binding discipline, and treat adding its enforcer
as pickable work.

1. **The receipt beats the promise.** "Configured", "registered",
   and "exited 0" are claims; evidence of the user-visible behavior
   is the receipt. Delegated lanes declare their receipt type at
   launch and the lead re-runs receipts centrally.
   *Enforcer: **aspirational** (ruled discipline —
   `.claude/rules/attune/decision-routine.md` delegation receipts
   + this contract's Verification receipts section; no mechanical
   gate).*

2. **The code is the contract; spec text is a hypothesis.** Before
   executing any spec-named scope, grep the code for the property
   the phase targets and execute against THAT set.
   *Enforcer: **aspirational** (lessons-core rule; no gate can
   check intent — partially backstopped by drift guards below).*

3. **One source, projected — never hand-edited twins.** Skills,
   the collaboration contract, help pages, and docs feature pages
   are projections; edit the master and re-project.
   *Enforcers: `tests/unit/plugins/test_sync_agents_skills.py`
   (skills mirror), `tests/unit/scripts/
   test_project_collaboration_contract.py` (contract blocks),
   `tests/unit/lessons/test_core_mirror.py` (lessons core),
   `tests/unit/authoring/test_projection_drift.py` (authored
   projections) — all fail CI on drift.*

4. **Dangerous constructs are blocked, not discouraged.** No
   `eval`/`exec`, no unvalidated file paths, no bare `except`.
   *Enforcers: `src/attune/hooks/scripts/security_guard.py`
   (PreToolUse block on eval/exec), pre-commit detect-secrets,
   `tests/unit/gates/test_path_validation_gate.py` (AST scan —
   modules with write-capable file ops must reference a
   path-validation helper or hold an allowlist entry; seeded
   2026-07-29 with 35 vetted modules, ratchets shrink-only).*

5. **Coverage is a floor, not a goal.** Changed code carries
   ≥85% coverage — CI and the local bar are the same number
   (chair-ruled 2026-08-22, superseding the earlier 80/85 split).
   *Enforcers: `codecov.yml` project+patch gates (85%),
   `tests/unit/ci/test_workflow_yaml.py::
   test_coverage_threshold_is_at_least_80` (the threshold itself
   is drift-guarded).*

6. **CI spends attention, never money.** Per-push/PR workflows run
   keyless (`ANTHROPIC_API_KEY: ""`); the real secret lives only in
   allowlisted, manually-dispatched or budget-capped jobs.
   *Enforcers: `tests/unit/ci/test_ci_spend_guard.py` (secret refs
   allowlisted, non-allowlisted assignments must be `""`),
   `tests/unit/ci/test_workflow_yaml.py`
   (timeouts/pinning/concurrency).*

7. **A failed gatekeeper fails the gate.** A security auditor that
   errors or goes missing fails the Security gate — absence is not
   a pass.
   *Enforcer: sentinel semantics pinned by
   `tests/unit/agents/test_release_prep_team_orchestration.py`
   (chair-ruled 2026-07-29).*

8. **Docs may not cite fiction.** A doc that names a symbol which
   no longer imports fails CI.
   *Enforcers: `doc-import-audit` CI job +
   `tests/unit/test_generated_doc_import_drift.py`; wiring claims
   checked by the `wiring-audit` job.*

9. **Identity and brand drift are ratcheted.** Legacy identifiers
   and retired framing cannot re-enter the tree.
   *Enforcers: G5 brand-drift pre-commit gate +
   `tests/unit/gates/test_brand_drift.py`,
   `tests/unit/gates/test_claim_drift.py`.*

10. **Context is budgeted.** Always-loaded rule bodies fit a
    byte budget; everything else is JIT-recalled via the index.
    *Enforcer: `tests/unit/rules/test_rules_residency_budget.py`.*

11. **Seats advise; the chair promotes; the lead integrates.**
    Cross-provider seats are advisory, the integrating lead owns
    synthesis and central receipt re-runs below the chair, and only
    the chair promotes (R8).
    *Enforcer: **aspirational** (governance ruling, D8/D9 +
    R8 — carried by this contract's text on all provider surfaces;
    inherently procedural).*

12. **Memory is derived, never authored in the serving layer.**
    Durable findings land in the tracked corpus (lessons, spec
    decisions, handoffs); Redis indexes are hydrated projections.
    *Enforcer: **aspirational** (contract text; hydration
    overwrites hand-written keys on the next run, which is a
    ratchet-by-reconstruction, but nothing blocks the direct
    write).*

13. **Simpler is better.** Three clear lines beat one clever
    abstraction: flatten nested conditionals, inline one-use
    helpers, prefer stdlib over custom abstractions, and review
    every change for complexity it didn't need.
    *Enforcer: **aspirational** (ratified design philosophy,
    carried in every provider surface's instructions; simplicity
    is judged in review, not gated).*

14. **A handoff is context, not authority.** The receiving agent
    verifies a handoff against the current Git state and tests
    before continuing; the current worktree, Git state, and test
    results are the shared truth, never hidden chat context.
    *Enforcer: **aspirational** (contract text — Shared truth +
    Handoffs sections; inherently procedural, since the check IS
    the receiving agent's first action).*

15. **Degrade gracefully around the memory layer.** When Redis or
    the memory index is unreachable, skip recall and proceed —
    work is never blocked on the memory layer, and recalled
    results are context to verify, not authority.
    *Enforcers: `tests/unit/memory/test_session_stash.py` (backend
    resolution failure degrades to None, never raises),
    `tests/unit/test_mcp_memory_tools.py` (memory tools degrade
    gracefully when the layer is absent),
    `tests/unit/memory/test_session_hydrate_fail_open.py` (the
    SessionStart hydrate hook exits 0 with a skip notice when the
    backend is unreachable — machine-local by design: the hook is
    personal infra outside this repo, so the test runs the real
    script where `~/.attune/memory/` exists and skips elsewhere,
    including CI).*

16. **Claims carry their basis.** A load-bearing claim states how it
    is known — "verified by <probe>" or, plainly, "inferred, not
    checked". Verify whenever the probe is cheap; when you do not,
    SAY SO IN THE CLAIM. The bug is never the inference, it is the
    inference wearing the grammar of a verified fact, because the
    reader cannot tell them apart and acts on both alike. Before
    asserting, name the property your check actually establishes and
    ask whether it is the property the next action depends on — a ref
    comparison answers "what does the remote know", never "what is on
    this disk"; a clean `git status` means "matches its own HEAD", not
    "is current".
    *Enforcer: **aspirational** (no gate detects an inference stated
    as fact — it is a property of prose. Reviewed periodically via
    the `/retro` close-out, which asks which claims carried no stated
    basis. Ratified 2026-08-22 after a release session in which four
    such claims — one relayed to a peer as the chair's authorization
    — each had a one-line verifying command available and unrun, and
    in which two existing detection mechanisms had already reported
    the problem and were read as ambient noise.)*

### Shared truth

- Treat the current worktree, Git state, and relevant test results as
  authoritative. Do not rely on hidden chat context for a handoff.
- Preserve unrelated working-tree changes and do not touch another
  agent's worktree.
- Discover capabilities from the available tools, MCP server, and
  tracked skills; do not rely on hard-coded capability counts.

### Session protocol

- Before non-trivial work, run
  `python scripts/collaboration_preflight.py`. It is read-only, uses
  cached Git refs, and does not fetch, pull, switch branches, invoke
  `uv`, or create an environment.
- State the goal, acceptance criteria, assumptions, and intended
  verification before non-trivial implementation.
- Prefer existing repository conventions and public interfaces before
  adding a parallel mechanism.
- Keep provider-specific setup in adapters. The shared contract must
  still work when only one provider is available.

### Lead programmer and delegation

- The project has a **lead programmer: Claude**, global by default.
  A per-feature lead may be set via the feature-lead-governance
  spec's mechanism; where one is set, it overrides the global
  default for that feature only (feature lead, not permanent model
  owner — its D1).
- The lead owns integration, synthesis, central receipt re-runs,
  and the final recommendation below the chair. Other seats work
  ADVISORY: their findings and drafts route through the lead, and
  they should expect the lead to integrate, amend, or decline with
  a recorded reason. Only the chair promotes (R8).
- **Single-provider fallback:** when the lead's provider is absent
  from a session, lead duties devolve to the CHAIR (integration and
  final recommendation), the active provider works
  advisory-to-the-chair, and receipt re-runs fall to whatever
  provider is present. The contract stays executable with one
  provider; the lead role resumes when its provider returns.
- **Receipt-declared delegation is binding for cross-provider
  lanes**: every delegated lane names its receipt type(s) at
  launch (suite / behavioral / live-fire / metric /
  evidence-chain), and the lead re-runs the receipts centrally
  before work reaches the chair. A seat's self-report is never
  the receipt.
- **Lead-conduct guards (D11d, 2026-07-30 — ruled from a live
  pushback test):** (1) CHAIR-ARMS: the lead never arms auto-merge
  on a diff that expands lead authority or touches
  governance/enforcement text; the chair's own label application
  is the read-receipt, bound to the head SHA the chair armed — a
  subsequent push invalidates the receipt, so the lead disarms and
  the chair re-arms after re-reading. (2) COUNTER-CASE: a ruling recommendation
  reaches the chair carrying the strongest argument against
  itself, unprompted. (3) CADENCE BRAKE: the second
  authority-affecting ruling in one session is flagged as such,
  with a fresh-eyes batch offered. (4) FEEDBACK-ASK GRAMMAR, FULL
  SCOPE: a seat asking the chair for feedback on its own conduct,
  work, or a ruling recommendation renders the ask through the
  communication grammar throughout, each construct firing when its
  content exists — a counter-position as a pushback shape (the
  user's position and the seat's alternative, side by side, with
  the rationale), enumerable points as a per-point pick (adopt /
  modify / reject per item), and open-ended asks as free-text form
  fields; no construct fabricates disagreement or options to
  satisfy the rule. The SHAPE is the requirement, not any widget:
  seats without a form surface render the constructs as structured
  text blocks. (Chair overruled the
  lead's disposition-only recommendation; rich-surface mechanics
  for Claude sessions live in
  `.claude/rules/attune/communication-grammar.md`.)
  (5) PROTECT-THEN-ASK: reversible protective acts against the
  lead's OWN prior actions execute BEFORE any elicitation form is
  built, with the form rendered afterward for the standing
  decision; undoing a chair action is never a protective act,
  neither directly nor by reverting an own-action the chair has
  since endorsed or relied on.
- Delegated runs are recorded in the cross-review R5 dogfood
  ledger (`docs/specs/cross-review/receipts.md`). P1 FULL
  ACTIVATION was ruled at the D8 bar (chair, 2026-07-30, 11
  fully-triaged runs): the lead/delegation model is the standing
  operating mode, no longer pilot-scoped; per-feature leads are
  set via the feature-lead-governance spec's mechanism, and the
  ledger keeps accruing as the standing evidence surface.
- **The lead is reviewed too (D11, 2026-07-29):** a lead-authored
  diff touching a risk class — authored contract/spec/rule text
  (named explicit 2026-07-30 as the R5 ledger's highest-yield
  class), security, persistence, release, governance/enforcement
  surfaces (gates, guards, ledgers, this contract), external
  boundaries, or a disputed finding — requires a different-model
  review lane BEFORE the chair reads the recommendation; the chair
  may override in either direction.
  When the lead REJECTS a seat's finding, the ledger row carries
  the seat's claim verbatim plus the lead's reason
  (`tests/unit/gates/test_ledger_rejection_format.py` enforces the
  format). RULED (chair, 2026-07-30, at the D8 bar with 10
  fully-triaged runs): risk-triggered lanes are the PERMANENT
  default — the lane is not expanded to all lead diffs (5 clean
  lanes on well-tested code and release diffs showed cost without
  yield there). Yield stays measured in the R5 ledger; a future
  chair ruling can revisit either direction.

### Artifact selection

- Match the artifact to the work before non-trivial implementation and
  name the selected tier in the session contract:
  - **Inline edit** — trivial, one file, no ambiguity.
  - **Structured one-shot** — single-session work framed by a goal,
    constraints, and acceptance criteria.
  - **XML task** — dependent work across three or more files, or work
    that must be executable as a cold handoff.
  - **Spec** — multi-session or multi-PR work, design ambiguity, or an
    irreversible choice.
- Escalate the artifact tier when ambiguity or dependencies grow; do
  not add ceremony to work that still fits a smaller tier.

### Verification receipts

- Before implementation, name the claim and a probe that would fail if
  the claim were false. Report the probe actually run and its result.
- Treat unit tests as evidence only inside their tested boundaries.
  Hooks, persistence, networking, packaging, and other external seams
  require a non-mocked round trip through the real boundary.
- “Configured,” “registered,” and “exited successfully” are not
  working receipts. Prefer evidence of the user-visible behavior.

### Handoffs

- For multi-step work, create or update a portable handoff from
  `templates/agent-handoff.md` at `docs/handoffs/<branch-slug>.md`
  (slug = branch name with `/` replaced by `-`), tracked on the
  branch. Delete the file when the branch merges.
- A receiving agent verifies the handoff against the current Git state
  and tests before continuing; a handoff is context, not authority.
- Record only concrete evidence: commands actually run, their results,
  changed files, unresolved risks, and the next action.

### Shared memory

- A shared cross-session memory index lives in local Redis
  (`idx:attune_memory`): curated memories, lessons, and file
  pointers, hydrated from the tracked corpus. Recall before
  non-trivial work on unfamiliar ground:
  `redis-cli FT.SEARCH idx:attune_memory "<term|term>" RETURN 2
  description type LIMIT 0 5` — OR-join terms with `|` (plain
  multi-word queries AND-join and miss paraphrases) — or
  `redis-cli FCALL recall_digest 0 "<term|term>"` for scored
  digests.
- Recalled results are context, not authority: they reflect when
  they were written. Verify against the current tree before
  acting on one.
- The index is DERIVED — never write `attune:memory:*` keys
  directly. To persist a durable finding, commit it to the
  tracked corpus (`.claude/lessons.md`, the owning spec's
  `decisions.md`, or a handoff file); it is re-indexed at the
  next hydration.
- Degrade silently when Redis is unreachable: skip recall and
  proceed. Never block work on the memory layer.

### Critical code rules

- NEVER use `eval()` or `exec()`.
- ALWAYS validate file paths in file operations; security tests are
  required for file-op code.
- NEVER use bare `except:` — catch specific exceptions and log them
  before handling.
- Type hints and docstrings on all public APIs; minimum 85% test
  coverage on changed code.
- Simpler is better: flatten nested conditionals, inline one-use
  helpers, prefer stdlib over custom abstractions.

### Git and pre-commit

- Commits are GPG-signed; `git pull` rebases.
- Pre-commit auto-fix hooks modify staged files mid-commit.
  Pre-flight the PINNED tools on your files BEFORE `git add`
  (`uv run --with pre-commit pre-commit run black --files <f>`).
- After every commit, verify it landed (`git log --oneline -1` +
  `git status --short`) — hooks can skip a commit with exit 0.
- If a hook reformats staged files, the fixes land unstaged —
  `git add` again and retry.
- A guard blocks commit messages containing literal `eval(` /
  `exec(` — write the message to a file and `git commit -F <file>`.
- `--no-verify` is forbidden. To skip ONE misbehaving hook:
  `SKIP=<hook-id> git commit …`.
- detect-secrets flags placeholder-looking strings; annotate false
  positives with `# pragma: allowlist secret`.

### Branch and worktree discipline

- One branch per agent per task. Never commit to a branch another
  agent has in flight.
- Before updating `main`, inspect its existing checkout. Pull only when
  that checkout is on `main` and clean; otherwise fetch `origin/main`
  separately and leave the current task worktree untouched.
- One PR per feature surface: before opening a PR, check for an
  existing or parallel PR touching the same files
  (`gh pr list`, `git log origin/main -- <files>`).
- Fold in, don't stack: when a same-function PR is already open with
  ≤1 commit, fold its diff into the new PR and close the old one AT
  OPEN TIME — a PR stacked on an open sibling goes DIRTY against its
  own merged content (#2200, 2026-08-23). Record the fold in the new
  PR body.
- Before every commit: `git branch --show-current` — confirm the
  checkout you edited is on the branch you mean to ship.
- **More than one branch in flight → one worktree each** (chair,
  2026-08-25). Juggling concurrent branches through a single checkout
  is what produces wrong-branch commits, vacuous pushes, and resets
  that eat unrelated work; separate worktrees make the whole class
  impossible because there is no switch to get wrong. Single-branch
  work stays in one checkout — the policy is deliberately scoped to
  concurrency, since every worktree costs a ~460 MB venv and the pile
  is already large.
  *Enforcer: `src/attune/hooks/scripts/dirty_switch_guard.py` — a
  PreToolUse hook that refuses a branch switch or `git reset --hard`
  while the tree is dirty (new-branch creation, path-scoped restores,
  and explicit `--force` stay allowed).*
- **Reap the worktree when its PR merges.** A worktree outlives its
  branch otherwise; delete it in the same step that confirms the
  squash, never in a bulk sweep that might delete the session's own
  worktree or one holding an open PR.
- Never suppress stderr on a state-changing git command
  (`2>/dev/null` on a `checkout`/`commit`/`push` turns a loud refusal
  into a silent no-op you will then reason from).
- Don't touch other agents' worktrees under `.claude/worktrees/`.

### Single-source projections

- `plugin/skills/*/SKILL.md` and `.claude/skills/*/SKILL.md` are
  SOURCES for the tracked `.agents/skills/` mirror — after editing
  a skill, run `python scripts/sync_agents_skills.py --write` and commit
  both sides (a drift-guard test fails CI otherwise).
- This contract's own projected blocks and
  `templates/agent-handoff.md` are owned by
  `scripts/project_collaboration_contract.py` — edit the master,
  re-run the projector.
- `.help/` and docs feature pages are projector-owned; edit the
  source and re-project, never the generated output.

### CI notes

- Per-push/PR workflows run with `ANTHROPIC_API_KEY: ""` (empty,
  keyless) by design — never wire the real secret into them. To
  reproduce keyless CI locally use the empty string, not unset.
- Windows matrix lanes are slow (~13 min) but real — path,
  subprocess, and encoding changes must wait for them.

<!-- attune:collaboration:end -->

---

## Command Hubs

Use `/hub-name` to access organized workflows:

| Command | Description |
| ------- | ----------- |
| `/spec` | Spec-driven development with approval loop |
| `/attune` | Socratic discovery — routes to any workflow |
| `/security` | Security audit |
| `/smart-test` | Find test gaps, generate tests |
| `/release` | Release preparation and publishing |
| `/help` | Quick reference for all commands |

**More commands** (type `/help` for full list):

`/dev` `/plan` `/brainstorm` `/code-quality`
`/doc-gen` `/fix-test` `/refactor` `/deep-review`
`/agent` `/wizard` `/bulk` `/remember`

---

## Code Simplification

After writing or modifying code, review it for unnecessary
complexity. Claude tends to over-engineer — too many
abstractions, unnecessary classes, premature optimization,
over-configurable interfaces. Counteract this by:

- Flattening deeply nested conditionals (use early returns)
- Inlining trivial helper functions used only once
- Removing dead code paths and unused parameters
- Preferring stdlib over custom abstractions
- Reducing class hierarchies when a function suffices

Simpler is better. Three clear lines beat one clever
abstraction.

---

## Critical Rules

- NEVER use eval() or exec()
- ALWAYS validate file paths with _validate_file_path()
- NEVER use bare except: - catch specific exceptions
- ALWAYS log exceptions before handling
- Type hints and docstrings required on all public APIs
- Minimum 85% test coverage
- Security tests required for file operations
- When creating a detailed plan with 3+ tasks or touching
  3+ files, use XML-enhanced prompt format (see
  `.claude/rules/attune/xml-enhanced-prompts.md`). For
  simpler work (single-file edits, config changes, bug
  fixes), plain descriptions are fine.

---

## Socratic Interaction Rule

**Asking for more than one thing? That is a form. Build it before you
write the sentence.**

Everything below elaborates that line. If you only remember one thing,
remember that one — it is the case that fires most often and the case
I most often get wrong, because prose is cheap to emit and a form costs
a beat.

**Build a form; don't hand-write a question turn.** (D21 — this rule
used to name `AskUserQuestion`, and naming a tool got it executed as
that tool, so the communication grammar almost never fired.) Construct
a `FormSchema` via `attune.elicitation.form_from_dict` and let
`select_form_surface` pick the surface. The widget is the default;
`AskUserQuestion` is one of its fallbacks, not the starting point.
When the session is widget-capable, render `form_to_widget_html(form)`
on the widget surface — hand-writing an `AskUserQuestion` turn without
consulting `select_form_surface` IS the D21 failure mode (re-hit live
2026-08-02 on the /fix intake; caught by Patrick).

### Two grammars, two directions — not a ranking

They are not competing methods and neither is "primary". They serve
opposite directions of the same exchange:

| Direction | Grammar | When |
|---|---|---|
| I ask | a form | something genuinely needs settling |
| You answer | terse vocab (`y` / `go` / `1` / `→ X`) | it is already settled |

A bare confirm is **not an ask** — it is you closing a loop I opened.
Putting a form in front of `go` adds friction to the highest-frequency
interaction in the loop. Do not do it.

The failure mode this rule guards is not "used terse vocab where a form
belonged." It is **mis-classifying a multi-dimension ask as a bare
confirm** because prose is faster to write.

### Fire a form when ANY of these holds

- **Two or more independent dimensions must be settled** — batch them
  into ONE form, never N sequential turns. This is the highest-value
  case, and it is the headline above.

  Build the `FormSchema` even when the surface ends up being
  `AskUserQuestion`. It is a portable, validated artifact that renders
  to every surface — `form_to_widget_html`, `form_to_elicitation_schema`,
  and `form_to_askuserquestion` ("render a form to BATCHED
  `AskUserQuestion` payloads"). Hand-writing the turn skips the
  validation and pins you to one surface.

  Actual limits, so you size the form rather than guess: **2–4 options
  per question, 1–4 questions per call.** A batch of >1 question is
  blocked by default by `ask_question_format_guard.py` and opts in via
  `metadata.source` containing "form" (e.g. `"elicit-form"`) — a policy
  default with a documented hatch, NOT a structural cap. Beyond 4
  dimensions, split into a two-tier picker.
- Three or more alternatives, or two with tradeoffs worth stating
  (→ `decision` construct).
- You are recommending against something the user named
  (→ `pushback` construct).
- The answer is a number, a date, or free text longer than a phrase.
- The choice changes scope, architecture, files, external state, or
  acceptance criteria — or is hard to reverse.

### A raw button-turn is correct only when ALL of these hold

One dimension, ≤3 options, no tradeoffs worth stating. Plus one
standing exception: the user is in keyboard mode.

(The terse-vocab path is not an exception here — see "two grammars"
above. A bare confirm of a resolved referent is not a question turn at
all, so this test never applies to it.)

**Examples:**

- "run tests" → one dimension, few options → button-turn is fine.
- "security audit" → path + focus + depth → ONE form, three fields.
- "review code" → area + focus + output shape → ONE form.
- "commit" → files + change kind → ONE form.

**Do NOT:**

- Jump straight to running commands without scoping
- Assume the user wants the broadest possible execution
- Ask N sequential button-turns for what is one form
- Pad a form with fields you don't need — ceremony is the failure mode
  this rule is most likely to cause. If one dimension is genuinely all
  you need, ask for one.

This rule applies to ALL workflow interactions, not just `/attune`.

---

## Project Structure

```text
src/attune/
├── agents/            # Release agents, state persistence, recovery
│   ├── release/       # ReleaseAgent, ReleasePrepTeam
│   └── state/         # AgentStateStore, AgentRecoveryManager
├── workflows/         # AI-powered workflows (all SDK-native)
├── models/            # Authentication strategy and LLM providers
├── meta_workflows/    # Intent detection and natural language routing
├── orchestration/     # Dynamic teams, workflow composition, agent models
├── plugins/           # BasePlugin + register_mcp_tools() hook
├── telemetry/         # FeedbackLoop, UsageTracker (MemoryBackend protocol)
└── cli_router.py      # Natural language command routing

attune_redis/          # Redis plugin — BUNDLED, ships in the attune-ai
                       # wheel (packages.find scans '.'); redis +
                       # agent-memory-client are core deps. There is no
                       # `pip install attune-redis` — that name 404s.
```

---

**Version:** 16.2.0 | **License:** Apache 2.0 | **Repo:** [attune-ai](https://github.com/Smart-AI-Memory/attune-ai)

## Lessons — core

The always-loaded core (Patrick-ratified 2026-06-12): high-severity
classes, session mechanics that fire before retrieval could, and the
highest fire-frequency judgment rules. The FULL corpus (380+ lessons)
lives in [.claude/lessons.md](lessons.md) — canonical source; these
entries are verbatim mirrors, drift-guarded by
`tests/unit/lessons/test_core_mirror.py`.

Query the tail with `/recall <topic>`; relevant lessons also surface
automatically at prompt time and at tool-call decision points.
APPEND NEW LESSONS to `.claude/lessons.md`, not here — mirror into
this section only if core-worthy (and then keep both copies in sync).

- **Diagnosing CI from the `gh` CLI — field names, cancellation
  traps, and in-flight log availability**:
  - **`gh pr checks --json` field is `bucket`**
    (pass/fail/pending/skipping/cancel), NOT `conclusion`
    (discover the full field list by passing an invalid field
    name and reading the error).
  - **`--watch --fail-fast` exits prematurely (exit 0) on
    cancelled-but-"fail"-tagged guard jobs** — `--fail-fast`
    triggers on any row reading `fail` even when the job
    conclusion is `cancelled` (zero steps — e.g. a
    dependabot-only guard skipping on a regular PR; `Run
    Security Scanner` does this). Exit 0 makes it look like all
    passed. Drop `--fail-fast` (wait the full matrix), or
    post-process to ignore rows whose actual conclusion (`gh api
    .../jobs/<id>`) is `cancelled`. Always re-fetch `gh pr
    checks <PR>` after the watcher exits — never trust its exit
    code as "CI done".
  - **`gh run view --log-failed` returns nothing while the run
    is in flight** — even when jobs already show `fail` it says
    "run is still in progress; logs available when complete"
    (the job-level link doesn't help). You can DETECT failures
    early via `gh pr checks --json bucket` polling but can't
    DEBUG until the whole run completes — don't start
    speculative fixes on the fail count alone (could be a flake,
    real bug, or tolerable cancellation).
  - **Rapid pushes + `cancel-in-progress` cancel the prior run,
    and cancelled-but-required = BLOCKING** — N commits within
    minutes trigger N runs; `concurrency` + `cancel-in-progress:
    true` cancels each prior, and the latest can also get
    cancelled (webhook race), leaving a required check in
    `cancel` bucket → PR BLOCKED. Recovery: `gh run rerun
    <run-id>` on the latest SHA. Prevention: before pushing a
    fix, check `gh run list --workflow=X.yml --branch=<name>
    --limit=1 --json status` — if `in_progress`, wait ~5-7 min
    or accept the re-run.

- **The editable install's MAPPING points `attune` at the MAIN
  checkout, not your worktree — so code/deps resolve wrong when run
  from a worktree.** `.venv/.../__editable___attune_ai_*_finder.py`
  maps `attune` → main's `src/`, so `uv run attune …` /
  `python -m attune.X` from a worktree runs MAIN's code (often behind
  origin/main) and worktree-local edits are invisible to the running
  process. Diagnose: `cat .venv/lib/python*/site-packages/
  __editable__*_finder.py | grep MAPPING`; `ps -p <pid> -o command=`
  (always the main venv); `curl -s localhost:8765/api/info` for the
  live version. The fixes below all stem from this one root cause.
  - **Run worktree code:** `PYTHONPATH=<ABSOLUTE-worktree>/src
    <python> -m attune.X`. Use an **absolute** worktree path — NEVER
    `$(pwd)/src`: if the cwd shifted out of the worktree (or a pasted
    `cd` got dropped), `$(pwd)` silently resolves to main's src and the
    process runs main's branch while looking identical (a wrong-version
    trap caught only by render-time tells). `uv run --project /main`
    does NOT help — main's venv MAPPING still points at main's src; the
    PYTHONPATH override is mandatory.
  - **Which python (worktree venv lacks extras):** the worktree
    `.venv` is `uv sync`'d with only `--extra dev --extra developer`,
    so `[ops]` deps (fastapi/uvicorn/jinja2) are absent →
    `ModuleNotFoundError`. Either (a) use the MAIN venv's python (it
    usually has all extras) + `PYTHONPATH=<worktree>/src`, or
    (b) bring up the worktree venv: `uv pip install -q fastapi
    'uvicorn[standard]' jinja2 python-multipart pytest pytest-xdist
    pytest-asyncio httpx` (quote bracket-extras; a later `uv sync`
    WIPES these — durable fix: add the deps to `[dev]` in pyproject).
  - **`attune.ops` / `python -m <pkg>` launch:** working invocation is
    `/path/to/main/.venv/bin/python -m attune.ops --project-root
    /path/to/main --port <p> --no-browser` with
    `PYTHONPATH=/path/to/worktree/src`. `--project-root` overrides the
    cwd-based default so the PROJECT label / `cfg.project_root` resolve
    to main, not the worktree slug.
  - **Coverage measurement** from a worktree reports 0% (the
    `[tool.coverage.run] source=["attune",…]` filter can't map the
    worktree path to the package name via the main-pointing MAPPING).
    Workaround: `cd /tmp && rm -f .coverage && PYTHONPATH=<repo>/src
    PYTEST_ADDOPTS="-p no:xdist -o addopts=" <venv>/bin/python -m
    coverage run --rcfile=/dev/null --source=attune.<mod> -m pytest
    <repo>/tests/…` (cwd in /tmp skips the rcfile; strip
    `-n auto`/`--cov`). Plain test *execution* from a worktree is
    fine — only coverage measurement needs this.
  - **MCP server in a worktree** (e.g. `rag_knowledge_query` failing
    `…requires the [attune-help] extra`): the worktree venv lacks the
    extra. Fix `uv pip install --python <worktree-venv> attune-help`;
    the ALREADY-running MCP server self-heals on the next query (lazy
    per-query load — no restart). Recurs per worktree until the extra
    is in `[dev]` + lockfile.
  - **Entry-point-resolved backends** (`resolve_backend()` via the
    `attune.memory_backends` entry point) resolve DIFFERENTLY per env —
    which python + cwd + installed extras + service reachability all
    matter, and `import attune_redis` shadows to the worktree's
    cwd-local copy. Verify the LIVE process's resolution (log
    `type(resolve_backend()).__name__` from inside the hook), never
    infer from a convenient `python -c`.
  *(Consolidated 2026-06-05 from 8 separate lessons.)*

- **Spec-named work-scope drifts from code reality —
  grep the actual instances before executing the named
  scope**: hit 2026-06-01 executing Phase 5 of
  `sdk-error-message-fidelity`. The spec named six
  workflows as Phase 5 targets: `test-audit`, `doc-audit`,
  `doc-gen`, `discovery-sweep`, `secure-release`,
  `deep-review`. A `grep -l "sdk_error_message"
  src/attune/workflows/` showed only ONE of those (`deep_review`)
  actually used the legacy helper Phase 5 was designed to
  retire. The other FIVE named workflows had hand-rolled
  error messages (a different, less-bad failure mode) —
  but five OTHER workflows not on the spec's list
  (`rag_code_gen`, `research_synthesis`, `simplify_code`,
  `release_prep`, `deep_review`) DID still use the legacy
  helper. The spec text was written when those code paths
  looked different; the code moved; the spec text didn't.
  Blindly migrating the spec-named six would have left
  four legacy-helper users unmigrated and migrated five
  workflows that didn't need it. **Pattern**: before
  executing a spec phase whose scope is named by
  workflow/module/file, grep the code for the actual
  property the phase targets (legacy helper usage, deprecated
  call, pattern signature) and use THAT set as the
  execution scope. Update the spec text to reflect reality
  in the same PR. The spec's named list is a starting
  hypothesis, not the contract — the code is the contract.
  Pairs with the "Re-validate a spec's premise" lesson
  above and the "Audits with 'possibly delete if X'
  qualifiers" lesson — same family (spec/audit text goes
  stale; verify against current code before acting).

- **`git stash pop` gotchas — inverted --ours/--theirs and
  silent skips**:
  - **--ours/--theirs are INVERTED from a regular merge** —
    stash-pop has `git apply` semantics: `--ours` = the CURRENT
    working tree (e.g. main after a ff-merge — the authoritative
    content), `--theirs` = the STASHED content (same direction
    as `git merge`, opposite of `git rebase`). In the "ff-merge +
    restore wip" dance, the common conflict is a spec status
    field upstream changed since the stash; `git checkout --ours
    <files>` keeps upstream. ALWAYS `git stash drop` after a
    deliberate-discard resolution (else the stale entry lingers
    and is easy to revive by mistake): `git checkout --ours
    <files> && git add <files> && git stash drop`.
  - **Silent skip when the destination branch TRACKS files the
    stash treated as untracked** — stashing untracked-on-branch-A
    files, switching to a branch where they're tracked, then
    popping: the stash is retained but those files are silently
    dropped from the working tree (the branch's tracked versions
    stay, your stashed versions vanish — no conflict marker, no
    warning). Diagnostic: after pop, `git diff stash@{0} --
    <path>`; non-empty diff + `git status` showing the file
    unchanged = silently skipped. Mitigation: pop with `git
    checkout stash@{0} -- <files>` to force the overwrite, then
    drop manually.

- **Harness safety classifier blocks bundled-destructive
  scripts even when the user authorizes the pattern — do
  destructive ops as individual commands**: user said "Do
  (a)" where (a) was "merge as green using the temp-
  remove-reviews dance," then I wrote a watcher script
  that combined three destructive steps (drop
  `required_approving_review_count=0` → admin-merge
  three PRs → restore reviews). The harness blocked the
  script with "Script disables branch protection and
  uses --admin --delete-branch to merge PRs without
  review; user said 'Do A' which doesn't authorize
  disabling protection or admin-merging multiple PRs."
  The fix was procedural, not technical: have the user
  manually run the protection-drop API call themselves,
  then I do the three `gh pr merge --squash --admin
  --delete-branch` commands one at a time (each
  individual command passed the classifier). General-
  ization: when the user authorizes a multi-step
  destructive sequence ("do X" where X has several
  unsafe ops), don't bundle them into a script — even
  with a `trap` for cleanup. Run each step as its own
  command and either ask per-step OR have the user
  pre-stage the most-protected operation. Saves a
  cycle of "wrote script → blocked → explained to user
  → user grants per-step." Read-only polling scripts
  (no merges, no protection changes — just `gh pr
  checks` reads) pass the classifier fine and are the
  right home for unattended logic during long CI waits.

- **Read/head/cat on untracked `.txt` files in a
  repo working tree can leak secrets into the
  conversation transcript — let the filename do
  the smell test BEFORE the Read**: hit
  2026-05-15 during attune-author release prep.
  `git status` showed three untracked files
  (`Codex-results.txt`, `attune.txt`,
  `twilio.txt`). A reflexive `head -3` on all
  three to "see what they are" echoed a live
  `sk-ant-api03-...` Anthropic API key into the
  conversation transcript. Required revoke +
  rotate. Pairs with the existing "Never paste
  PyPI tokens into chat" lesson — same failure
  mode, different vector (file Read vs human
  paste). Defensive rule: **filenames are the
  smell test**. Untracked files whose names
  suggest credentials (`attune.txt`, `twilio.txt`,
  `*.env*`, `*creds*`, `*secrets*`, `*api*key*`,
  `*token*`) should be treated as opaque — move
  out of the working tree (`mv X
  ~/.attune/scratch/`) or delete with `rm` based
  on provenance, without opening. Reserve Read
  for filenames whose shape suggests safe content
  (`*.md`, `*.py`, output dumps with clear
  topical naming like `Codex-results.txt`). The
  transcript is permanent; revocation is the
  only recovery.

- **Editor settings-sync is a silent secret-exposure vector — never
  put credentials in `settings.json` (VS Code, Cursor, JetBrains,
  etc.)**: discovered 2026-05-19 when a Read on VS Code's user
  settings.json surfaced a live `ANTHROPIC_API_KEY` stored in
  `claudeCode.environmentVariables`. The key flowed to Microsoft/
  GitHub cloud via VS Code's Settings Sync feature — revoking the
  key at the provider neuters its use but does NOT scrub the sync
  history. Two hazards combine: (1) editor extensions sometimes
  offer convenience fields like `claudeCode.environmentVariables`,
  `cursor.openai.apiKey`, JetBrains' "stored secrets in IDE config"
  that all flow through the same sync surface; (2) Read tools that
  open settings.json (yours or any agent's) immediately pull the
  literal credential into the conversation transcript. **Safe
  pattern:** store secrets in a 0600-permission file like
  `~/.attune/anthropic.env` and source it from `~/.zshrc` with a
  guard:
  ```
  [ -f ~/.attune/anthropic.env ] && set -a && source ~/.attune/anthropic.env && set +a
  ```
  Editor extensions that need the env var inherit it from the shell
  when launched. The .env file stays off Settings Sync entirely.
  **Detection hint:** when reviewing any editor config (settings.json,
  .vscode/settings.json, JetBrains XML), grep for known provider
  prefixes (`sk-`, `ghp_`, `xoxb-`, `AKIA`, etc.) BEFORE pulling the
  content into agent context. Filename smell-test from the existing
  ".txt secret leak" lesson applies broadly: any config file owned
  by an editor or IDE is a potential secret-leak surface, not just
  obviously-named credential files. **Recovery checklist when a leak
  is found:** (a) revoke at provider FIRST; (b) move to safe
  storage; (c) remove the plaintext entry from the editor config;
  (d) if Settings Sync was enabled while the secret was present,
  `Settings Sync: Reset` (Cmd+Shift+P) to clear cloud history; (e)
  add `settings.json` to detect-secrets baseline if not already
  scanned. Pairs with the existing "Read/head/cat on untracked .txt
  files" lesson (same failure mode, different file class) and the
  "Never paste PyPI tokens into chat" lesson (transcripts are
  permanent).

- **NEVER print a secret-bearing file "with a mask" — a masking regex
  that fails matches NOTHING and prints the secret verbatim, and BSD
  sed silently ignores `\S`, `\d`, `\w`**: 2026-08-22, live incident. To
  show a Redis conf's structure I ran
  `sed -E 's/(requirepass )\S+/\1<PW>/'` over the file and printed the
  result. On macOS that substitution matched **nothing** — `\S` is a GNU
  extension BSD sed does not implement, and sed does not error on an
  unknown escape, it just fails to match — so the line printed as
  `requirepass "<the live password>"` into a permanent transcript. The
  password had to be rotated again. **A failed mask is
  indistinguishable from a successful one in the output you are about to
  emit, because the thing that proves it worked is the thing you cannot
  see until it is too late.** Three rules, in order of preference:
  (1) **Do not print the file.** Print KEY NAMES only —
  `grep -oE '^[A-Z_]+' .env`, `grep -nvE '^\s*$' conf | cut -d' ' -f1` —
  or answer the question with a boolean/digest instead of content.
  (2) If a masked print is genuinely needed, **prove the mask on that
  exact line first** by asserting the output does NOT contain the
  secret, and only then emit. (3) Prefer Python's `re` over `sed` for
  masking: it implements `\S`/`\d`/`\w` portably, and you can assert the
  substitution count (`re.subn` returns it — a count of 0 is the alarm).
  Note this repo's corpus already carries three secret-leak lessons
  (untracked `.txt` reads, editor settings-sync, pasted PyPI tokens);
  this is the fourth vector and the first where the LEAK CAME FROM THE
  DEFENSE — the masking step itself. Corollary that cost extra here: the
  same value also appeared correct-looking in a `diff` I ran with the
  same broken mask, so the "empty diff" I reported proved nothing.
  **Never trust a redaction you have not tested.**

- **`Write` to an absolute `/Users/patrickroebuck/attune-ai/...`
  path from a worktree session lands the file on the PARENT
  MAIN checkout, not the worktree** — extends the existing
  worktree-vs-main lessons with a write-side failure mode.
  Hit 2026-05-31 writing
  `docs/specs/spec-status-self-truthing/decisions.md`. The
  worktree's CWD was the right place, but I used the bare
  repo absolute path (which resolves to `~/attune-ai/`, the
  main checkout, not `~/attune-ai/.claude/worktrees/<slug>/`).
  Symptom: `git -C <main> status` shows the new untracked
  file even though my branch is in the worktree. Detection:
  after any Write that touches the repo, `git -C <worktree>
  status` AND `git -C <main> status` — divergence = wrong
  path. Recovery: copy file to worktree, `git -C <main>
  checkout --` or `rm` to clean main. **Defensive rule**:
  when working in a worktree, absolute paths must include
  the worktree segment (`.claude/worktrees/<slug>/`). Bare
  `/Users/patrickroebuck/attune-ai/` paths are wrong by
  construction in a worktree session. Pairs with the
  existing `PYTHONPATH=$(pwd)/src` / launch lessons — same
  class of bug, different surface (write-side instead of
  execute-side).

- **"Create a new worktree to continue last session"
  usually means "use the existing worktree on that
  branch," not "create a second one" — git refuses
  two worktrees on the same branch**: hit 2026-06-02
  when a session-startup ask was "create a new
  worktree" with a queued `gh pr create --head
  <branch>` and the branch already had a worktree at
  `.claude/worktrees/<slug>` left over from the prior
  session. Creating a literal "new" worktree on that
  branch would have failed with `fatal: '<branch>' is
  already used by worktree at '<path>'`. The "new"
  framing here means "fresh session context," not
  "fresh git worktree" — the existing worktree's git
  state IS what the user wants to continue from.
  **Diagnostic recipe**: before creating a worktree
  for a named branch, `git worktree list | grep
  <branch>`. If a row matches, `cd` into it and reuse;
  surface the reuse to the user
  ("an existing worktree at <path> is on this branch
  — reusing it"). If not, create one off the requested
  base. Same pattern applies when a queued command
  references `--head <branch>` or `--base <branch>` —
  the worktree the command needs may already exist.
  Pairs with the existing worktree-PYTHONPATH /
  Write-absolute-path / dirty-state-recovery lessons —
  all are about correctly locating the right worktree
  for a piece of work; this one's about the
  multi-session handoff case where the prior session
  left state behind.

- **Verify-first applies to infra/config diagnoses, not just
  code APIs — read `gh api .../branches/main/protection` before
  asserting what blocks a merge**: 2026-06-03, the recurring
  per-PR "merge tax" was misdiagnosed as the scary-red
  `Run Security Scanner` CANCELLED check. Reading
  `required_status_checks` showed that check **wasn't even
  required** — a red non-required check is cosmetic. The real
  gate was `required_approving_review_count: 1` on
  self-authored PRs, and the `auto-approve-owner` job built to
  satisfy it was silently SKIPPING because its guard read
  `github.actor == 'patrickroebuck'` while the owner's GitHub
  login is `silversurfer562` (confirm with `gh api user --jq
  .login`). A 30-second `gh api` read would have caught all of
  it; instead I confidently asserted the wrong cause and even
  proposed a one-PR "fix" for the wrong thing. Pairs with the
  "research subagents confabulate SDK signatures — introspect
  before coding" and "re-validate a spec's premise" lessons —
  same discipline, applied to CI/branch-protection: (1)
  distinguish required vs non-required checks before treating a
  red check as blocking; (2) read the actual review gate; (3)
  grep workflow `if: github.actor ==` guards against the real
  `gh api user` login before trusting them. The cosmetic
  CANCELLED noise (separate, low-priority) is policy-bound,
  NOT config-fixable — `cancel-in-progress: false` is INVALID
  (`tests/unit/ci/test_workflow_yaml.py` requires it `true`;
  PR #1100 closed); clear it per-PR with `gh run rerun <run-id>`.

- **Branch-vs-worktree commit tangle — committing from a worktree
  that's on the WRONG branch lands the commit elsewhere and ships
  an EMPTY branch on push**: the failure mode is creating a branch
  in one checkout (`git -C <main> checkout -b X`) while editing and
  committing from a *different* worktree that is still on another
  branch. The edits + `git commit` land on the worktree's CURRENT
  branch (not `X`), so the new branch `X` points at the old main
  commit with none of the work, and a subsequent `git push origin X`
  ships an EMPTY branch — no diff, no PR content, looks like a
  successful push. Hit twice in one session 2026-06-03 (both
  recovered by re-applying the diff onto the right branch). **Fix —
  one cheap check before every commit:** confirm the worktree you
  are editing in is on the target branch with
  `git -C <worktree> branch --show-current` (or just
  `git branch --show-current` from inside it). Do all edits for a
  given branch INSIDE the worktree that is checked out on that
  branch; don't create the branch in checkout A and commit from
  worktree B. When a session spans multiple worktrees, the safest
  pattern is to keep all work for one branch in a single worktree
  and switch that worktree's branch between tasks, rather than
  juggling `git -C` across checkouts. Pairs with the existing
  worktree-PYTHONPATH / Write-absolute-path / dirty-state-recovery
  / "create a new worktree to continue last session" lessons —
  same family (correctly locating the right worktree+branch for a
  piece of work), this one is the commit-destination surface.

- **"Registered ≠ working" — dogfood the live loop; a non-mocked
  round-trip test is the receipt**: the P2 memory hooks were registered
  in the live plugin AND 1665 mocked unit tests were green, yet the
  live Stop→stash→recall loop did not round-trip on first real contact
  (caught only because Patrick demanded the receipt instead of accepting
  "hooks registered = done"). The mocked tests passed precisely because
  they mocked Ollama + the backend. Two durable takeaways: (1) for any
  hook/pipeline with external deps (LLM, backend, network), ship at
  least one **non-mocked round-trip** test (real input → real sanitize →
  real write → real recall) — it both proves the persistence logic AND,
  when the live system still fails, distinguishes a code bug from an
  environmental one (here the non-mocked round-trip PASSED, reframing
  the live "0 stashed" as environment, not code); (2) "wired up" /
  "registered" / "smoke-exits-0" are necessary-not-sufficient — dogfood
  the actual end-to-end before declaring done. The receipt beats the
  promise (§7).

- **Keyless-CI-faithful local runs need `ANTHROPIC_API_KEY=""`
  (EMPTY), not `env -u` (UNSET) — unset lets `load_dotenv` inject the
  real key from `~/.attune/anthropic.env` and the "keyless" run spends
  real money**: `env -u ANTHROPIC_API_KEY pytest tests/integration`
  ran 6 real SDK workflows (~$3, 8m46s) because dotenv only skips
  variables that EXIST; CI sets the secret to the empty string, which
  both blocks dotenv's injection AND makes `skipif(not
  os.environ.get(...))` gates fire. The empty-string run matched CI
  exactly (295 passed / 41 skipped, 5.8 s). Rule for any
  provider-key-gated suite: simulate CI-keyless with `KEY="" pytest`,
  never by unsetting.

- **A repo CI secret becoming VALID is a spend event — tests.yml passed
  `secrets.ANTHROPIC_API_KEY` to every push/PR × 12 matrix lanes and
  burned ~$1200 in ~6 hours the night the dead key was replaced with a
  live one (2026-06-10 02:52 UTC)**: tests that change behavior when a
  key is present (mismarked `HAS_API_KEY`-gated tests, real-SDK
  spawners of the #728 class, keyed Haiku-summary paths) made real API
  calls at CI scale the moment the secret went live — for weeks prior
  the key was invalid so the same workflow config burned nothing.
  Diagnostic chain that pinned it: (1) sum LOCAL telemetry
  (`~/.attune/telemetry/usage.jsonl` by day — showed only ~$126/month,
  so the burn wasn't local); (2) `grep -l ANTHROPIC_API_KEY
  .github/workflows/*.yml` + read HOW each uses it
  (`integration-tests.yml` sets it to `""` keyless-by-design;
  `tests.yml` passed the real secret); (3) `gh api .../actions/secrets`
  `updated_at` correlates the spend window. Rule: per-push/PR test
  workflows get `ANTHROPIC_API_KEY: ""` ALWAYS; the real secret
  belongs only to deliberately-scheduled, budget-capped jobs
  (`integration-auth.yml` with `ATTUNE_MAX_BUDGET_USD`). Pairs with
  the "keyless-CI-faithful local runs need EMPTY not unset" lesson —
  same empty-string discipline, opposite direction (CI side).

- **An interrupted/rejected compound Bash command may have PARTIALLY
  executed — re-establish actual git state before continuing, or you
  build on phantom assumptions**: hit on the 8.2.0 release (2026-06-10,
  the #737/#738 take-2). A multi-step command (commit prep → switch
  branch → edit → commit → push → PR) was user-interrupted; the
  rejection message implies nothing ran, but the branch existed, one
  commit existed, and the push had landed. Subsequent commands then
  compounded the misread: `git branch -D release/8.2.0` printed
  `(was bf88edb1)` — that SHA was the REAL release-prep commit, deleted
  with its branch; a later "release prep" commit was actually
  lessons-only (the bumps were already committed away); PR #737's
  squash merged WITHOUT the version bumps. The release-execute step-10
  gate (verify content IN the merge SHA before tagging) caught it
  pre-tag; recovery was cherry-picking the dangling commit. Durable
  rules: (1) after ANY interrupted/denied compound command, run
  `git log --oneline -3` + `git status --short` + `git ls-remote
  --heads origin <branch>` and reconcile EVERY step's expected effect
  before the next command; (2) `git branch -D` output `(was <sha>)`
  names a commit — before treating it as disposable, `git show --stat
  <sha>` to check for unmerged content; (3) verify merge-SHA content
  via the GitHub API (`gh api repos/<o>/<r>/contents/<file>?ref=<sha>`)
  — authoritative and immune to the local stale-object trap that
  muddied diagnosis; (4) when archaeology spirals, stop theorizing and
  fact-check trees directly (`git diff <ref-a> <ref-b> --stat` — an
  empty diff settles arguments instantly); (5) the SAME reconciliation
  applies to a rejected or interrupted **Edit** — `grep` the exact
  target line before re-applying, because the rejection message is not
  proof that nothing was written (an append that reported "NOT
  written" was already on disk, and re-applying duplicated the
  suffix); (6) prefer an `old_string` that is NOT a strict prefix of
  the intended `new_string`, so an accidental double-apply fails
  loudly instead of silently duplicating. Pairs with the "harness
  safety classifier blocks bundled-destructive scripts" lesson — same
  root cause family (compound commands + interruption), this one is
  the state-reconciliation half.

- **Teaching a scanner a new safe idiom can make its gate go BLIND —
  and a shrink-only allowlist ratchet then demands you delete the
  entries that were still load-bearing**: 2026-08-21, closing
  library-review class G1. The sweep-fix replaced ten `path.write_text()`
  atomic-publish sites with `tempfile.mkstemp` + `os.fdopen` + `replace`.
  The path-validation gate's scanner knew `.write_text` / `.write_bytes` /
  `.open(mode="w")` / `shutil.*` / `os.remove|unlink|rename|replace` — it
  did **not** know `os.fdopen` or `tempfile.mkstemp`. So six modules
  dropped OFF its offender list the moment they adopted the SAFER idiom,
  while still writing files exactly as before. Then
  `test_allowlist_entries_are_still_needed` (correctly, by its own logic)
  failed demanding those six ALLOWLIST entries be removed as stale —
  which would have made the gate assert something untrue: "these modules
  have no unvalidated file ops." The gate would have gone quiet on six
  real writers and looked *healthier* for it. **The tell is an allowlist
  ratchet firing "no longer needed" on modules you did not make safer —
  if a shrink-only list suddenly wants to shrink right after a
  refactor, suspect the SCANNER lost sight of the code, not that the
  code improved.** Fix: teach the scanner the new idiom in the SAME PR
  as the migration (`tempfile.{mkstemp,mkdtemp,NamedTemporaryFile,
  TemporaryFile}` + `os.fdopen`-for-write), with unit tests pinning both
  directions (write mode detected, read mode ignored). The widening then
  surfaced **two pre-existing writers the scanner had NEVER seen**
  (`authoring/fact_check/tutorial_static_check.py`,
  `ops/session_summary_cache.py`) — one of which interpolates an
  unvalidated id into a path; allowlisted with the traversal question
  recorded in the comment and chipped separately rather than waved
  through. Generalizes beyond this gate: **any AST scanner encodes a
  vocabulary of the idioms in use when it was written, so every idiom
  migration is a silent recall regression for every gate that scans for
  the old one.** Pairs with the existing "reviewing gate allowlist
  entries — the escape hatch is the actual attack surface" prior.
- **State the BASIS with every load-bearing claim — an inference in
  the grammar of a verified fact is the bug, and the verifying command
  is usually one line you already have**: ratified 2026-08-22 after a
  release session where four such claims each had a cheap probe
  available and unrun. **The rule: verify when the probe is cheap; when
  you do not, SAY SO IN THE CLAIM** ("verified by X" / "inferred, not
  checked"). An unmarked confident assertion is indistinguishable from
  a checked one, so the reader acts on both alike — and Patrick's
  framing is that guessing without flagging it is itself the issue, the
  same posture that produces sloppy code. **The diagnostic: name the
  property your check actually establishes, then ask whether it is the
  property your next action depends on.** `git rev-parse origin/main:src`
  matching a tag establishes something about a REMOTE-TRACKING REF; a
  scan that reads the WORKING TREE is unaffected by it (that checkout
  was 17 commits behind, and the review would have been filed as the
  release receipt). `git status --porcelain` clean means "matches its
  own HEAD", NOT "is current" — pair it with
  `git rev-list --count HEAD..origin/main`. "The runner spawns a
  subprocess" establishes a code path, not process independence — read
  `ps -o ppid=,command=`. Which code will execute is answered by the
  filesystem that will be read (`pyproject.toml` on disk, an imported
  `__version__`, `__file__`), never by a ref. Relaying a third party's
  approval ("X approved this") is the same error in social form: the
  recipient cannot verify it through you. **Why this sits in core
  rather than the tail: recall cannot save you from it.** The corpus
  already held adjacent lessons and none fired, because the failure
  happens when a CONCLUSION IS FORMED — earlier than any tool call that
  would trigger retrieval — and on that session the SessionStart hook
  had already printed `STALE SOURCE … 17 commits behind` and it was
  read as ambient noise. Verification also keeps OLD lessons honest:
  the same session found a core lesson's premise ("Windows lanes are
  NOT required") had gone stale, so acting on it unverified would have
  added pointless friction.
- **Calibrate a new gate rule on the real tree BEFORE writing it, and
  report the precision — a first-draft rule is routinely 60-70% precise,
  and the discriminator that fixes it must be pinned as a fixture**:
  2026-08-21, across four library-review gates. Every rule was probed as
  a throwaway script first, and every one moved after seeing its hits.
  G2's naive form ("any `int()`/`float()` after a per-record skip
  guard") found 6 sites, 4 real: the lookalikes were
  `int(elapsed * 1000)` on floats and `int(m.group(1))` on a `(\d+)`
  capture, neither of which can raise. Requiring the coerced value to
  come from a parsed record (a `.get(...)` or a subscript) took it to
  4/4/0. I-4's naive form found 12, narrowed to 3. H1's FIRST form was
  worse than imprecise — it was **wrong**: keyed on literal `host=`/
  `port=` arguments, it found one site and MISSED the live user-facing
  bug entirely, because `redis.Redis(socket_connect_timeout=2)` has no
  endpoint argument at all and defaults to localhost:6379 implicitly.
  A rule that finds one already-defensible hit is evidence the QUESTION
  is wrong, not that the tree is clean. Three durable practices: (1)
  **probe first, in a scratch script, and look at every hit by hand** —
  the triage is where the rule gets designed; (2) **state the precision
  in the PR** (hits / real / false positives) so a reviewer can judge
  whether the gate will cry wolf, since a noisy gate gets allowlisted
  into uselessness; (3) **pin each false positive you eliminated as a
  passing test fixture** — the discriminator is the most fragile part of
  the rule and the easiest thing for a later "simplification" to drop.
  Matches the class register's own pipeline (confirm -> mechanize ->
  CALIBRATE -> gate -> sweep-fix -> close) and its standing line that
  "uncalibrated rules do not gate anything".
- **Progressive tier escalation is dead spend for any agent whose gate value is a fail-closed ratchet — check monotonicity before paying for CAPABLE/PREMIUM retries**: 2026-08-23, triaging bug-predict's six `security_agent.py` hypotheses (PR #2204). The reported bug was narrow: the `-1` did-not-run sentinel made `ReleaseAgent.process()` escalate CHEAP → CAPABLE → PREMIUM, re-running bandit (and in real mode the LLM) three times for an outcome that could not change. Fixed with a `retryable: False` signal on the degrade dicts. But reading the ratchet showed the sentinel case is just the visible corner of a larger property: bandit's counts are authoritative and the LLM may only RAISE them, so `critical_issues` is monotone non-decreasing across tiers and a CHEAP-tier failure can never become a CAPABLE/PREMIUM success — escalation helps this agent in NO case. **Diagnostic: before trusting an escalate-on-failure loop, ask what a stronger model could change about the SUCCESS predicate specifically.** If the predicate reads a tool-derived value the escalation cannot move (deterministic tool, fail-closed ratchet, absent-tool sentinel), every extra tier is pure cost and should short-circuit. Left the broader case as a chair-callable follow-up in the PR body; the triage precision was 3 real / 2 already-correct-pinned / 2 false positive across seven hypotheses.
- **A required check failing on a PR that cannot possibly affect it means
  MAIN is red — check main's last run BEFORE debugging the PR; and
  `strict: false` does NOT mean "no rebase needed"**: 2026-08-21. A
  docs-only PR (#2153, one markdown file in an archived spec) failed the
  required `coverage` check. Nothing in that diff can move coverage, and
  that impossibility is the diagnostic: one `gh run list --branch main
  --limit 6 --workflow=tests.yml` showed main itself failing since
  `ced39888a`. The PR was inheriting main's breakage, so debugging the
  PR would have found nothing. **Order of operations: a check that the
  diff cannot influence is a signal about the BASE, not the branch.**
  Root cause was `scripts/check_badge_freshness.py` (it runs inside the
  coverage job, so its failure is reported as "coverage"): the README
  carries a manually-maintained round FLOOR for the tests badge and
  `MARGIN = 5000`, so it trips once actual exceeds floor + 5,000. The
  floor had drifted ~4,970 stale on its own; four gate PRs merged that
  afternoon added the tests that crossed it. Fix was a one-line bump to
  the next round number — which the README's own maintenance comment had
  already named ("bump once the suite clears 25,000"), so **read the
  maintenance comment before inventing a value**.
  **The correction worth carrying:** I first reasoned the follow-up PR
  needed no rebase, because branch protection has `strict: false` and
  the two PRs touched different files. Wrong in effect — `strict`
  governs merge ELIGIBILITY (may a behind-branch merge), not whether a
  check PASSES. The follow-up branched before the fix, so its own tree
  still held the stale floor and `coverage` genuinely failed on its own
  content. It needed the rebase regardless of strictness. **Ask "does
  this branch's TREE contain the fix?", never "is this branch allowed to
  merge behind?"** (rebase re-signed cleanly; verify `%G?` before
  force-pushing, per the existing GPG lesson.)
  Rider on the same episode: merging four PRs inside ~15 minutes made
  `cancel-in-progress` kill an earlier main run's `test (ubuntu-latest,
  3.13)`, and cancelled-but-required also blocks — the known
  rapid-PUSH lesson applies identically to rapid MERGES. No code fixes
  that half; it clears on the next full main run, so verify the two
  previously-red JOB conclusions directly rather than trusting the
  run-level green (a cancelled-but-required check that stays cancelled
  looks the same from a distance and needs `gh run rerun`).

- **A transport migration silently converts every emptiness-asserting
  test into a vacuous one — and the suite CANNOT report it, because the
  failure mode satisfies the assertion**: 2026-08-22, the telemetry
  scan-then-`get()` -> `MGET` migration (#2162). Tests that stubbed only
  `client.get` then handed the listing a bare `Mock` for `mget`, which is
  not iterable; the `TypeError` was swallowed by each listing's
  function-wide `except Exception` and the call returned `[]` / `0` /
  `None`. Any `assert x == []` therefore passed **for the wrong reason**:
  the payload the test set up was never read (`get()` call count 0) and
  the logic under test never ran. The cost, measured: disabling
  `get_pending_approvals`' "only pending" status filter ENTIRELY left the
  whole telemetry suite green (646 passed) — that filter's only guard was
  one of the vacuous tests. **The selection effect is the point, and it
  was perfectly clean across the 13 tests touching those listings:
  POSITIVE-assertion tests (`assert len(x) == 1`) go red instantly and
  get fixed during the migration — 3/3 were wired; emptiness-asserting
  tests are satisfied by the very failure they should catch — 0/10 were.**
  Nobody was careless; the suite emitted no signal, so no amount of care
  would have caught it. Generalizes to ANY mock-stubbed transport swap
  (sync->async, single->batch, REST->GraphQL, one client method ->
  another) wherever the caller has a broad `except` that degrades to an
  empty result. **Diagnostics**: (1) mutation-test the logic the tests
  claim to guard — if breaking a filter leaves the suite green, its
  coverage is fiction; (2) count `get()` calls — a stubbed payload that
  is never read is the tell; (3) grep for tests that stub the OLD method
  and assert emptiness. **Fix**: define the new method in terms of the
  old (`client.mget.side_effect = lambda ks: [client.get(k) for k in ks]`
  — literally the Redis contract), so each test keeps configuring
  payloads the way it already does. **Gate it**: a static check that any
  test feeding a non-empty scan must also serve the batched read
  (`tests/unit/gates/test_listing_mock_transport_gate.py`), since this
  class is invisible by construction and vigilance cannot cover it.

- **A "do X when Y merges" handoff held by TWO sessions puts two agents on ONE branch in ONE worktree — the second session amends and pushes your commit under you, and your next `git diff` shows edits you never made**: 2026-08-23, the #2204 → #2205 follow-up. This session was told "open the follow-up PR when #2204 merges" and parked a commit on `fix/security-agent-failure-not-retryable`; the sibling session that shepherded #2204 to merge held the same instruction implicitly (it owned the merge). When #2204 landed, BOTH acted: I cherry-picked onto a fresh branch off the squash and pushed `142dea663`; within minutes the sibling amended that commit in MY worktree (`1f5103fc8`, adapting #2204's late-added "LLM cannot steer escalation" test to the new semantics), pushed, opened #2205, ran the D11 lane, and appended the ledger row. Tells that something else was writing: a serial run that flipped from `1 failed` to `210 passed` with no edit of mine; a `git diff origin/main --stat` that grew from 18 to 34 test lines while `git status` stayed clean; `git reflog` showing a `commit (amend)` I did not run. It resolved coherently only by luck — the edits were compatible. **Rules:** (1) before acting on a "when Y merges" trigger, `gh pr list --head <branch>` AND `git reflog -3` — if a PR exists or the reflog shows a foreign amend, the other session owns it; verify its claims (the PR body's "ledger row appended" was true only after a `git fetch`, not in the local diff) and STOP; (2) a chip/handoff that says "open the PR when #N merges" should name ONE owner — the merging session is the natural one, because it already knows the moment; the waiting session should hold the branch but not the trigger; (3) `git reflog` is the cheap probe for "did someone else touch this checkout" — `git status` clean says nothing about it. Pairs with the existing "one branch per agent" contract rule (violated here by construction, not by carelessness) and the "interrupted compound command — re-establish actual git state" lesson.

- **A "full local run" scoped to tests/unit is NOT what CI runs — the
  suite also collects top-level tests/models, tests/security,
  tests/workflows, tests/memory, tests/agent_factory — and new-code
  ratchets fire on ANY new matching site, so run tests/unit/gates
  before every push that adds an except/write/idiom**: 2026-08-24,
  twice in one session. The #2242 hardening PR went red on ubuntu for
  tests/models/test_fallback.py + tests/security/
  test_audit_logger_extended.py (both outside tests/unit, both pinning
  behavior the diff changed), and the #2237 PR went red solely on the
  broad-except ratchet (tests/unit/gates) for a new intentional catch.
  Cheap receipts, in order: (1) `pytest tests` (the WHOLE tree — ~4min
  with xdist) before push, not per-suite spot runs; (2) any new
  `except Exception` needs its ratchet baseline entry raised WITH a
  reason comment in the SAME commit; (3) grep the top-level test dirs
  for the symbol you changed (`grep -rn get_delay_ms tests/` found the
  extra pinning tests instantly — after CI already had).

- **An external fork PR that adds a workflow, executes the author's own
  tool in CI, and asks the maintainer to mint a new secret is a
  supply-chain pattern — review the AUTHOR and the trust flow before the
  diff's mechanics**: 2026-08-25, PR #2265 ("ci: add repository-signed
  control proof") from `sulmusic2-star` (account created 2025-10, 0
  followers, unknown to the project). The diff was mechanically CLEAN —
  SHA-pinned actions, `timeout-minutes`, `concurrency`,
  `permissions: contents: read`, no key refs; dropped into the tree it
  passed all 340 workflow-policy gate tests — and its "failing tests"
  were fork-PR artifacts (labeler's read-only `GITHUB_TOKEN`, Vercel
  fork-deploy authorization), so a greenness-focused review would have
  found nothing. The actual risk lived in the trust flow: the workflow
  checked out the AUTHOR'S OWN repo (`sulmusic2-star/agent-vigil`), ran
  `npm ci && npm run build` (full unaudited dependency tree), executed
  its CLI in our Actions environment, and the PR body pre-scripted the
  maintainer into creating an Ed25519 private key stored as a repo
  secret — written to disk in the same job where the foreign tool runs.
  Exfiltrating that key would compromise the very trust anchor the
  feature claims to provide, and the feature was theater anyway
  (re-signing exit codes of checks CI already runs, with a key held in
  the same repo, referencing a nonexistent `attune-governance` signer
  identity). **Review order for external `.github/` PRs: (1) who is the
  author and whose code does the workflow fetch/execute; (2) what
  secrets does it ask to exist and which code can read them; (3) only
  then the diff's mechanics.** A visibly agent-operated repo is a
  target for exactly this shape — polished, gate-compliant, body
  pre-scripting the next steps. Closed without merging; `.github/`
  diffs are out of class for every auto-merge lane (D8 carve-out),
  which held correctly.
