From e512dfed92acc7c6240c4d711cf46e4c1cfa29d8 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 06:56:15 +0000 Subject: [PATCH 1/2] Squash of AdaWorldAPI fork work (105 commits, PRs #1-#9) onto sonos/tract main Our fork-only changes since the last shared commit 7ef965c, replayed as one commit on upstream 7e8be2f: ndarray SIMD/GEMM/AMX-BF16 pilots in linalg, the CI lint gate, and the .claude knowledge files. Lock files stay deleted, and both deny.toml files keep upstream's new skip entries plus our allowed git sources. Full history is on backup/main-pre-upstream-sync-2026-10-29. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v --- .claude/ATT/DE/README.md | 91 + .claude/ATT/DE/agent-coordination-mcp-spec.md | 765 ++ .claude/ATT/DE/anti-skim-agent-spec.md | 1102 +++ .../ATT/DE/autoattended-orchestrator-spec.md | 1257 ++++ .claude/ATT/README.md | 87 + .claude/ATT/agent-coordination-mcp-spec.md | 743 ++ .claude/ATT/anti-skim-agent-spec.md | 1077 +++ .claude/ATT/autoattended-orchestrator-spec.md | 1231 ++++ .claude/EN/CLAUDE-AGENT-PATTERN.md | 121 + .claude/EN/README.md | 71 + .claude/EN/agents/README.md | 68 + .claude/EN/agents/baton-handoff-auditor.md | 114 + .claude/EN/agents/brutally-honest-tester.md | 139 + .claude/EN/agents/meta-agent.md | 191 + .claude/EN/agents/preflight-drift-auditor.md | 114 + .claude/EN/agents/worker-template.md | 218 + .claude/EN/knowledge/a2a-workarounds.md | 288 + .../autoattended-multi-agent-pattern.md | 316 + .claude/settings.json | 166 + .github/workflows/binaries.yml | 2 +- .github/workflows/crates.yml | 2 + .github/workflows/lint.yml | 55 + .gitignore | 6 + Cargo.lock | 6490 ----------------- Cargo.toml | 10 +- README.md | 2 +- api/rs/deny.toml | 7 + cli/deny.toml | 7 + cli/src/hwbench.rs | 4 + linalg/Cargo.toml | 19 + linalg/benches/amx_bf16_gap_decomposition.rs | 289 + linalg/benches/leaky_relu.rs | 28 +- linalg/benches/ndarray_bf16_gemm.rs | 53 + linalg/benches/ndarray_gemm.rs | 50 + linalg/src/arm32/mod.rs | 10 +- linalg/src/frame/mmm/macros.rs | 6 +- linalg/src/frame/mmm/tests/mod.rs | 15 + linalg/src/generic/reduce.rs | 6 +- linalg/src/x86_64/act.rs | 36 + linalg/src/x86_64/mmm.rs | 68 +- linalg/src/x86_64/mod.rs | 5 + linalg/src/x86_64/ndarray_amx_native_pack.rs | 427 ++ linalg/src/x86_64/ndarray_bf16_gemm.rs | 357 + linalg/src/x86_64/ndarray_bf16_native_gemm.rs | 302 + linalg/src/x86_64/ndarray_gemm.rs | 228 + rust-toolchain.toml | 6 +- tools/asset-mirror/Cargo.lock | 678 -- transformers/src/ops/gdn_recurrent.rs | 4 + 48 files changed, 10137 insertions(+), 7194 deletions(-) create mode 100644 .claude/ATT/DE/README.md create mode 100644 .claude/ATT/DE/agent-coordination-mcp-spec.md create mode 100644 .claude/ATT/DE/anti-skim-agent-spec.md create mode 100644 .claude/ATT/DE/autoattended-orchestrator-spec.md create mode 100644 .claude/ATT/README.md create mode 100644 .claude/ATT/agent-coordination-mcp-spec.md create mode 100644 .claude/ATT/anti-skim-agent-spec.md create mode 100644 .claude/ATT/autoattended-orchestrator-spec.md create mode 100644 .claude/EN/CLAUDE-AGENT-PATTERN.md create mode 100644 .claude/EN/README.md create mode 100644 .claude/EN/agents/README.md create mode 100644 .claude/EN/agents/baton-handoff-auditor.md create mode 100644 .claude/EN/agents/brutally-honest-tester.md create mode 100644 .claude/EN/agents/meta-agent.md create mode 100644 .claude/EN/agents/preflight-drift-auditor.md create mode 100644 .claude/EN/agents/worker-template.md create mode 100644 .claude/EN/knowledge/a2a-workarounds.md create mode 100644 .claude/EN/knowledge/autoattended-multi-agent-pattern.md create mode 100644 .claude/settings.json create mode 100644 .github/workflows/lint.yml delete mode 100644 Cargo.lock create mode 100644 linalg/benches/amx_bf16_gap_decomposition.rs create mode 100644 linalg/benches/ndarray_bf16_gemm.rs create mode 100644 linalg/benches/ndarray_gemm.rs create mode 100644 linalg/src/x86_64/ndarray_amx_native_pack.rs create mode 100644 linalg/src/x86_64/ndarray_bf16_gemm.rs create mode 100644 linalg/src/x86_64/ndarray_bf16_native_gemm.rs create mode 100644 linalg/src/x86_64/ndarray_gemm.rs delete mode 100644 tools/asset-mirror/Cargo.lock diff --git a/.claude/ATT/DE/README.md b/.claude/ATT/DE/README.md new file mode 100644 index 0000000000..2065f30a59 --- /dev/null +++ b/.claude/ATT/DE/README.md @@ -0,0 +1,91 @@ +> **Sprache:** Deutsch · siehe `../README.md` für die englische Quellfassung. + +# `.claude/ATT/` — NLSpecs unseres Kits im Attractor-Stil + +> **Status:** DRAFT · **Version:** 0.1.0 · **Stand:** 2026-05-17 +> +> **Was das ist.** Die Ideen unseres `.claude/EN/`-Kits, neu formuliert +> im [strongdm/attractor](https://github.com/strongdm/attractor) NLSpec-Format, +> damit ein Coding-Agent sie direkt umsetzen kann. +> [NLSpec](https://github.com/strongdm/attractor#terminology) = +> "human-readable spec intended to be directly usable by coding agents +> to implement/validate behavior." +> +> **Was das NICHT ist.** Kein Ersatz für `.claude/EN/`. Die beiden sind +> komplementär: `.claude/EN/` ist der Cheat-Sheet für Operatoren +> (Prosa, in-session-Use); `.claude/ATT/` ist die Engineering-Spec +> (NLSpec, build-time-Use). + +## Die drei Specs + +| Datei | Spiegelt Attractors | Ergänzt unsere Innovation | +|---|---|---| +| [`autoattended-orchestrator-spec.md`](./autoattended-orchestrator-spec.md) | [`attractor-spec.md`](https://github.com/strongdm/attractor/blob/main/attractor-spec.md) (DOT-Graph-Pipeline-Runner) | Wave-basierter 12-Worker-Fan-out; 4-savant Verdict-Gates (PP-13/14/15/16); 6 Worker-Iron-Rules; Sprint-Token-Budget (~300k/Wave); Multi-File-Board-Pattern mit single-mutable-file-Invariante | +| [`anti-skim-agent-spec.md`](./anti-skim-agent-spec.md) | [`coding-agent-loop-spec.md`](https://github.com/strongdm/attractor/blob/main/coding-agent-loop-spec.md) (die Per-LLM-Call-Agent-Library) | Reading-Depth-Ladder (grep→read→thorough→fan-out); Lie-Detector LD-1..5 (Sentinel-Token / Proof-of-Read SHA / 3-Section-Challenge / Negative-Knowledge-Test / Line-Range-Quote); typisierte Stuck-Protocol-Blocker (AMBIGUITY / MISSING_INVARIANT / SPEC_SOURCE_MISMATCH / BEHAVIOUR_QUESTION / EXTERNAL_DEPENDENCY) | +| [`agent-coordination-mcp-spec.md`](./agent-coordination-mcp-spec.md) | [`unified-llm-spec.md`](https://github.com/strongdm/attractor/blob/main/unified-llm-spec.md) (Provider-agnostisches LLM-SDK) | Drei Koordinations-Layer (Role-Teleport / File-Blackboard / Branch-Pub-Sub) so wie sie ein nativer A2A-MCP-Server exponieren sollte; strukturiertes Handover-Schema; Decision-Matrix dafür, wann welcher Layer passt | + +## Was wir von Attractor übernommen haben (die fünf Wins) + +Das sind konkrete Dinge, die Attractors Specs festnageln und die unseren +Prosa-Docs in `.claude/EN/` gefehlt haben — jetzt eingearbeitet: + +| # | Attractor-Konzept | Landet in unserer NLSpec | Übernahme | +|---|---|---|---| +| 1 | Typisiertes `status.json`-Schema + 5-Wert-`StageStatus`-Enum (Attractor Appendix C: `{outcome, preferred_label, suggested_next_ids, context_updates, notes}`) | [`autoattended-orchestrator-spec.md` §9](./autoattended-orchestrator-spec.md#9-status-file-schema) | Übernommen **mit verpflichtendem `auto_status=false`** (siehe "Wo es kollidiert" unten). | +| 2 | DOT-Graph-DSL für den Workflow + Lint-Regeln (Attractor §2 Grammatik + §7 Validierung: `reachability`, `start_no_incoming`, `goal_gate_has_retry`, `condition_syntax`) | [`autoattended-orchestrator-spec.md` §6](./autoattended-orchestrator-spec.md#6-sprint-plan-format) (DOT + YAML-Mirror) + [§7](./autoattended-orchestrator-spec.md#7-validation-rules) (WAVE-001..WAVE-017 mit ERROR/WARNING-Severity) | Übernommen mit drei Wave-spezifischen Zusätzen: `unique-write`, `declared-shared`, `auto-status-false`. | +| 3 | Context-Fidelity-Ladder (Attractor §5.4: `full` / `truncate` / `compact` / `summary:low/medium/high` mit Token-Budgets + Vorrang edge > node > graph > default) | [`autoattended-orchestrator-spec.md` §11](./autoattended-orchestrator-spec.md#11-context-fidelity) | Übernommen mit einer Verschärfung: `fidelity=truncate` entbindet einen Worker NICHT von der §3.3-Reading-Depth-Ladder aus `anti-skim-agent-spec.md`. | +| 4 | In-Loop-Tool-Call-Loop-Detection (Attractor coding-agent §2.10: letzte 10 Calls scannen auf wiederholende Patterns der Länge 1/2/3 → Steering-Warning einspielen) | [`anti-skim-agent-spec.md` §6](./anti-skim-agent-spec.md#6-tool-call-loop-detection) + AP9 in [§9](./anti-skim-agent-spec.md#9-anti-pattern-catalog-ap1ap9) | Wortgleich übernommen; auf System-Level-Invariante erhoben. PP-13s Post-hoc-AP9 fängt, was der In-Loop-Detector verpasst. | +| 5 | Definition-of-Done-Checklisten + Cross-Provider-Parity-Matrix pro Spec (Konformanz-Tabellen im Stil von Attractor §10) | Jede NLSpec endet mit `§ Definition von Fertig` + `§ Cross-{Language,Provider}-Parity-Matrix` | Als strukturelles Template übernommen. Der 26-Repo-Rollout ist jetzt maschinell prüfbar. | + +## Warum dieses Format + +Drei Eigenschaften, die wir aus Attractors NLSpec-Format bekommen und +die unseren Prosa-Docs in `.claude/EN/` fehlen: + +1. **Definition-von-Fertig-Checklisten** am Ende jeder Spec — gibt uns + einen Konformanz-Test für "ist diese Implementierung fertig?" +2. **Cross-Provider-Parity-Matrix**-Tabellen — geben uns ein Per-Language / + Per-Runtime-Mapping, sodass dieselbe NLSpec in Rust, Python, TypeScript, + Go landen kann ohne Drei-Wege-Drift. +3. **Validierungs-Regeln mit ERROR/WARNING/INFO-Severity** — macht aus + Linting einen deterministischen Prozess, kein Bauchgefühl. + +## Wo es mit Attractors Haltung kollidiert (und warum wir bei unserer Position bleiben) + +| Attractors Default | Unsere Position | Warum | +|---|---|---| +| `auto_status=true` (§4.5 + Appendix C: "wenn der Handler keinen Status schreibt, auto-generiere SUCCESS") | `auto_status=false` ist Pflicht | Genau das ist der Silent-Skim-Failure-Mode, gegen den unser Lie-Detector LD-1..5 existiert. Fehlender Status = FAIL, nicht SUCCESS. | +| Single-Threaded Graph-Traversal (§3.8: "Nur ein Node läuft zur Zeit") | Wave-Fan-out ist die Baseline, kein Spezial-Fall `parallel`-Node | Unser Token-Budget ist pro Wave (~300k für 12 Workers), nicht pro Node. Waves als einen riesigen `parallel`-Node zu modellieren ist syntaktisch hässlich. | +| Engine-Level-Retries mit Exponential-Backoff (§3.5-3.6) | Stuck-Agents filen typisierte Blocker in REQUESTS-FROM-AGENTS.md; Meta-Agent entscheidet | Retries sollen kontextuell und inspiziert sein, nicht silent und uniform. | +| `wait.human` als Default; LLM-Gates sind Test-Fixtures (§6.4: `AutoApproveInterviewer` "Used for automated testing") | LLM-Meta-Agent ist das Production-Gate | Unser Meta-Agent macht Plan-Review + P0/P1-PR-Review + Inbox-Drain als Production-Rolle, nicht als Test-Fixture. | +| Subagent-Tiefe = 1 Default (coding-agent §7.3) | Wave-Fan-out fährt routinemäßig 12 Workers aus einem Orchestrator | Wir überschreiben die Tiefe auf ≥2 — Workers sollen Sub-Investigations spawnen dürfen. | + +## Konformanz + +Ein Repo, das diese NLSpecs einbindet, ist konform, wenn es die +"Definition von Fertig"-Checkliste am Ende jeder Spec erfüllt. Die +lance-graph-Implementierung (siehe [`AdaWorldAPI/lance-graph` `.claude/agents/`](https://github.com/AdaWorldAPI/lance-graph/tree/main/.claude/agents)) +ist die reifste Referenz; die WoA + woa-rs-Implementierungen sind die +Wave-basierte Referenz. + +## Provenienz + +- Quell-Kit: `.claude/EN/` in diesem Repo (siehe [`.claude/EN/README.md`](../../EN/README.md)) +- Format-Inspiration: [strongdm/attractor](https://github.com/strongdm/attractor) (NLSpecs im MIT-Stil) +- Distillation-Handover: [`META/HANDOVER-AGENTKIT-CONSOLIDATION-2026-05-17.md`](https://github.com/AdaWorldAPI/WoA/blob/main/META%2FHANDOVER-AGENTKIT-CONSOLIDATION-2026-05-17.md) (in `AdaWorldAPI/WoA`) +- Schwester-Handover (Rust-Hardening-Pass): [`META/HANDOVER-WOA-RS-AGENT-HARDENING-2026-05-17.md`](https://github.com/AdaWorldAPI/WoA/blob/main/META%2FHANDOVER-WOA-RS-AGENT-HARDENING-2026-05-17.md) + +## Build + +Um diese NLSpecs in eine lauffähige Implementierung zu verwandeln, +gib einem modernen Coding-Agent (Claude Code, Codex, OpenCode, Amp, +Cursor, ...) diesen Prompt: + +``` +codeagent> Implement the Autoattended Orchestrator + Anti-Skim Agent + + Agent Coordination MCP as described by + https://github.com/AdaWorldAPI//tree/main/.claude/ATT + together with strongdm/attractor as the substrate. +``` + +*Ende der Datei README.md.* diff --git a/.claude/ATT/DE/agent-coordination-mcp-spec.md b/.claude/ATT/DE/agent-coordination-mcp-spec.md new file mode 100644 index 0000000000..e237613f71 --- /dev/null +++ b/.claude/ATT/DE/agent-coordination-mcp-spec.md @@ -0,0 +1,765 @@ +# Agent-Coordination-MCP-Spezifikation + +> **Sprache:** Deutsch · siehe `../agent-coordination-mcp-spec.md` für die englische Quellfassung. +> +> **Status:** DRAFT · **Version:** 0.1.0 · **Stand:** 2026-05-17 +> **Format:** NLSpec (nach [strongdm/attractor](https://github.com/strongdm/attractor)) +> **Substrat:** Designed als Schwester zu `unified-llm-spec.md` — +> ein *Koordinations*-SDK so wie Attractors Spec ein *LLM*-SDK ist. + +--- + +## §1 Überblick + +### §1.1 Zweck + +Claude Code spawned jeden `Agent()` als isolierten Subprocess. Jeder +Subprocess bekommt ein frisches Context-Window, kann nicht andere +Agents' Tools aufrufen, kann nicht andere Agents' In-Flight-State +lesen, und returned einen einzelnen Blob an seinen Parent. Das +bricht drei Patterns, die in früheren Claude- / Gemini-Setups +funktioniert haben: + +1. **Role-Teleportation** — Persona-Switch in-context ohne Verlust. +2. **Mid-Flight-Coordination** — Agent A sagt Agent B, was er gefunden hat. +3. **Cross-Session-Handoff** — Session A's Arbeit feedet Session B in Echtzeit. + +Diese Spec definiert das **Agent-Coordination-MCP**: das Drei-Layer- +Koordinations-Modell, das nötig ist, um diese Patterns wiederherzustellen, +und die File- / Git- / MCP-Primitives, die es implementieren. + +### §1.2 Zwei Operating-Modes + +| Mode | Substrat | Wann nutzen | +|---|---|---| +| **Native** | Ein zukünftiger MCP-Server der `post_entry` / `read_entries` / `subscribe`-Endpoints exponiert (§5). | Wenn verfügbar — bevorzugt für Low-Latency. | +| **Workaround** | `tee -a`-Markdown-Files in Git + GitHub-PR-Webhooks via `mcp__github__subscribe_pr_activity`. | Heute, in jeder Claude-Code-Session. Hier als kanonischer Fallback definiert. | + +Beide Modes implementieren das gleiche Drei-Layer-Modell (§3) mit +den gleichen Schemata (§6). Der Native-Server ist eine dünne +serde-Schicht über dem Wire-Format des Workaround. + +### §1.3 Das universelle Wire-Format: `tee -a [bug/proposal]` + +Der gleiche Markdown-Blob dient **drei Zwecken gleichzeitig**: + +| Zweck | Leser | Mutability | +|---|---|---| +| **MCP-Orchestrations-Message** | der Orchestrator (und jede subscribed Session via `subscribe_pr_activity`) | append-only | +| **A2A-Wire-Format** | Sibling-Agents (Worker → Worker, Worker → Meta, Savant → Savant) | append-only | +| **Log-Format (Audit + Replay)** | jede zukünftige Session, die Git-History liest | immutable once committed | + +Ein Write, drei Uses. Das ist by design: derselbe Code, der einen +Eintrag ans File-Blackboard (Layer 1) postet, emittiert AUCH die +Log-Zeile, wird AUCH zu einer cross-session-readable Orchestrations- +Message in dem Moment, in dem es auf dem Branch eines +Coordination-PRs landet. + +Die Form des Blobs ist **immer ein typisierter Envelope + ein Body**: + +``` +## YYYY-MM-DDTHH:MM — KIND[severity]: one-line headline (author, branch) + +**Author:** +**Kind:** BUG | PROPOSAL | HANDOVER | FINDING | DECISION | RFC | STATUS +**Severity:** P0 | P1 | INFO (optional; default INFO) +**Refs:** | | +**Proof-of-read:** +- file= sha256=<...> lines= depth= +- file= sha256=<...> lines= depth= + +--- + + +``` + +Der `tee -a`-Envelope ist das Wire; der Body ist die Payload. Router +(Orchestrator, Meta-Agent, MCP-Server) dispatchen nach `Kind` und +`Severity`; Leser greppen nach `Author` + `Refs`; Auditors replayen +in der immutable Git-Log-Order. + +### §1.4 Tabellen vor Prosa (NLSpec-Disziplin) + +Implementierungen SOLLTEN tabellarischen Content vor Prosa bevorzugen +für jeden Katalog (Anti-Patterns, Validierungs-Regeln, +Verdict-Vokabulare, Konfigurations-Defaults, Parity-Matrizen). +Begründung: + +| Eigenschaft | Tabelle | Prosa | +|---|---|---| +| Maschinell parsbar | ja | nein | +| Erzwingt Konsistenz (alle Zeilen haben die gleichen Spalten) | ja | nein | +| Komprimierbar (jede Zeile ist ein Fakt) | ja | nein | +| Diffed sauber in PRs | ja | partial | +| Widersteht hedging Language | ja (Cell-Breite cappt Qualifier) | nein | +| Geeignet für englische Narrative | nein | ja | + +Nutze Prosa für: Section-Overviews, Rationale-Absätze, Conflict- +Erklärungen. Nutze Tabellen für: alles andere. + +### §1.5 Was diese Spec NICHT ist + +- Keine Workflow-Engine. Wave- / Sprint-Struktur ist in + [`autoattended-orchestrator-spec.md`](./autoattended-orchestrator-spec.md). +- Kein Per-Agent-Loop. Worker-Verhalten ist in + [`anti-skim-agent-spec.md`](./anti-skim-agent-spec.md). +- Nicht Provider-gekoppelt. Beide Modes sind LLM-Provider-agnostisch. + +--- + +## §2 Terminologie + +| Begriff | Definition | +|---|---| +| **Layer-0 Teleportation** | Der Main-Thread lädt eine Agent-Card und „trägt ihren Hut" in-context. Zero Transport. | +| **Layer-1 File-Blackboard** | Eine per-session append-only Markdown-File (`AGENT_LOG.md`), die Workers beim Eintritt lesen und beim Austritt anhängen. | +| **Layer-2 Branch-Pub/Sub** | Der Push-Activity-Feed eines Draft-PRs, genutzt als Real-Time-Cross-Session-Message-Bus. | +| **Handover** | Eine strukturierte Markdown-File (`.claude/handovers/YYYY-MM-DD-HHMM-*.md`), geschrieben am Session-Ende für die nächste Session, beim Start zu lesen. | +| **Blackboard-Entry** | Ein Append an `AGENT_LOG.md`; Schema in §6.1. | +| **Cross-Session-Broadcast** | Eine committed-and-protected File getrennt von `AGENT_LOG.md`; der durable Mirror wichtiger Einträge. | +| **Coordination-PR** | Ein Draft-PR (nie gemerged), dessen Push-Activity der Layer-2-Transport ist. | + +--- + +## §3 Die drei Koordinations-Layer + +### §3.1 Layer-0: Teleportation + +**Wann:** die Aufgabe braucht den VOLLEN Conversation-Context (keine +Summary) und der Role-Switch ist temporär. + +**Wie:** auf dem Main-Thread (NICHT via `Agent()`): + +``` +1. Read .claude/EN/agents/.md +2. Lade ihre Tier-1-Knowledge-Docs (per der Card §Inputs) +3. Mach die Arbeit mit vollem Session-Context intakt +4. Wenn fertig, optional Switch: lies eine andere Role-Card +5. Review die Arbeit aus der Perspektive der anderen Rolle +6. Zurück zur Original-Rolle — nichts verloren +``` + +**Cost:** Zero Transport, Single-Threaded, Context-Budget füllt +sich, während Role-Cards gelesen werden. Keine Isolation: ein +Fehler gemacht „als Rolle-X" ist für ein nachfolgendes „als Rolle-Y"- +Review sichtbar (Feature, kein Bug). + +**Nicht für:** mechanische Grind-Arbeit (nutze Layer-1 + `Agent()`) +oder wirklich independent parallele Arbeit (nutze `Agent()` direkt). + +### §3.2 Layer-1: File-Blackboard + +**Ersetzt:** Mid-Flight-Coordination (partiell). +**Pfad:** `.claude/board/AGENT_LOG.md` (oder `META/AGENT_LOG.md` +per Repo-Konvention). +**Permission:** vor-erlaubt in `.claude/settings.json` als +`Bash(tee -a *)` und `Bash(tee -a **)`. + +**Setup (Native-Mode):** + +Ein zukünftiger MCP-Server exponiert die File als strukturierten +Stream (`post_entry`, `read_entries`, `subscribe`). Bis das +existiert, ist der Fallback die Markdown-File direkt. + +**Agent-Prompt-Template (Workaround-Mode):** + +``` +Bevor du anfängst zu arbeiten, lies .claude/board/AGENT_LOG.md +um zu sehen, was andere Agents schon shipped oder gefunden haben. + +Nach dem Commit, häng deinen Eintrag an: + +tee -a .claude/board/AGENT_LOG.md > /dev/null <<'EOF' + +## YYYY-MM-DDTHH:MM — description (model, branch) + +**Agent-id:** +**Bundle:** +**Sentinel:** +**Commit:** `abc1234` +**Tests:** N pass (M new) +**Outcome:** One-line summary. +**Proof-of-read:** +- file= sha256=<...> lines= depth= +EOF +``` + +**Limitierungen:** + +- Nicht Real-Time: Agent B sieht nur, was Agent A *committed* hat, + nicht was A gerade in Arbeit hat. +- Git-Staging: wenn Agent A und B appenden ohne zu committen, gewinnt + nur das letzte `git add`. Mitigation: sofort nach Append committen. +- Ordering: Einträge erscheinen unten (per `tee -a`); Konvention ist + newest-first. Main-Thread reordered bei Board-Hygiene. + +### §3.3 Layer-2: Branch-Pub/Sub + +**Ersetzt:** Cross-Session-Handoff. +**Wie:** öffne einen Coordination-PR. Beide Sessions subscriben. +Push-Events kommen als ``-Tags via +`mcp__github__subscribe_pr_activity`. + +**Setup:** + +```bash +# Session A (creates the bus): +git checkout -b claude/blackboard +echo "# Coordination Blackboard" > .claude/board/AGENT_LOG.md +git add .claude/board/AGENT_LOG.md +git commit -m "init coordination blackboard" +git push -u origin claude/blackboard + +mcp__github__create_pull_request( + owner="", repo="", + title="A2A coordination blackboard", + head="claude/blackboard", base="main", + body="Cross-session pub/sub bus. Do not merge.", + draft=true +) + +mcp__github__subscribe_pr_activity(owner="", repo="", pullNumber=NNN) + +# Session B (joins): +mcp__github__subscribe_pr_activity(owner="", repo="", pullNumber=NNN) +git fetch origin claude/blackboard +git checkout claude/blackboard +# Read AGENT_LOG.md +``` + +**Koordinations-Loop:** + +``` +Session A: Session B: + [does work] + tee -a AGENT_LOG.md > /dev/null < + git pull origin claude/blackboard + cat AGENT_LOG.md + [builds on A's findings] + tee -a AGENT_LOG.md ... + git add && commit && push + ← + git pull + # reads B's entry, continues +``` + +**Warum es funktioniert:** + +- `subscribe_pr_activity` ist schon im MCP-Toolkit — Zero Infra. +- GitHub-Webhooks feuern bei jedem Push, unabhängig vom Inhalt. +- Append-only-Files mergen sauber (kein Konflikt bei concurrenten + Appends an unterschiedlichen Positionen). +- Der Draft-PR mergt nie — er ist der Bus, nicht ein Deliverable. + +**Limitierungen:** + +- GitHub-Webhook-Latency: Sekunden bis niedrige Minuten. +- Rate-Limits: GitHub-API 5000/h authenticated. +- Braucht Netzwerk: funktioniert nicht offline. +- PR MUSS offen bleiben: Schließen tötet die Subscription. + +### §3.4 Layer-3 (Handover): Strukturierte Handover-Files + +**Ersetzt:** Session-zu-Session-Context-Transfer. +**Pfad:** `.claude/handovers/YYYY-MM-DD-HHMM--to-.md`. + +Der `SessionStart`-Hook (`.claude/hooks/session-start.sh`) cat'ed +die letzte Handover-File in den Context der nächsten Session. + +Schema ist in §6.3. + +--- + +## §4 Decision-Matrix + +| Bedarf | Workaround | Native-MCP-Equivalent | Cost | +|---|---|---|---| +| Agent A's Findings feeden Agent B (gleiche Session) | Layer-1 File-Blackboard | `post_entry` + `read_entries` | Low: `tee -a` + `git add` | +| Session A's Arbeit feedet Session B (Real-Time) | Layer-2 Branch-Pub/Sub | `subscribe` auf Entries | Medium: PR + subscribe | +| Full-Context-Role-Switch (kein Verlust) | Layer-0 Teleportation | n/a (in-process) | Zero: Card lesen reicht | +| Session-zu-Session-Knowledge-Transfer | Handover-Files | `post_handover` + `read_latest_handover` | Low: einmal schreiben, beim Start lesen | +| Parallele independent Grind-Arbeit | Standard `Agent()`-Spawns | n/a | Low: fire and forget | +| Multi-Source-Synthese die Judgment braucht | Teleportation auf Opus-Main-Thread | n/a | Zero | + +--- + +## §5 Native-MCP-Server-Contract + +Ein zukünftiger MCP-Server SOLLTE die folgenden Endpoints exponieren. +Sie sind hier spezifiziert, damit das Wire-Format des Workaround-Modes +forward-kompatibel zum Native-Mode ist. + +### §5.1 `post_entry` + +```yaml +endpoint: post_entry +params: + board: string # z. B. ".claude/board/AGENT_LOG.md" + entry: + timestamp: string (ISO 8601) + agent_id: string + bundle: string | null + sentinel: string | null + commit: string | null # short SHA + outcome: enum (SUCCESS | PARTIAL_SUCCESS | RETRY | FAIL | SKIPPED) + summary: string + proof_of_read: list of ProofOfRead # siehe §6.2 +returns: + entry_id: string + position: integer +errors: + - INVALID_OUTCOME + - SENTINEL_MISMATCH + - PROOF_OF_READ_MISSING_REQUIRED +``` + +### §5.2 `read_entries` + +```yaml +endpoint: read_entries +params: + board: string + since: string (entry_id) | null + limit: integer (default 100, max 1000) + filter: + agent_id: string | null + outcome: enum | null + bundle: string | null +returns: + entries: list of BlackboardEntry + next_cursor: string | null +``` + +### §5.3 `subscribe` + +```yaml +endpoint: subscribe +params: + board: string + cursor: string | null # Start-Position +streams: + type: server_sent_events + event_kinds: + - entry_appended + - entry_redacted + - board_archived +returns_initial: + cursor: string +errors: + - BOARD_NOT_FOUND + - SUBSCRIPTION_LIMIT_EXCEEDED +``` + +### §5.4 `post_handover` / `read_latest_handover` + +```yaml +endpoint: post_handover +params: + from_session: string + to_session: string | null # null = „nächste Session, wer auch immer" + handover: Handover # siehe §6.3 +returns: + handover_id: string + +endpoint: read_latest_handover +params: + to_session: string # current session id +returns: + handover: Handover | null +``` + +### §5.5 Authentifizierung + +Der Native-Server SOLLTE das gleiche Auth-Substrat wie der GitHub- +MCP-Server nutzen (per-session OAuth oder PAT). Workaround-Mode +nutzt Git-Push-Permissions auf dem Branch des Coordination-PRs. + +--- + +## §6 Schemata + +### §6.1 BlackboardEntry + +```json +{ + "entry_id": "01HXYZ-WAVE12-A4-SUCCESS", + "timestamp": "2026-05-17T17:42:00Z", + "agent_id": "A4", + "bundle": "customer-master-data", + "sentinel": "WAVE-12-A4-7f3c", + "commit": "a1b2c3d4", + "outcome": "SUCCESS", + "summary": "customer-master portiert; 7 Parity-Tests grün; keine Iron-Rule-Verletzungen.", + "proof_of_read": [ + { "file": "META/INVARIANTS.md", "sha256": "...", "lines": 412, "depth": "thorough" } + ] +} +``` + +### §6.2 ProofOfRead + +```json +{ + "file": "path/relative/to/repo-root", + "sha256": "64-hex", + "lines": 412, + "depth": "thorough" +} +``` + +`depth`-Werte: `grep` | `sed-partial` | `skim` | `read` | `thorough` +| `troubleshooting` | `fan-out` | `truncated:head-N` | `truncated:tail-N`. + +### §6.3 Handover + +```yaml +handover: + id: 2026-05-17-2200-wave12-to-wave13 + from_session: 2026-05-17-evening + to_session: null # any next session + topic: customer-master-port + + what_i_did: + - bullet: 12 Bundles (A1..A12) für customer-master portiert + commits: [a1b2c3, d4e5f6, ...] + + finding: + - bullet: BBB-Barrier-Compile-Error tritt bei A5 auf wenn sea-orm 0.12 im Path + severity: P0 + source: META/INVARIANTS.md §BBB + + conjecture: + - bullet: Switch zu sea-orm 0.13 könnte den Migrations-Path brechen; braucht Probe + severity: P1 + + blockers: + - description: A5 kann nicht gemerged werden bis der sea-orm-RFC akzeptiert ist + filed_in: META/REQUESTS-FROM-AGENTS.md#A5 + + open_questions: + - "Sollte A11 ein separater PR sein oder in die Consolidation rolled?" + + proof_of_read: + - file: META/INVARIANTS.md + sha256: "..." + lines: 412 + depth: thorough +``` + +### §6.4 RequestEntry (`META/REQUESTS-FROM-AGENTS.md`) + +```yaml +request: + agent_id: A5 + file: src/customer/master/sea_orm.rs + timestamp: 2026-05-17T18:14:32Z + + question: | + Das Reference-Python nutzt ein idempotentes UPSERT, aber der + sea-orm-Builder den ich habe macht INSERT-OR-FAIL. Spec ist + mehrdeutig. + + tried: + - sea-orm `on_conflict().do_nothing()` — failt mit `Error: ...` + - manueller Transaction-Wrap — funktioniert aber verdoppelt Latency + + blocker: AMBIGUITY + + proof_of_read: + - file: vendor/ogit/NTO/WorkOrder/entities/customer.ttl + sha256: "..." + lines: 137 + depth: full + - file: ../WoA/woa/blueprints/customer.py + sha256: "..." + lines: 891 + depth: full +``` + +### §6.5 AnswerEntry (`META/ANSWERS-TO-AGENTS.md`) + +```yaml +answer: + agent_id: A5 + in_response_to: 2026-05-17T18:14:32Z + + decision: | + Nutze sea-orm-Transaction-Wrap. Der Latency-Cost ist akzeptabel. + + propagation: + invariants_section: META/INVARIANTS.md §UPSERT-PATTERN added + rfcs: null + + signed_off_by: meta-agent (auto) + timestamp: 2026-05-17T18:42:00Z +``` + +--- + +## §7 Append-only-Governance + +### §7.1 Mutability-Invariante + +Über alle Coordination-Files hinweg: + +| File | Mutability | +|---|---| +| `Stand.md` / `STATUS_BOARD.md` | Mutable (die EINZIGE). | +| `AGENT_LOG.md` | Append-only. | +| `REQUESTS-FROM-AGENTS.md` | Append-only. | +| `ANSWERS-TO-AGENTS.md` | Append-only. | +| `INVARIANTS.md` | Append-only nach Section; Sections können SUPERSEDED markiert werden, aber nie gelöscht. | +| Handover-Files | Immutable nach Write. | +| Cross-Session-Broadcast-Files (`PR_ARC_INVENTORY.md`, `LATEST_STATE.md`, `EPIPHANIES.md`, `IDEAS.md`, `INTEGRATION_PLANS.md`, `ISSUES.md`, `TECH_DEBT.md`, `STATUS_BOARD.md`) | Append-only auf Entry-Level; spezifische Felder sind mutable State. | + +### §7.2 Enforcement + +Implementierungen SOLLTEN Append-only-Governance auf der +Permissions-Schicht durchsetzen. Die lance-graph-Referenz (siehe +[`AdaWorldAPI/lance-graph` `.claude/settings.json`](https://github.com/AdaWorldAPI/lance-graph/blob/main/.claude/settings.json)) +denied `Edit` und `Write` auf den 8 Bookkeeping-Files und erlaubt +nur `Bash(tee -a *)`. Das ist das empfohlene Pattern. + +--- + +## §8 Relation zu Runtime-A2A (Layer 1, In-Process) + +Wenn ein Projekt auch ein In-Process-Blackboard in seiner eigenen +Runtime fährt (z. B. `lance_graph_contract::a2a_blackboard::Blackboard`), +sind die Session-Level-Workarounds strukturell isomorph dazu: + +| Runtime (in-process) | Session (diese Spec) | +|---|---| +| `Blackboard.entries` | `AGENT_LOG.md`-Entries | +| `BlackboardEntry.expert_id` | Agent-description + Model | +| `BlackboardEntry.capability` | Bundle / D-ids | +| `BlackboardEntry.result` | Commit-Hash + Outcome | +| `BlackboardEntry.confidence` | Tier-1-Gate-Pass-Count | +| `Blackboard.round` | Git-Commit-Sequence | +| Experts lesen prior Rounds | Agents lesen prior Log-Entries | + +Die Isomorphie ist absichtlich: das gleiche Koordinations-Pattern +funktioniert auf beiden Layern, weil das Problem dasselbe ist — +unabhängige Experten komponieren Resultate auf einem geteilten +Substrat. + +--- + +## §9 Definition von Fertig + +Eine Implementierung ist konform, wenn sie ALLE erfüllt: + +- [ ] Drei Layer (Teleport / Blackboard / Branch-Pub/Sub) sind + implementierbar mit den existierenden Primitives (File + Git + + MCP `subscribe_pr_activity`). +- [ ] Native-MCP-Endpoints §5.1–§5.4 existieren ODER der + Workaround-Mode ist als expliziter Fallback dokumentiert. +- [ ] `BlackboardEntry`, `ProofOfRead`, `Handover`, `RequestEntry`, + `AnswerEntry`-Schemata sind implementiert und validiert. +- [ ] Append-only-Governance (§7) ist auf der + `.claude/settings.json`-Schicht für die 8 Bookkeeping-Files + durchgesetzt. +- [ ] Die Single-Mutable-File-Invariante ist durchgesetzt: `Stand.md` + / `STATUS_BOARD.md` ist die EINZIGE File, die Workers + überschreiben dürfen. +- [ ] Decision-Matrix (§4) wird befolgt: Workers nutzen den + richtigen Transport für die richtige Art Message. +- [ ] Coordination-PRs sind Draft, benamt `claude/`, und + explizit als `Do not merge` im Body markiert. +- [ ] Handover-Files nutzen das §6.3-Schema mit Required Sections: + What-I-did / FINDING / CONJECTURE / Blockers / Open-Questions + / Proof-of-Read. + +--- + +## §10 Cross-Provider-Parity-Matrix + +| Fähigkeit | Claude Code | OpenAI Codex / codex-rs | Gemini CLI | Notizen | +|---|---|---|---|---| +| Layer-0 Teleportation | Read Agent-Card auf Main-Thread | Same | Same | Provider-agnostisch; hängt nur von File-Read ab. | +| Layer-1 File-Blackboard | `Bash(tee -a)` + `git add` | Same | Same | POSIX-only; Provider-agnostisch. | +| Layer-2 Branch-Pub/Sub | `mcp__github__subscribe_pr_activity` | `gh pr`-Polling | `gh pr`-Polling | Claude Code hat natives MCP; andere pollen. | +| Native-MCP-Server (§5) | TBD (diese Spec) | TBD | TBD | Alle drei Provider können MCP sprechen. | +| Append-only-Governance | `.claude/settings.json`-Deny-Liste | Provider-spezifische Permission-File | Provider-spezifisch | Pattern überträgt sich; Allow-List-Syntax unterscheidet sich. | +| Handover-Files | `.claude/hooks/session-start.sh` cat'ed letzte | Equivalent Shell-Hook | Equivalent Shell-Hook | Hook-Script ist Bash; Provider-agnostisch. | + +--- + +## §11 Appendix A — Sample `AGENT_LOG.md`-Eintrag (verbatim) + +```markdown +## 2026-05-17T17:42 — customer-master ported; 7 parity tests green (sonnet, claude/wave-12-customer-master) + +**Agent-id:** A4 +**Bundle:** customer-master-data +**Sentinel:** WAVE-12-A4-7f3c +**Commit:** `a1b2c3d4` +**Tests:** 7 pass (3 new) +**Outcome:** SUCCESS — alle Tier-1-Gates grün; keine Iron-Rule-Verletzungen. +**Proof-of-read:** +- file=META/INVARIANTS.md sha256=fa39a3... lines=412 depth=thorough +- file=../WoA/woa/blueprints/customer.py sha256=8e1a45... lines=891 depth=full +- file=vendor/ogit/NTO/WorkOrder/entities/customer.ttl sha256=4c0b... lines=137 depth=full +``` + +--- + +## §12 Appendix B — Wann NICHT Coordination nutzen + +Wenn die Aufgabe ist... + +- wirklich independent und parallel → nutze Standard-`Agent()`-Spawns; kein Coordination-Layer nötig. +- eine einzelne One-Shot-Frage → nutze `Agent()` mit sub-agent_type=Explore; kein `AGENT_LOG.md`-Write. +- ein security-sensitives Review → nutze Layer-0-Teleportation auf dem Main-Thread; NICHT einen separaten Agent() spawnen, der von einer untrusted Source kompromittiert werden könnte, die er liest. + +--- + +## §13 Cooperative-Savant-Scratchpad-Bus + +> Hinzugefügt 2026-05-18. Die Transport-Schicht für den Cooperative +> Savant Council, definiert in `autoattended-orchestrator-spec.md` §15. + +### §13.1 Was der Scratchpad-Bus ist + +Eine zweck-geformte Erweiterung des Layer-1 File-Blackboard (§3.2), +genutzt spezifisch für die iterative Cooperation des Savant-Councils. +Gleiches Wire-Format (`tee -a`-Markdown), gleiche Append-only- +Governance (§7), eine neue Directory-Konvention: + +``` +.claude/board/savant-council-/ +├── ROUND-0-ARTIFACT.md Snapshot des Artifacts unter Review +├── ROUND--.md eine File pro (Round, Savant) +└── COUNCIL-VERDICT.md Chairman-Synthese nach CONVERGE +``` + +Topic-Naming: `savant-council-` (z. B. +`savant-council-SPRINT-17-attractor-fold-in/`). + +### §13.2 Per-Savant-File-Schema + +```markdown +# ROUND-{N} {savant_id} — {topic} + +**Round:** N +**Savant:** PP-13 | PP-14 | PP-15 | PP-16 +**Read at round start:** [Liste der diese Round gelesenen Peer-Files] +**Status:** IN-PROGRESS | ROUND-COMPLETE + +## New findings (this round) + +### F{round}-{savant}-{N}: {one-line headline} +**Axis:** AP1..AP9 | BAP1..BAP10 | PD1..PD10 | EP1..EP8 +**Severity:** P0 | P1 +**File:** : +**Detail:** +**Remediation:** + +## Cross-references to peers' findings + +- F{R}-PP-X-N: NOTED — mein Winkel: +- F{R}-PP-Y-N: SUPERSEDED-BY meinem F{R+1}-{self}-M + +## Withdrawals (meine prior Findings, die ich zurückziehe) + +- F{R-1}-{self}-N: WITHDRAWN — covered by PP-X round R aus stärkerem Winkel +``` + +### §13.3 Reading-and-Writing-Protokoll + +Jeder Savant in jeder Round: + +1. **Lies** ALLE `ROUND-(R-1)-*.md`-Files (Peer-Findings aus der + vorigen Round) plus die eigene `ROUND-(R-1)-.md` falls vorhanden. +2. **Entscheide** Cross-Refs, Withdrawals, neue Findings. +3. **Schreib** `ROUND-R-.md` in einem `tee -a`-Call (keine + partial Writes — Savants teilen sich ein Directory und partial + Writes verwirren die nächste Peer-Round). +4. **Emittiere** `Status: ROUND-COMPLETE` wenn keine neuen Findings + UND keine un-addressed Cross-Refs von Peers. + +### §13.4 Chairman-Synthese + +Nach CONVERGE (alle vier Savants `ROUND-COMPLETE` in der gleichen +Round) liest der Chairman-Savant (deklariert in Sprint-Config; +default PP-16 für Protokoll A, PP-15 für Protokoll B) ALLE +ROUND-*.md-Files in lexikalischer Ordnung und schreibt eine +einzelne `COUNCIL-VERDICT.md` matching das Schema in +`autoattended-orchestrator-spec.md` §15.5. + +Der Chairman: +- MUSS jede gehaltene Finding mit ihrem raised-by + Cross-Referrals + zitieren. +- MUSS Duplikate konsolidieren, die die Cooperation-Rounds + überlebten (selten — die meisten sollten sich selbst zurückziehen, + aber Races können ein Paar hinterlassen, das in eines gemerged werden muss). +- DARF NICHT eigene Findings hinzufügen, die in keinem `ROUND-*.md` + auftauchten (der Chairman ist ein Synthesizer, keine fünfte Stimme). +- MUSS `super_verdict` per §15.5-Regeln setzen. + +### §13.5 Concurrency-Contract + +Savants können in separaten Sessions laufen; der Scratchpad-Bus ist +der Synchronisations-Punkt. + +- **Round-Boundary** ist implicit: ein Savant startet Round R erst, + nachdem er alle vier `ROUND-(R-1)-*.md`-Files (inklusive seiner + eigenen) gelesen hat. +- **Last-Writer-Wins** ist akzeptabel für die Per-Savant-Per-Round- + File, da jedes (Savant, Round)-Paar by construction genau einen + Writer hat. Wenn ein Savant zweimal in derselben Round schreibt + (z. B. ein Retry nach Tool-Call-Loop), ersetzt der zweite Write + den ersten. +- **Round-Skipping** ist verboten: ein Savant DARF NICHT + `ROUND-(R+2)` schreiben, bevor `ROUND-(R+1)` für ihn existiert. + +### §13.6 Decision-Matrix-Update + +Erweitert §4 mit einer neuen Zeile: + +| Bedarf | Workaround | Native-MCP-Equivalent | Cost | +|---|---|---|---| +| Cooperative Multi-Agent-Review mit iterativem Cross-Refer + Withdraw | Layer-1 File-Blackboard scoped auf `savant-council-/`-Directory (§13) | `post_council_finding` + `read_council_round` + `subscribe_council_round` | Medium: `tee -a` pro Round + Git-Commit pro Round | + +### §13.7 Native-MCP-Endpoints (Sketch) + +```yaml +endpoint: post_council_finding +params: + topic: string # z. B. "SPRINT-17-attractor-fold-in" + round: integer + savant: enum (PP-13 | PP-14 | PP-15 | PP-16) + payload: FindingsFile # matched §13.2-Schema +returns: + file_id: string + +endpoint: read_council_round +params: + topic: string + round: integer +returns: + findings_by_savant: map[savant -> FindingsFile] + complete_savants: list[savant] # die, die ROUND-COMPLETE emittiert haben + +endpoint: subscribe_council_round +params: + topic: string +streams: + event_kinds: [ findings_appended, savant_round_complete, council_converged, council_stalled, council_blocked ] +``` + +### §13.8 Validierungs-Regeln + +| Regel | Beschreibung | Severity | +|---|---|---| +| `COORD-001 directory-naming` | Council-Scratchpad MUSS unter `.claude/board/savant-council-/` liegen. | ERROR | +| `COORD-002 file-naming` | Per-Savant-Files MÜSSEN `ROUND--PP-.md` benamt sein. | ERROR | +| `COORD-003 append-only` | Das Scratchpad-Directory erbt die Append-only-Governance von §7. | ERROR | +| `COORD-004 chairman-no-new-findings` | Die `COUNCIL-VERDICT.md` des Chairmans DARF NICHT Findings einführen, die in keinem `ROUND-*.md` vorkommen. | ERROR | +| `COORD-005 round-monotonic` | Jeder Savant MUSS `ROUND-(R+1)` erst schreiben, nachdem `ROUND-R-.md` existiert. | ERROR | + +--- + +*Ende der Datei agent-coordination-mcp-spec.md.* diff --git a/.claude/ATT/DE/anti-skim-agent-spec.md b/.claude/ATT/DE/anti-skim-agent-spec.md new file mode 100644 index 0000000000..8f1ae2877f --- /dev/null +++ b/.claude/ATT/DE/anti-skim-agent-spec.md @@ -0,0 +1,1102 @@ +# Anti-Skim-Agent-Spezifikation + +> **Sprache:** Deutsch · siehe `../anti-skim-agent-spec.md` für die englische Quellfassung. +> +> **Status:** DRAFT · **Version:** 0.1.0 · **Stand:** 2026-05-17 +> **Format:** NLSpec (nach [strongdm/attractor](https://github.com/strongdm/attractor)) +> **Substrat:** Komponiert auf `coding-agent-loop-spec.md`; ergänzt die +> Verifikations- und Eskalations-Schicht, die Attractors Spec auslässt. + +--- + +## §1 Überblick + +### §1.1 Zweck + +Der Anti-Skim-Agent ist ein Per-Worker-LLM-Loop, der **verifizierbar +lesen-vor-handeln** macht. Er behandelt **Overconfidence** des Agents +als primären Failure-Mode (vgl. Kahneman / Tversky System-1-Easy-Path, +Dunning-Kruger) und verschiebt die Beweislast auf den Agent: + +- Jede Behauptung über den Inhalt einer Datei MUSS mit einer + SHA-256 + Line-Count + Reading-Depth-Deklaration belegt sein. +- Jedes Brief enthält ein Sentinel-Token, das der Agent literal + zurückspielen MUSS. +- Ein Stichproben-Check besteht je Supervisor-Pass einen der fünf + Lie-Detector-Tests. + +### §1.2 Relation zu `coding-agent-loop-spec.md` + +Der Anti-Skim-Agent ist als Coding-Agent-Loop-Session implementierbar, +mit den Ergänzungen in §3–§6. Konkret: + +- Der `Session.run()`-Loop (coding-agent §2.5) wird mit einer + Proof-of-Read-Vorbedingung umwickelt (§7). +- Die `steering_queue` (coding-agent §2.6) wird um die typisierten + Stuck-Protocol-Blocker aus §5 erweitert. +- Der Tool-Call-Loop-Detector aus coding-agent §2.10 wird wortgleich + übernommen und auf System-Level-Invariante gehoben (§6). +- Tool-Output-Truncation (coding-agent §5) wird übernommen mit einer + Ergänzung: getrunkierte Reads MÜSSEN im Proof-of-Read mit + `depth=truncated` auftauchen, NICHT `depth=full`. + +### §1.3 Was diese Spec NICHT ist + +- Kein Orchestrator. Wave-Level-Koordination ist in + [`autoattended-orchestrator-spec.md`](./autoattended-orchestrator-spec.md). +- Kein Per-Agent-Loop. Worker-Verhalten ist in + [`anti-skim-agent-spec.md`](./anti-skim-agent-spec.md). +- Kein Transport. Cross-Agent-Messages laufen über das File-Blackboard + + Handover-Files in + [`agent-coordination-mcp-spec.md`](./agent-coordination-mcp-spec.md). + +--- + +## §2 Terminologie + +| Begriff | Definition | +|---|---| +| **Sentinel-Token** | Ein eindeutiger, opaker String im Worker-Brief, den der Worker literal in seiner ersten Antwort zurückspielen MUSS. | +| **Proof-of-Read** | Ein typisiertes Eintrags-Tupel `{ file, sha256, lines, depth }`, das der Worker für jede konsumierte Datei deklariert. | +| **Reading-Depth** | Eine von sieben eskalierenden Stufen: `grep` (anti) / `sed-partial` (anti) / `skim` / `read` / `thorough` / `troubleshooting` / `fan-out`. | +| **Lie-Detector-Test** | Einer von fünf Tests (LD-1..5), den der Meta-Agent / Orchestrator stichprobenartig auf den Worker-Output anwendet. | +| **Drift-Signal** | Ein passiver Indikator für Skim oder Overconfidence (§4.3). | +| **Stuck-Protocol-Blocker** | Ein typisierter Eintrag in `META/REQUESTS-FROM-AGENTS.md` (einer von fünf Typen), der signalisiert „ich komme nicht weiter". | +| **Tool-Call-Loop** | Ein wiederholendes Pattern der Länge 1, 2 oder 3 in den letzten 10 Tool-Calls des Workers. | + +--- + +## §3 Die Reading-Depth-Ladder + +### §3.1 Sieben Tiefen, zwei Anti-Tiefen + +| Depth | Wann angemessen | Proof | +|---|---|---| +| `grep` (anti) | Symbol-Locator NACH oder PARALLEL zu einem echten Read. | Worker deklariert `depth=grep`; NIEMALS `depth=full`. | +| `sed-partial` / `head-only` (anti) | Snapshot weniger Zeilen zur Orientierung. | Worker deklariert `depth=sed-partial`; NIEMALS `depth=full`. | +| `skim` | Riesige Datei; eine Section lokalisieren, dann voll lesen. | Worker macht NUR Aussagen über die gelesene Section. | +| `read` | Datei < ~500 Zeilen; Standard-Full-Read. | Top to Bottom, kein Skip; bei größeren Files: offset/limit-Chunks aber jeder Chunk tatsächlich gelesen. | +| `thorough` | Iron Rules, INVARIANTS, RFCs, Schema-Migrationen. | Zweimal lesen: einmal Verständnis, einmal Verifikation. Worker kann 3 Sections namentlich nennen. | +| `troubleshooting` | Bekannter Bug + Error-Message. | Error → grep Symbol → READ Funktion voll → READ Caller(s). | +| `fan-out` | Cross-File-Pattern, Refactor-Planung, Audit über > 5 Dateien. | Inventur-File als Output; Explore-Subagent spawnen. | + +### §3.2 Eskalations-Default + +**Default-Tendenz: lieber zu tief lesen als zu flach.** Bei Zweifel: +Depth eine Stufe upgraden; niemals downgraden. + +### §3.3 Wenn → minimale Tiefe + +| Wenn der Worker im Begriff ist ... | ... dann mindestens | +|---|---| +| `INVARIANTS.md`, `CLAUDE.md`, `BOOT.md` öffnen | `thorough` | +| Eine Memory-File (`CONTEXT.md`, `JOURNAL.md`, `TODO.md`) öffnen | `thorough` | +| Eine Schema- oder Migrations-Datei anfassen | `thorough` + Downstream-Drift-Detector mit prüfen | +| Eine unbekannte Datei zum ersten Mal öffnen | mindestens `read`, eher `thorough` | +| Reine Symbol-Lookup („wo ist `foo` definiert?") | `grep` OK aber dann Definition voll lesen | +| Bug-Bericht triagieren | `troubleshooting` (Error → grep → READ Funktion + Caller) | +| Refactor / Wave / Audit planen | `fan-out` (Inventur-File Pflicht) | +| Unsicher welche Depth | eine Stufe upgraden | + +### §3.4 Interaktion mit Truncation + +Wenn `coding-agent-loop-spec` einen Tool-Output truncated (per +coding-agent §5.2), MUSS der Proof-of-Read-Eintrag des Workers für +diese Datei `depth=truncated:head-N` oder `depth=truncated:tail-N` +deklarieren — NIEMALS `depth=full`. + +--- + +## §4 Der Lie-Detector + +### §4.1 Fünf Tests + +Billig zu teuer: + +| # | Test | Wie | Ehrlicher Agent | Flacher Agent | +|---|---|---|---|---| +| LD-1 | **Sentinel-Token** | Brief endet mit: „Wenn du dies vollständig gelesen hast, beginne deine erste Antwort mit ``" | spielt Token literal zurück | Token fehlt / falsch / paraphrasiert | +| LD-2 | **Proof-of-Read mit SHA** | Output muss enthalten `{ file, sha256, lines, depth }` pro konsumierter Datei | SHA + Line-Count matchen die echte Datei | SHA fehlt, falsch, oder ``-Platzhalter | +| LD-3 | **3-Sections-Namens-Challenge** | „Nenne 3 Sections aus File X namentlich (Heading + ungefähre Zeilen-Spanne)" | 3 konkrete Headings, plausible Spannen | vage Themen-Labels; keine echten Headings | +| LD-4 | **Negative-Knowledge-Test** | „Steht in File X etwas zu Thema Y?" — wobei Y NICHT drin ist | „nein, nicht enthalten" | halluziniert plausibel klingenden Inhalt zu Y | +| LD-5 | **Line-Range-Quote** | „Quote Zeilen N-M aus File X verbatim" | exakter Quote ODER „Range existiert nicht (File nur K Zeilen)" | paraphrasiert, weicht ab, oder verweigert ohne Grund | + +> **Adoption-Note.** LD-3 / LD-4 / LD-5 sind SHOULD-USE auf Savants +> jede Wave, MAY-USE auf Workers selektiv — typisch wenn +> `phase_reached=internalize` geclaimt wird oder der Output +> overconfident wirkt. Per-Worker-per-Wave-Rotation ist Overkill +> für routinemäßige 200-LoC-Bundles. Siehe §16 Adoption-Tiers. + +### §4.2 Trigger-Heuristik + +| Worker-Antwort-Pattern | Lie-Detector | +|---|---| +| Vage („nicht sicher", „ungefähr 60", „etwa 5 Sections") | KEIN Trigger — ehrliche Unsicherheit. | +| Selbstbewusst **falsch** | FEUERT — Overconfidence + Drift. | +| Selbstbewusst **richtig** ohne Proof-of-Read | FEUERT — Glück und Halluzination sind ohne LD-1..5 nicht unterscheidbar. | + +### §4.3 Drift-Signale (passive Detection, Meta-Agent-Pflicht) + +Der Meta-Agent scannt JEDEN Worker-Status vor dem Goal-Gate-Verdict. +Wenn ein Signal triggert, MUSS der Worker re-dispatched werden ODER +ein Lie-Detector-Test angewendet werden: + +| Signal | Aktion | +|---|---| +| `depth=grep` deklariert bei semantischer Aufgabe | Re-Dispatch mit Pflicht-`depth=full`. | +| `depth=sed-partial` deklariert bei semantischer Aufgabe | Re-Dispatch; SHA-256 über die ganze Datei verlangen. | +| `depth=full` deklariert ohne `sha256` im Proof-of-Read | Stop; Worker MUSS SHA nachliefern. | +| Status-`notes` zitiert eine nicht-existente Section einer Datei | LD-3-Äquivalent fehlgeschlagen post-facto → P0 Re-Dispatch. | +| Status-`notes` ist wortgleich zu einem Sibling-Agent ohne Cross-Read-Vermerk | Lockstep-Drift; Stichprobe LD-5 auf einen der beiden. | +| Eröffnungs-Output zählt eine spezifische Zahl („62 Lehren") OHNE Proof-of-Read auf der Quelle | Overconfidence-Trigger; Agent MUSS SHA + 3 Einträge namentlich nachliefern. | +| Status fasst eine INVARIANT an, aber `proof_of_read` enthält `INVARIANTS.md` nicht | Mandatory `thorough` Re-Read. | + +### §4.4 Beweislast-Regel + +Bei Verdacht: der Worker MUSS aufrüsten. Niemals den Verdacht +„wegerklären" — entweder Proof nachliefern, oder ehrlich +downgraden (`depth=skim`/`grep` mit Notiz). + +--- + +## §5 Stuck-Protokoll + +### §5.1 Fünf typisierte Blocker + +| Typ | Bedeutung | +|---|---| +| `AMBIGUITY` | Spec ist mehrdeutig. Mehr als eine sinnvolle Interpretation. | +| `MISSING_INVARIANT` | Eine Iron Rule sollte den Fall abdecken, tut es aber nicht; die Konvention fehlt. | +| `SPEC_SOURCE_MISMATCH` | Die autoritative Spec sagt X; die Reference-Source tut Y. | +| `BEHAVIOUR_QUESTION` | Möglicher Bug in der Reference-Source; Behaviour-Divergenz braucht RFC. | +| `EXTERNAL_DEPENDENCY` | Drittsystem zickt; Workaround unklar. | + +### §5.2 Das Protokoll + +Wenn stuck, der Worker MUSS: + +1. STOP schreiben von Code. +2. EINEN Eintrag an `META/REQUESTS-FROM-AGENTS.md` anhängen mit + dem §5.3-Schema. +3. Sein `status.json` auf `outcome=RETRY` und `notes=stuck:` + setzen. +4. Idle bis Meta-Agent in `META/ANSWERS-TO-AGENTS.md` schreibt + ODER `META/INVARIANTS.md` aktualisiert. + +### §5.3 Request-Schema + +```markdown +## Agent {{agent_id}}, file , timestamp + +### Question + + +### Tried + + +### Blocker + + +### Proof-of-read attached +- file= sha256=<...> lines=<...> depth=<...> +- file= sha256=<...> lines=<...> depth=<...> +``` + +Der Proof-of-Read MUSS JEDE Datei abdecken, die der Worker in der +Question referenziert. Stuck-Requests ohne Proof-of-Read werden bei +Intake zurückgewiesen (§7.5 in `autoattended-orchestrator-spec.md`). + +### §5.4 Verbotene Alternativen + +Der Worker DARF NICHT: + +- Raten. +- Einen `TODO:`-Kommentar in Code schreiben und weitermachen. +- Außerhalb des Bundles refaktorieren „während er wartet". +- Den gleichen Request zweimal ohne neue Information filen. + +--- + +## §6 Tool-Call-Loop-Detection + +### §6.1 Übernahme von Attractor coding-agent §2.10 + +Ein Worker MUSS seine eigenen letzten `N=10` Tool-Calls (Signature = +Name + Arguments-Hash) auf wiederholende Patterns der Länge 1, 2 +oder 3 scannen. + +``` +Pattern-Länge 1: [A][A][A] — gleiches Tool, gleiche Args, 3+ Mal +Pattern-Länge 2: [A][B][A][B] — alternierend, 2+ Zyklen +Pattern-Länge 3: [A][B][C][A][B][C] — 3-Zyklus, 2+ Zyklen +``` + +### §6.2 Aktion bei Detektion + +Bei Detektion: + +1. Der Worker DARF NICHT den nächsten vorhergesagten Call im Pattern + ausgeben. +2. Der Worker MUSS einen typisierten RETRY-WITH-STEER-Status emittieren: + `outcome=RETRY`, `notes=tool-call-loop:length=:pattern=`. +3. Der Worker MAG einen `META/REQUESTS-FROM-AGENTS.md`-Eintrag unter + dem `AMBIGUITY`-Blocker-Typ (§5.1) filen, wenn der Loop keine + offensichtliche externe Auflösung hat. + +### §6.3 Anti-Pattern AP9 + +Diese Regel wird auch von PP-13 brutally-honest-tester als +Anti-Pattern AP9 (tool-call-loop) durchgesetzt. PP-13s Post-hoc- +Detection greift, wenn der In-Loop-Detector versagt hat. + +> **Implementations-Note (ehrlicher Caveat).** Das aktuelle Claude +> Code `Agent()`-Tool exponiert KEINE Self-Interrupt-API. Der +> §6.1-Detector lebt nur als prompt-level Instruction; Workers +> können sich nicht tatsächlich via Tool-API terminieren — sie +> können nur `outcome=RETRY` emittieren nachdem sie den Loop +> bemerken. Das fängt In-LLM-Oszillation; es fängt NICHT OS-Level- +> Hangs (z. B. `cargo build` stuck auf einem Netzwerk-Resolver). +> Für OS-Level-Hangs MUSS der Orchestrator jeden Spawn in einen +> Wall-Clock-Timeout wrappen. Siehe §16.4. + +--- + +## §7 Proof-of-Read-Schema + +### §7.1 Per-File-Eintrag + +```json +{ + "file": "path/relative/to/repo-root.md", + "sha256": "abc...64-hex...def", + "lines": 412, + "depth": "thorough", + "comment": "Two-Pass-Read; INVARIANTS §3.1-3.4 im Worker-Output zitiert" +} +``` + +### §7.2 Erlaubte Depth-Werte + +``` +grep sed-partial skim +read thorough troubleshooting +fan-out truncated:head-N truncated:tail-N +``` + +`grep`, `sed-partial`, `truncated:*` DÜRFEN NICHT mit Behauptungen +kombiniert werden, die volles Wissen über die Datei implizieren +(LD-3 / LD-4 / LD-5). + +### §7.3 Multi-File-Einträge + +Workers MÜSSEN einen Eintrag pro konsumierter Datei emittieren. Der +Orchestrator-Goal-Gate (PP-13) DARF bis zu drei Einträge pro Worker +pro Wave per LD-2 stichprobenartig prüfen (SHA neu berechnen, +Line-Count vergleichen). + +### §7.4 Speicherung + +Proof-of-Read-Einträge leben in der `status.json` des Workers +(per `autoattended-orchestrator-spec.md` §9.1). Sie werden auch +nach `META/AGENT_LOG.md` als One-Line-Summary nach jedem gelandeten +Chunk gespiegelt: + +``` +## YYYY-MM-DDTHH:MM — A chunk N/M +- chunk N landed (sha ) +- typecheck: green | +- proof-of-read: sha= lines= depth= +``` + +### §7.5 Sentinel-Token-Speicherung + +Das Sentinel-Token wird aus der ersten Reply-Zeile des Workers in +das Feld `sentinel_token` der `status.json` gespiegelt. Mismatch = +FAIL. + +--- + +## §8 Toolchain-Tiers + +### §8.1 Tier 1 (jeder PR — verpflichtend) + +Owned by PP-13 brutally-honest-tester. Per-Language-Adapter: + +> **Adoption-Note.** Default ist Tier-1 **coordinator-side** — PP-13 +> fährt die volle Toolchain einmal auf dem konsolidierten Diff. +> Per-Worker-Tier-1 ist teuer bei 12-Agent-Fan-out (z. B. ~1 GB +> Rust-`target/` pro Worker). Projekte DÜRFEN Tier-1 auf +> per-worker pushen (kleinere Workspaces, schnellerer Feedback) +> durch Setzen von `tier_1_runner: per-worker` in `INVARIANTS.md`. +> Siehe §16.3. + +| Zweck | Rust | Python | TypeScript | Go | +|---|---|---|---|---| +| Lint, keine Warnings | `cargo clippy --all-targets --all-features -- -D warnings` | `ruff check` | `eslint --max-warnings 0` | `golangci-lint run` | +| Formatter-Check | `cargo fmt --check` | `ruff format --check` | `prettier --check` | `gofmt -l` | +| Advisory-Scan | `cargo audit` | `pip-audit` | `npm audit --omit=dev` | `govulncheck ./...` | +| Dep-Policy / Vet | `cargo deny check` | `deptry .` | (Projekt-definiert) | `go vet -all` | +| Typecheck | (Clippy impliziert) | `mypy --strict` | `tsc --noEmit --strict` | (Compiler impliziert) | +| Tests | `cargo test --all-features` | `pytest` | `vitest run` | `go test ./...` | + +### §8.2 Tier 2 (Quality / Maintenance — Opt-in) + +| Zweck | Rust | Python | TypeScript | +|---|---|---|---| +| Unused-Dep-Detector | `cargo machete` | `deptry .` | `depcheck` | +| Unsafe-Scan | `cargo geiger` | `bandit` | (n/a) | +| Public-API-SemVer-Compat | `cargo semver-checks check-release` | `griffe check` | `api-extractor` | +| Spellcheck | `cargo spellcheck` | CSpell | CSpell | + +### §8.3 Tier 3 (Heavier — Opt-in, alle stable) + +| Zweck | Rust | Python | +|---|---|---| +| Bounded Model Checker | `kani` | (none standard) | +| Concurrency Model Checker | `loom` (lib) | (none standard) | +| Mutation Testing | `cargo mutants` | `mutmut` | +| Coverage | `cargo tarpaulin` | `pytest --cov` | + +### §8.4 Tier-1-Invariante + +Für jeden PR, jede Sprache: der Worker beweist, dass er ein +No-Warning-Gate erfüllt BEVOR der Orchestrator den PR öffnet. +Tier-1-Failure mappt direkt auf `outcome=FAIL` in der Status-File +(§9.2 von `autoattended-orchestrator-spec.md`). + +--- + +## §9 Anti-Pattern-Katalog (AP1..AP9) + +PP-13 brutally-honest-tester jagt diese aktiv: + +| # | Anti-Pattern | Detektion | Finding | +|---|---|---|---| +| AP1 | Silent Fallback der Errors schluckt (`unwrap_or_default()`, bare `try { ... } catch {}`, `result, _ := f()`) | grep PR-Diff nach diesen Idiomen | P0 wenn im Nicht-Test-Codepath | +| AP2 | Hardcoded Secret / Token / URL in Source | grep nach plausiblen Secret-Regex | P0 immer | +| AP3 | Compile-Time-Guard fallengelassen (z. B. `#[cfg(test)]`-Block entfernt im Nicht-Test-Kontext) | Diff-Inspektion | P0 wenn Guard load-bearing war | +| AP4 | Test besteht durch Tautologie (asserts `true`, `assert x == x`) | jeden neuen Test-Body lesen | P1 außer wenn es der EINZIGE Test für die Funktion ist (dann P0) | +| AP5 | Behaviour-Divergenz von Spec ohne gemergten RFC | Spec-vs-Implementation-Diff | P0 immer | +| AP6 | Fehlender Parity-Test für einen portierten Handler | `tests/parity_/` prüfen | P0 immer | +| AP7 | Neue Workspace-Dependency nicht in RFC deklariert | Metadata-Diff vs `git log --oneline rfcs/` | P0 immer | +| AP8 | `#[allow(clippy::*)]` / `# noqa` / `// eslint-disable` ohne Begründung | grep nach diesen Directives, Commit-Body prüfen | P1 außer wenn Begründung fehlt (dann P0) | +| **AP9** | **Tool-Call-Loop (Länge 1/2/3 über letzte 10 Calls; siehe §6)** | **In-Loop-Detector ODER Post-hoc-Replay der Tool-Call-History** | **P1; Re-Dispatch mit engerem Scope** | + +--- + +## §10 Definition von Fertig + +Eine Implementierung ist konform, wenn sie ALLE erfüllt: + +- [ ] Jedes Worker-Brief enthält ein eindeutiges `sentinel_token`. +- [ ] Jeder Worker beginnt seine erste Reply MIT dem Sentinel literal. +- [ ] Jeder Worker emittiert eine `status.json` per + `autoattended-orchestrator-spec.md` §9.1 mit einem + Proof-of-Read-Eintrag pro konsumierter Datei. +- [ ] Proof-of-Read-Einträge deklarieren `sha256` + `lines` + eine + Depth aus der §7.2-Liste. +- [ ] Workers fahren den §6.1-Tool-Call-Loop-Detector nach JEDEM + Tool-Call mit `N=10`. +- [ ] Bei Loop-Detection emittiert der Worker `outcome=RETRY` mit + `notes=tool-call-loop:...`. +- [ ] Bei Stuck nutzt der Worker einen der fünf §5.1-Blocker-Typen + und hängt Proof-of-Read für jede referenzierte Datei an. +- [ ] Der Meta-Agent / Supervisor stichprobenartig EINEN von LD-1..LD-5 + auf jedem **Savant** pro Wave (rotierend, sodass der Test nicht + gamed werden kann). Auf routinemäßigen **Workers** ist LD-1 + (Sentinel) Pflicht; LD-3 / LD-4 / LD-5 sind konditional — angewendet + wenn `phase_reached=internalize` geclaimt wird ODER der Output + overconfident wirkt. Siehe §16. +- [ ] Drift-Signale (§4.3) werden für jeden Worker vor dem + Goal-Gate-Verdict gescannt. +- [ ] PP-13 fährt die §8.1-Tier-1-Toolchain für die Sprache des + Workers; Tier-1-Failure → `outcome=FAIL`. +- [ ] PP-13s Anti-Pattern-Scan deckt AP1..AP9 ab. +- [ ] Tool-Output-Truncation (per `coding-agent-loop-spec.md` §5) + taucht im Proof-of-Read als `depth=truncated:head-N` oder + `depth=truncated:tail-N` auf; NIEMALS `depth=full`. +- [ ] `auto_status=false` ist Pflicht; fehlende `status.json` = FAIL. + +--- + +## §11 Cross-Provider-Parity-Matrix + +| Fähigkeit | Claude Code | OpenAI Codex / codex-rs | Gemini CLI | Notizen | +|---|---|---|---|---| +| Sentinel-Token-Replay (LD-1) | Verfügbar via System-Prompt-Addendum | Same | Same | Provider-agnostisch; nutzt Standard-Chat-Completion. | +| Proof-of-Read-Schema (LD-2) | Worker schreibt status.json | Same | Same | File-basiert; Provider-agnostisch. | +| 3-Sections-Challenge (LD-3) | Supervisor-Prompt | Same | Same | Provider-agnostisch. | +| Negative-Knowledge-Test (LD-4) | Supervisor-Prompt | Same | Same | Provider-agnostisch. | +| Line-Range-Quote (LD-5) | Supervisor-Prompt + File-Re-Read durch Supervisor | Same | Same | Provider-agnostisch. | +| Tool-Call-Loop-Detection (§6) | Implementierbar in `Session.run()`-Loop-Wrapper | Same | Same | Loop-Detector-Code ist Provider-unabhängig (operiert auf lokaler Tool-Call-History). | +| Toolchain-Tiers (§8) | Bash-Tools | Bash-Tools | Bash-Tools | Per-Language-Commands identisch. | +| Truncation → `depth=truncated:*` | Wrapper um Tool-Output | Wrapper | Wrapper | Provider-spezifisch in WAS truncated wird (unterschiedliche Default-Toolsets), uniform in WIE es aufgezeichnet wird. | + +--- + +## §12 Appendix A — Worker-Brief-Template + +```markdown +Du bist Agent {{agent_id}} in Wave {{wave_id}} von Sprint {{sprint_id}}. + +Du besitzt Bundle: {{bundle_name}} +Owned Files (read-write): +{{owned_files_table}} + +Read-only Files: +{{read_only_files_table}} + +Spec-Files (autoritativ): +{{spec_files_table}} + +INVARIANTS (lies diese zuerst, depth=thorough): +- META/INVARIANTS.md sha256={{invariants_sha}} lines={{invariants_lines}} + +Reading-Depth required (per anti-skim-agent-spec.md §3.3): +- INVARIANTS.md → thorough +- Spec-Files → full +- Reference-Source-Files → full +- Skeleton-Files → read +- Alles andere → mindestens read + +Status-File: schreib nach {{status_file_path}} matching das Schema in +autoattended-orchestrator-spec.md §9.1. + +Wenn stuck: file EINEN Eintrag in META/REQUESTS-FROM-AGENTS.md per +anti-skim-agent-spec.md §5; setze Status auf outcome=RETRY; idle. + +Done-Kriterien: per autoattended-orchestrator-spec.md §10 + §10 dieser Spec. + +Tool-Call-Loop-Detector: fahre nach jedem Tool-Call per §6. + +SENTINEL TOKEN: {{sentinel_token}} + +Beginne deine erste Reply mit dem Sentinel-Token literal. Dann sage, +in einem Absatz, was du als Bundle verstehst. Dann beginn die Arbeit. +``` + +--- + +## §13 Skeleton-Fill-Contract (Protokoll A) + +> Hinzugefügt 2026-05-18. Gilt für Workers, die in +> `autoattended-orchestrator-spec.md` Protokoll A (Implementation) +> operieren, wo PP-16s Preflight ein auskommentiertes Rust-Skelett +> erzeugt hat (per §14.4 der Orchestrator-Spec). + +### §13.1 Was der Worker bekommt + +Drei Inputs: + +1. Die Skelett-Datei(en) am `skeleton_output_path` mit + `todo!("SOURCE: :")`-Macros an jeder Body-Site. +2. Die originalen Spec- / Reference-Source-Files, die in jeder + `SOURCE:`-Annotation referenziert sind. +3. Ein SHA-256 vom Skelett UND jeder referenzierten Source-File, + gepinnt von PP-16 zur Preflight-Zeit. + +### §13.2 Worker-Pflichten + +Für jedes `todo!("SOURCE: :")`, das der Worker füllt: + +1. **Lies den Source-Range** in der deklarierten Depth (§3.3); Default + für Ports ist `full`. +2. **Zeichne Proof-of-Read auf** für die Source-File im deklarierten + Line-Range (§7.1). +3. **Ersetze `todo!(...)` mit dem Body**, die umgebende Signatur + exakt beibehaltend. Die Signatur kam von PP-16; der Worker + ändert sie nicht ohne Iron-Rule-amendierende RFC. +4. **Quote den Source-Line-Range im Commit-Message-Body** (per + `autoattended-orchestrator-spec.md` §5.6). +5. **Verifiziere die SHA-256 des Skeletts gegen den gepinnten Wert** + beim Arbeitsstart. Wenn das Skelett gedriftet ist (z. B. ein + Sibling-Worker hat es angefasst), MUSS der Worker STOPPEN und + einen `EXTERNAL_DEPENDENCY`-Blocker in + `META/REQUESTS-FROM-AGENTS.md` filen. + +### §13.3 Verboten im Skeleton-Fill-Mode + +- Eine Signatur ändern, die das Skelett liefert (RFC nötig). +- Ein `todo!()` hinzufügen, das NICHT vom Skelett kam (würde + PP-16s Source-Annotation-Disziplin umgehen). +- Einen `// SAFETY:`-Kommentar von einem `unsafe`-Block entfernen + (PP-13 wird ablehnen; PP-16 hat ihn aus einem Grund geschrieben). +- Bodies in einer File außerhalb von `skeleton_output_path` füllen + (§5.1 Unique-File-Write-Disziplin). + +### §13.4 Required Reading-Depth + +| Source-Art | Minimale Depth | +|---|---| +| `SOURCE: :` (die zu portierende Funktion) | `full` für den genannten Line-Range | +| `UNSAFE-SOURCE: :` | `thorough` für den genannten Range UND die Caller der Funktion | +| Die Skelett-Datei selbst | `read` (um die umgebende Signatur zu bestätigen, in die du füllst) | +| `META/INVARIANTS.md` | `thorough` (per §3.3) | + +### §13.5 Validierungs-Regeln + +| Regel | Beschreibung | Severity | +|---|---|---| +| `FILL-001 source-range-read` | Jedes `todo!("SOURCE: P:L-M")`, das der Worker füllt, MUSS im Proof-of-Read des Workers mit `file=P, lines die L-M abdecken, depth=full` auftauchen. | ERROR | +| `FILL-002 skeleton-sha-pin` | Worker MUSS die SHA-256 des Skeletts gegen den Wert verifizieren, den PP-16 gepinnt hat, beim Arbeitsstart. Drift → STOP + `EXTERNAL_DEPENDENCY`. | ERROR | +| `FILL-003 signature-preserved` | Worker DARF NICHT eine Signatur ändern, die das Skelett liefert, ohne gemergten RFC. Diff-Inspektion durch PP-15 setzt durch. | ERROR | +| `FILL-004 unsafe-safety-preserved` | `// SAFETY:`-Kommentare auf `unsafe`-Blöcken vom Skelett MÜSSEN in den gefüllten Body überleben. PP-13 setzt durch. | ERROR | +| `FILL-005 no-new-todo` | Worker DARF NICHT neue `todo!()`-Calls einführen; jedes `todo!()` in der gefüllten Datei MUSS auf einen Skelett-Eintrag zurückverfolgbar sein. | ERROR | + +### §13.6 Definition von Fertig (Skeleton-Fill) + +- [ ] Jedes `todo!()` in den zugewiesenen Skelett-Datei(en) ist ersetzt. +- [ ] Keine neuen `todo!()` eingeführt (FILL-005). +- [ ] Skelett-SHA beim Start gematcht; finale SHA different (FILL-002). +- [ ] Proof-of-Read deckt jede `SOURCE:`-Annotation ab (FILL-001). +- [ ] Alle Signaturen erhalten (FILL-003). +- [ ] Alle `// SAFETY:`-Kommentare erhalten (FILL-004). +- [ ] Tier-1-Toolchain grün auf gefüllter Datei (§8.1). +- [ ] Status-File geschrieben per `autoattended-orchestrator-spec.md` §9.1. + +--- + +## §14 Reading-Phasen (orthogonal zu Depth) + +> Die Reading-Depth-Ladder in §3 sagt **wie viel** einer Datei du +> abdeckst. Die Reading-Phasen in dieser Section sagen **was du +> tust** mit dem, was du abgedeckt hast. Beides braucht's, damit ein +> Read als complete zählt. + +### §14.1 Die vier Phasen + +| # | Phase | Frage, die sie beantwortet | Output den der Worker produzieren können MUSS | +|---|---|---|---| +| 1 | **Survey** | „Was ist in dieser Datei? Welche Form hat sie?" | Section-Liste mit Zeilennummern; File-Shape (N Sections, K LOC, Language); Top-Level-Headline. | +| 2 | **Evaluation** | „Was davon matters für meine Aufgabe?" | Eine Relevanz-Map: welche Sections / Line-Ranges sind relevant fürs aktuelle Bundle, priorisiert P0 / P1 / unused. | +| 3 | **Kritische Findings** | „Was ist falsch, fehlt, widerspricht sich, ist gedriftet?" | Eine typisierte Finding-Liste (Severity P0 / P1) — Iron-Rule-Verletzungen, Spec-vs-Source-Drift, fehlende Sections, Anchors die nicht mehr resolven, tote Referenzen. | +| 4 | **Internalize** | „Kann ich darauf handeln ohne nochmal zu lesen?" | LD-3 bestehen (3-Section-Namens-Challenge), LD-4 (Negative-Knowledge), LD-5 (Line-Range-Quote). Kann faithful paraphrasieren; kann beantworten „was steht NICHT in dieser Datei?". | + +Phasen-Reihenfolge ist typisch **Survey → Evaluation → Kritische +Findings → Internalize**, aber Findings tauchen oft WÄHREND +Internalize auf (der Akt des Internalizens enthüllt Widersprüche). +Ein vollständiges Reading erreicht alle vier; ein partial Reading +stoppt früher und MUSS das deklarieren. + +### §14.2 Phase × Depth-Matrix + +| Depth | Survey | Evaluation | Kritische Findings | Internalize | +|---|:-:|:-:|:-:|:-:| +| `grep` (anti) | partial | nein | nein | nein | +| `sed-partial` / `head-only` (anti) | partial | partial | nein | nein | +| `skim` | ja | partial | partial (nur per-Section) | nein | +| `read` | ja | ja | partial | partial | +| `thorough` | ja | ja | **ja** | **ja** | +| `troubleshooting` | ja | ja | ja (fokussiert auf den Bug) | ja (fokussiert) | +| `fan-out` | ja (per-File flach) | ja | ja | partial (per File) | +| `truncated:head-N` / `truncated:tail-N` | partial | partial | nein | nein | + +Nur `thorough` und `troubleshooting` (innerhalb des fokussierten +Scopes) erreichen die volle Ladder. `fan-out` erreicht Internalize +*per-File*, aber der Inventur-Output ist die Synthese — der Worker +MUSS jede File im Inventar als noch-`thorough`-bedürftig behandeln +bevor er auf sie handelt. + +### §14.3 Phasen-Outputs in der status.json des Workers + +Das `proof_of_read`-Schema (§7.1) wird um ein `phase_reached`-Feld +erweitert, das die höchste completed-Phase pro File benennt: + +```json +{ + "file": "META/INVARIANTS.md", + "sha256": "fa39a3...", + "lines": 412, + "depth": "thorough", + "phase_reached": "internalize", + "phases_evidence": { + "survey": "9 Sections; INVARIANTS-kanonische Struktur erkannt", + "evaluation": "§BBB + §UPSERT-PATTERN sind load-bearing für dieses Bundle", + "critical_findings": "§UPSERT-PATTERN Zeile 187 widerspricht der customer.ttl ogit:CustomerWriter mandatory-attributes (filed REQUESTS-FROM-AGENTS.md#A4-2026-05-18T14:22)", + "internalize": "kann LD-3/4/5 literal beantworten" + } +} +``` + +Die `phases_evidence`-Map ist OPTIONAL wenn `phase_reached=survey` +(kein Evidence über die Section-Liste hinaus required), WIRD aber +required bei `phase_reached >= evaluation`, weil diese Phase +Judgment claimt. + +### §14.4 Mapping Lie-Detector-Tests auf Phasen + +Jeder LD-1..LD-5-Test (§4.1) probt eine spezifische Phase. Der +Meta-Agent SOLLTE den Spot-Check über die vier Phasen rotieren, +damit Workers nicht gamen können, welche Phase zu faken ist. + +| Test | Probt Phase | Was eine passing Answer beweist | +|---|---|---| +| LD-1 Sentinel-Token | **Survey** des Briefs | Der Worker hat den Brief tatsächlich in den Context geladen. | +| LD-2 Proof-of-Read mit SHA | **Survey** der File | Der Worker accessed die File am deklarierten Content. | +| LD-3 3-Sections-Namens-Challenge | **Survey + Evaluation** | Der Worker kann Struktur lokalisieren UND wählte welche Sections zu beachten. | +| LD-4 Negative-Knowledge-Test | **Internalize** | Der Worker hat ein faithful Mental-Model gebaut — kann beantworten „was steht NICHT in dieser Datei" ohne zu halluzinieren. | +| LD-5 Line-Range-Quote | **Internalize + Kritische Findings** | Der Worker kann verbatim recallen UND Drift zwischen Recall und Source detektieren. | + +Ein Worker, der `phase_reached=internalize` deklariert, MUSS LD-3, +LD-4 UND LD-5 bestehen können. Spot-Check-Failure bei einer +geclaimten Phase ⇒ Phase-Claim wird abgelehnt und der Proof-of-Read +wird auto-downgraded auf die höchste nachweisbar bestandene Phase. + +### §14.5 Per-File-Required-Phase nach File-Art + +Unterschiedliche File-Arten BENÖTIGEN unterschiedliche Minimum- +Phasen. Das Worker-Brief MUSS die Per-File-Phase-Requirements +zusammen mit den Depth-Requirements deklarieren: + +| File-Art | Minimum-Depth | Minimum-Phase | +|---|---|---| +| `META/INVARIANTS.md` | `thorough` | **internalize** | +| `CLAUDE.md` / `BOOT.md` / RFCs | `thorough` | **internalize** | +| Memory-Files (`CONTEXT.md` / `JOURNAL.md` / `TODO.md`) | `thorough` | **internalize** | +| Spec-Files fürs Bundle (z. B. TTL, OpenAPI, JSON-Schema) | `full` (read) | **internalize** | +| Reference-Source für Ports | `full` | **internalize** | +| Skeleton-Files die der Worker füllt | `read` | **evaluation** | +| Sibling-Bundle-Files (read-only Context) | `skim` | **evaluation** | +| Files referenziert für allgemeine Orientation | `skim` | **survey** | +| Files referenziert nur für Symbol-Lookup | `grep` | **survey** | + +Wenn ein Worker bei einer Phase unter dem Required stoppt, zählt +der Read NICHT als complete — selbst wenn die Depth ausreichend +war. Beide Achsen müssen die Bar klären. + +### §14.6 Kritische-Findings-Eskalation + +Findings produziert in Phase 3 (Kritische Findings) werden nach +Severity geroutet: + +| Finding-Severity | Filed wo | Worker-Aktion | +|---|---|---| +| P0 — Iron-Rule-Verletzung im Input, Spec-vs-Source-Widerspruch, gebrochener Anchor der das Bundle betrifft | `META/REQUESTS-FROM-AGENTS.md` mit Blocker-Typ aus §5.1; Worker idled | STOP Arbeit; nicht zum Commit weitergehen | +| P1 — kleinere Inkonsistenz, tote Referenz außerhalb des Bundle-Scopes, Typo, stale Comment | `Altlasten.md` / `TECH_DEBT.md`-Zeile mit der Bundle-ID des Workers | Arbeit fortsetzen; der Orchestrator triaged später | +| INFO — Observation die das Bundle nicht betrifft | Notes-Feld in `status.json`; nirgendwo sonst filed | Arbeit fortsetzen | + +Ein Worker der internalized, aber NICHT ein P0-Finding escaliert +ist in Verletzung: Missing-Escalation ist selbst ein P0-Finding, +das PP-13 catchen wird (Anti-Pattern AP1 — „silent Fallback der +Errors schluckt" — generalisiert hier als „silent Skim der +Findings schluckt"). + +### §14.7 Validierungs-Regeln + +| Regel | Beschreibung | Severity | +|---|---|---| +| `PHASE-001 phase-declared` | Jeder `proof_of_read`-Eintrag MUSS ein `phase_reached`-Feld enthalten. Absent ⇒ behandelt als `phase_reached=survey` (der schwächste Claim). | ERROR | +| `PHASE-002 phase-monotonic-with-depth` | Ein `phase_reached`-Claim MUSS konsistent mit der §14.2-Matrix sein. Z. B. `depth=grep` + `phase_reached=internalize` ist invalid. | ERROR | +| `PHASE-003 phase-evidence-required` | Wenn `phase_reached >= evaluation`, MUSS die `phases_evidence`-Map vorhanden sein mit non-empty Entries für jede geclaimte Phase. | ERROR | +| `PHASE-004 file-kind-phase-bar` | Wenn das Worker-Brief eine Minimum-Phase per §14.5 für eine File deklariert, MUSS der `phase_reached` des Workers für diese File ≥ dem deklarierten Minimum sein. | ERROR | +| `PHASE-005 critical-findings-routed` | P0-Findings produziert während Phase 3 MÜSSEN nach `META/REQUESTS-FROM-AGENTS.md` filed sein BEVOR der Worker irgendwelchen Code commited, der vom betroffenen Input abhängt. | ERROR | +| `PHASE-006 internalize-passes-LD-3-4-5` | Ein Claim von `phase_reached=internalize` MUSS Spot-Checks von LD-3, LD-4 und LD-5 in Rotation überleben. Failure ⇒ Auto-Downgrade auf höchst-bestandene Phase. | ERROR | + +### §14.8 Definition von Fertig (Per-File-Read) + +Ein Read einer einzelnen File ist complete wenn ALLE: + +- [ ] Depth aus §3 auf dem deklarierten Level. +- [ ] Phase aus §14.1 erreicht das required Minimum per §14.5. +- [ ] `proof_of_read`-Eintrag enthält `sha256`, `lines`, `depth`, + `phase_reached` und `phases_evidence` für `evaluation+`. +- [ ] Jedes P0-kritische-Finding ist filed bevor der Worker commited. +- [ ] LD-3 / LD-4 / LD-5-Spot-Checks bestanden wenn + `phase_reached=internalize` geclaimt ist. + +--- + +## §15 Kognitive Anti-Patterns (CA1..CA4) + +> Wo AP1..AP9 (§9) **Output**-Probleme fangen — der Code selbst sieht +> falsch aus — fangen CA1..CA4 **Cognition**-Probleme — die Art wie +> der Worker zum Output gekommen ist, ist falsch. CA-Findings tauchen +> oft zur gleichen Zeit auf wie AP-Findings; die Unterscheidung ist: +> CA-Fixes brauchen Process-Change (Re-Read, Re-Think, Re-Spawn), +> während AP-Fixes manchmal in-place editiert werden können. +> +> Die kognitiven Anti-Patterns sind joint owned: der Meta-Agent spotted +> sie beim PR-Review via Lie-Detector (§4); PP-13 spotted sie beim +> Code-Review durch Korrelation von Commit-Timestamps mit +> Proof-of-Read-Timestamps. + +### §15.1 Die vier kognitiven Anti-Patterns + +| # | Name | Wie es aussieht | Detection-Signature | Counter-Pattern | Severity | +|---|---|---|---|---|---| +| **CA1** | **Cognitive Dissonance** | Worker sieht einen Widerspruch zwischen zwei autoritativen Sources (Spec vs. Reference-Source, INVARIANT vs. Comment, TTL vs. Code) und löst durch Hand-Waving oder Picken eines ohne Investigation. Die Dissonance wird übermalt statt eskaliert. | Output enthält Phrasen wie „I went with", „I chose", „I preferred Y because it feels right / is already there / compiles", ohne entsprechenden `SPEC_SOURCE_MISMATCH`-Eintrag in `META/REQUESTS-FROM-AGENTS.md`. | File `SPEC_SOURCE_MISMATCH` (§5.1) und idle bis der Meta-Agent ein RFC schreibt. Niemals Dissonance unilateral auflösen. | **P0** | +| **CA2** | **Dunning-Kruger Overconfidence** | Worker claimt confident Knowledge in einem Bereich, wo seine tatsächliche Depth shallow ist. Der Agent weiß nicht, was er nicht weiß, also ist sein Sense of Certainty un-kalibriert. Der Output liest sich definitiv, wo der Read dünn war. | Ein `phase_reached=internalize`-Claim (§14), der LD-3 / LD-4 / LD-5-Spot-Checks failt. Eine spezifische numerische Behauptung („62 Lehren", „die Signatur hat 3 Argumente") ohne entsprechenden `proof_of_read`-SHA. Confident Paraphrase, wo die Source eigentlich etwas anderes sagt. | Auto-Downgrade des Phase-Claims auf die höchst-bestandene Phase (per `PHASE-006`). Re-Read mit der proper Depth + Phase. Wenn der Worker weiter übersteuert, route ihn zu einem kleineren Bundle. | **P0** | +| **CA3** | **Kahneman/Tversky Easy-Path (System-1-Short-Circuit)** | Worker pattern-matched Surface-Features des Inputs — „File sieht aus wie ein CRUD-Handler, also muss es ein CRUD-Handler sein" — ohne den System-2-Check zu fahren (Read den tatsächlichen Function-Body, vergleich gegen die Spec). Easy-Path ist am schnellsten wenn Surface die Reality matched, aber verheerend wenn nicht. | Die erste Reply ist plausibel-correct-klingend, aber der Proof-of-Read ist `depth=grep` oder `depth=sed-partial`. Output beschreibt Struktur in generischen Termen („standard Route-Handler", „typische Migration") statt spezifischen Termen („die Function auf Zeilen 47-91 dispatched auf req.path"). | Force System-2: verlange LD-2 (SHA + Line-Count) und LD-5 (Line-Range-Quote) bevor irgendein structural Claim akzeptiert wird. Reading-Depth MUSS mindestens `read` sein; Phase MUSS mindestens `evaluation` sein, bevor irgendein Output, der einen structural Claim macht. | **P0** | +| **CA4** | **Eager Amok** | Worker fängt an Code zu schreiben (oder zu committen, oder zu pushen), bevor die required Reading- + Planning-Phasen complete sind. Enthusiasm rennt der Disziplin voraus. Die Arbeit *sieht* produktiv aus — es gibt einen Diff — aber sie ist auf incomplete Understanding gebaut. | Der erste Code-Write-Timestamp ist vor den `proof_of_read`-SHA-Pin-Timestamps für eine oder mehrere required Files (§14.5). `status.json` zeigt Commits, die landen, während `phase_reached` noch `survey` oder `evaluation` auf Files ist, die bei `internalize` sein sollten. Worker-Narrative springt von Brief-Read zu First-Commit ohne sichtbaren Thinking-Step. | STOP und verlange complete Proof-of-Read für ALLE Files an ihrer required §14.5-Minimum-Phase BEVOR irgendein Commit. Die Iron Rule gilt, egal wie „obvious" die Implementation scheint. | **P0** | + +### §15.2 Warum alle vier P0 sind + +Jedes von CA1..CA4 produziert Output, der *aussieht* als wäre er +richtig. AP1..AP9 produzieren Output, der falsch aussieht (ein +`unwrap_or_default()` ist sichtbar; ein fehlender Parity-Test ist +sichtbar). Kognitive Anti-Patterns produzieren Output, dessen +Correctness komplett auf dem unverifierbaren Claim ruht, dass der +Worker gelesen + verstanden + gedacht hat, bevor er schrieb. Sie +sind P0 weil sie den Trust-Contract brechen, auf den der Meta-Agent +sich verlässt, wenn er Diffs reviewed ohne den Read des Workers zu +wiederholen. + +### §15.3 Joint Ownership: wer fängt was + +| Anti-Pattern | Catch-Site | Detection-Methode | +|---|---|---| +| CA1 Cognitive Dissonance | Meta-Agent (PR-Review) + PP-15 (Cross-Source-Diff) | Grep PR-Commit-Messages nach „I went with" / „I chose" / „preferred" gegen Spec-vs-Source-Mismatch; check `REQUESTS-FROM-AGENTS.md` für Abwesenheit des entsprechenden Blockers. | +| CA2 Dunning-Kruger Overconfidence | Meta-Agent (Lie-Detector-Spot-Check) | Rotate LD-3 / LD-4 / LD-5 auf einem Worker pro Wave; cross-check `phase_reached`-Claims gegen tatsächliches Passing. | +| CA3 Kahneman/Tversky Easy-Path | Meta-Agent (Proof-of-Read-Inspection) + PP-13 (Output-vs-Source-Diff) | Worker-Output liest sich als Paraphrase statt Quote; SHA fehlt; Reading-Depth deklariert inkonsistent mit Output-Claims. | +| CA4 Eager Amok | PP-13 (Commit-Timestamp-Audit) + Meta-Agent (status.json-Ordering-Check) | Commit-Timestamps sind vor den `proof_of_read`-SHA-Pin-Timestamps; `phase_reached` war unter dem required Minimum beim First-Commit. | + +### §15.4 Counter-Patterns: wie ein gesunder Worker sich verhält + +| Anti-Pattern | Healthy-Alternative | +|---|---| +| CA1 | „Ich habe bemerkt, die Spec sagt X aber der existierende Code tut Y. Ich file `SPEC_SOURCE_MISMATCH`. Idle." | +| CA2 | „Ich bin confident über §3 von INVARIANTS.md (depth=thorough, phase=internalize). Ich bin uncertain über §6 (depth=skim, phase=survey). Ich vertiefe §6 bevor ich darüber etwas behaupte." | +| CA3 | „Bevor ich diese File-Struktur beschreibe: hier ist `proof_of_read: { file=X, sha256=..., depth=read, phase_reached=evaluation }`. Die Struktur ist: [spezifische Section-Namen mit Zeilennummern]." | +| CA4 | „Reading-Phase complete auf allen 4 required Files. Kritische Findings filed (Zero P0). Status: `phase_reached=internalize` auf allen Files. JETZT beginne ich den ersten Commit." | + +### §15.5 Validierungs-Regeln + +| Regel | Beschreibung | Severity | +|---|---|---| +| `CA-001 dissonance-escalation` | Wenn der Output eines Workers Spec-vs-Source-Divergenz erwähnt, MUSS `META/REQUESTS-FROM-AGENTS.md` einen entsprechenden `SPEC_SOURCE_MISMATCH`-Blocker-Eintrag enthalten. Abwesenheit ⇒ CA1 P0-Finding. | ERROR | +| `CA-002 confidence-calibration` | Ein `phase_reached=internalize`-Claim, der LD-3 / LD-4 / LD-5-Spot-Check failt ⇒ CA2 P0; Phase-Claim auto-downgraded. | ERROR | +| `CA-003 system-2-required-before-structural-claim` | Jeder structural Claim über den Content einer File (Function-Count, Section-Namen, Signature-Shapes) MUSS preceded sein von einem `proof_of_read`-Eintrag mit `depth >= read` UND `phase_reached >= evaluation`. Sonst CA3 P0. | ERROR | +| `CA-004 read-before-write-ordering` | `status.json` MUSS zeigen, dass alle required-Minimum `proof_of_read`-Einträge timestamped sind BEVOR dem First-Code-Commit-Timestamp für Files an der §14.5-required-Minimum-Phase. Sonst CA4 P0. | ERROR | + +### §15.6 Mindset-Level-Relation zu den vier Savants + +Die vier Savants in `autoattended-orchestrator-spec.md` §4.0 haben +jeweils eine *kognitive* Posture, die einer spezifischen CA +widersteht: + +| Savant | Mindset | Widersteht primär | +|---|---|---| +| PP-13 brutally-honest-tester | „was würde in Production um 3 Uhr morgens brechen, was der Author sich weggeredet hat?" | CA1 (Sich-Wegreden IST Cognitive Dissonance) + CA4 (Production bricht nicht wegen Enthusiasmus) | +| PP-14 convergence-architect | „was könnte das werden, das wir nicht sehen?" | CA3 (Easy-Path schließt Possibilities premature) | +| PP-15 baton-handoff-auditor | „lining sich diese Contracts an den Nähten wirklich auf?" | CA1 (Dissonance versteckt sich oft an Handoffs) | +| PP-16 preflight-drift-auditor | „matched der Plan immer noch das System wie es jetzt gerade ist?" | CA2 (Overconfidence in einen Plan der von der Reality gedriftet ist) | + +Der volle 4-Savant-Pass ist jointly ein Antibody gegen alle vier +kognitiven Anti-Patterns. Eine Wave, die nur einen Savant fährt, +catched nur die CAs, denen der Mindset dieses Savants widersteht; +eine Wave, die das Cooperative Council fährt (Orchestrator-Spec +§15), catched alle vier. + +### §15.7 Definition von Fertig (Kognitive Hygiene) + +Ein Worker besteht das Kognitive-Hygiene-Gate wenn ALLE: + +- [ ] `CA-001` clean: jede Spec-vs-Source-Erwähnung ist matched mit einem filed `SPEC_SOURCE_MISMATCH`-Blocker. +- [ ] `CA-002` clean: jeder `phase_reached=internalize`-Claim überlebt LD-3/4/5-Spot-Checks. +- [ ] `CA-003` clean: jeder structural Claim ist backed durch ausreichenden `proof_of_read`. +- [ ] `CA-004` clean: Read-Timestamps sind vor Commit-Timestamps für alle required-Minimum-Files. +- [ ] Worker hat nicht `I went with` / `I chose` / `I preferred` ausgegeben ohne associated Blocker-Filing. + +--- + +## §16 Adoption-Tiers (Projekt-spezifisch) + +> Specs shippen eine maximalistische Surface, damit jeder +> vernünftige Use-Case gecovered ist. Reale Projekte adopten +> Subsets. Diese Section benennt, was ein Projekt am Tag eins +> adopten SOLLTE (Tier-A), was den Cost nur bei High-Stakes wert +> ist (Tier-B), was Projekt-überschreibbar ist (Tier-C) und was +> ehrlich bekannte Limits hat (Tier-D). +> +> **Ursprung:** Field-Test-Feedback aus einem 12-Agent-Rust-Fan-out +> (Sprints PR-X4 cascade / PR-X10..13 consolidation / PR-X12 codec) +> wo ~1 GB Per-Worker-`target/`-Dirs Universal-Per-Worker-Tier-1- +> Toolchain prohibitiv teuer machten, und wo die fehlende +> Self-Interrupt der `Agent()`-API den §6.1-Detector zu prompt-level +> only zwang. + +### §16.1 Tier-A — am Tag eins adopten (minimum viable) + +Die minimale Surface, die ≥80% des Werts des Frameworks bei ≤20% +des Ceremony liefert. + +| § | Was | Warum Pflicht | +|---|---|---| +| §3 | Reading-Depth-Ladder per File | Wichtigste Anti-Skim-Primitive. | +| §5 | 5 typisierte Stuck-Blocker (`AMBIGUITY` / `MISSING_INVARIANT` / `SPEC_SOURCE_MISMATCH` / `BEHAVIOUR_QUESTION` / `EXTERNAL_DEPENDENCY`) | Ersetzt vages „report under 200 words" mit crispem typisiertem Routing. | +| §7 | `proof_of_read`-Schema in `status.json` | `{file, sha256, lines, depth, phase_reached}`. Trivialer Overhead; großer Audit-Win. | +| §14 | Reading-Phasen per File + §14.5-Per-File-Kind-Minimum | Fängt „Agent claimt confident File-Content ohne zu lesen" — der häufigste Silent-Skim-Failure. | +| §15 | CA1..CA4 kognitive-Anti-Pattern-Framing | Die Linse, durch die ein Savant-Audit schon schaut; billig zu adopten. | +| §15.6 | 4-Savant-Council für P0-Review auf Consolidation-Sprints | High-Leverage-Audit; läuft auf Opus, einmal pro Sprint. | +| §4.1 LD-1 | Sentinel-Tokens | ~50 LoC Ceremony pro Brief; fängt den trivialen „Agent hat das Brief nicht gelesen"-Fall. | +| §6 | Prompt-level Tool-Call-Loop-Instruction (best-effort, siehe §16.4) | Fängt In-LLM-Oszillation, wenn der Worker die Instruction honored. | +| §17 | PR-Lifecycle + Auto-Resolve (Worker erstellt PR, subscribed auf CI-/Review-Events, klassifiziert jedes Event silent-fix / ask-user / skip) | Das „done" eines Workers ist PR gemerged oder PR geschlossen, nicht Branch gepusht. Ohne §17 stoppt das Framework am Push und der Human owned die CI-Loop. | + +### §16.2 Tier-B — adopten nur für Savants / High-Stakes + +Reserviere für Review-Agents (PP-13/14/15/16) und High-Stakes-Worker- +Spawns. Skip für routinemäßige 200-LoC-Skeleton-Fills. + +| § | Was | Wann adopten | +|---|---|---| +| §4.1 LD-3 / LD-4 / LD-5 | 3-Section-Challenge / Negative-Knowledge / Line-Range-Quote | Auf Savants **jede Wave**. Auf routinemäßigen Workers **nur wenn** `phase_reached=internalize` geclaimt wird ODER der Output overconfident wirkt. Nicht als routinemäßige Per-Worker-per-Wave-Rotation. | +| §13 Skeleton-Fill (Protokoll A) | `todo!("SOURCE:")`-Marker erzwingen Read-Before-Fill | High-Stakes-Ports (Python→Rust-Transcode, x265→Rust-Codec) wo Source-Fidelity matters. Skip für Greenfield-Prose-Features. | +| Volle §10 / §13.6 / §14.8 / §15.7 DoD-Checklisten | Per-PR-DoD-Verifikation | Per-Sprint, nicht per-PR, bis v0.2-Prune. Siehe §16.6. | + +### §16.3 Tier-C — Projekt-überschreibbare Defaults + +Defaults, die diese Spec empfiehlt, aber die reale Projekte oft +überschreiben. Der Override SOLLTE in `INVARIANTS.md` des Projekts +dokumentiert sein. + +| § | Default | Override-Scenario | Typische Projekt-Wahl | +|---|---|---|---| +| §8.1 Tier-1-Toolchain | Coordinator-side (PP-13 fährt einmal auf konsolidiertem Diff) | Kleiner Workspace wo Per-Worker-Compile schnell ist | `tier_1_runner: per-worker` | +| §4.1 LD-3/4/5-Rotation | Spot-Check nur auf Savants + overconfident Workers | Projekt will Per-Worker-per-Wave-Rotation | `lie_detector_rotation: per-worker-per-wave` | +| §13 Skeleton-Fill | Opt-in pro Sprint via `preflight.skeleton: enabled` | Greenfield-Code ohne SOURCE-Referenz | `preflight.skeleton: disabled` (Default für Greenfield) | +| §14.5 Per-File-Kind-Minimum-Phase | INVARIANTS.md / Spec / Reference = internalize | Projekts INVARIANTS lebt woanders oder existiert nicht | Projekt-definiertes `file_kind_phase_map` in `INVARIANTS.md` | + +### §16.4 Tier-D — known-limited (ehrliche Caveats) + +| § | Caveat | +|---|---| +| §6.1 In-Loop-Tool-Call-Detection | Das aktuelle Claude Code `Agent()`-Tool exponiert KEINE Interrupt-API. Der Detector lebt nur als prompt-level Instruction. Fängt In-LLM-Oszillation; fängt NICHT OS-Level-Hangs. Wrap jeden Spawn in einen Wall-Clock-Timeout auf der Orchestrator-Schicht für OS-Hangs. | +| §8.1 Per-Worker-Tier-1 | Auf Rust-Workspaces mit ~1 GB Per-Worker-`target/`-Dirs ist Per-Worker-Tier-1 bei 12-Agent-Fan-out disk-prohibitiv. Nutze den §16.3-Coordinator-side-Override. | +| §4.1 LD-1-Sentinel-Tokens | Slight Per-Brief-Ceremony (~50 LoC). Projekt DARF das Token auf Orchestrator-Level templaten statt per-worker Brief, um den Cost zu amortisieren. | +| Spec-Line-Count bei v0.1.0 DRAFT | Diese Spec ist intentional maximalistisch für die erste Revision. Plan einen v0.2-Prune der vier DoD-Checklisten (§10 / §13.6 / §14.8 / §15.7) nachdem ein realer Sprint mit dem Framework gelaufen ist — behalte nur Items, die Field-Bugs gefangen haben. | + +### §16.5 Sprint-by-Sprint-Adoption Worked-Example + +Ein 12-Agent-Rust-Fan-out mit Cascade + Codec + Consolidation: + +| Sprint | Tier-A adopted | Tier-B adopted | Tier-C Overrides | +|---|---|---|---| +| PR-X4 Splat-Cascade (12 Workers, interne Refactor) | full | LD-1-Sentinel only; skip LD-3/4/5 auf routinemäßigen Workers | `tier_1_runner: coordinator` | +| PR-X12 Codec (Rust-Port von x265-Reference) | full | volle §13-Skeleton-Fill (High-Stakes-Source-Fidelity-Port); LD-3/4/5 auf Consolidation-Worker | `tier_1_runner: coordinator` | +| PR-X10+11+13 Consolidation-Review (Savant-Pass) | n/a (Review-Pass) | volles §15.6-Vier-Savant-Council auf Opus | n/a | + +### §16.6 v0.2-Prune-Targets (slim wenn field-tested) + +Nach einem vollen Sprint mit dieser Spec, slim die folgenden Sections +auf Items, die field-tested Catches produziert haben: + +| Section | Aktuelle Items | Slim-Target | +|---|---:|---| +| §10 Definition von Fertig | 13 | behalte ~5, die echte Bugs fangen | +| §13.6 Skeleton-Fill-DoD | 7 | behalte ~3 | +| §14.8 Per-File-Read-DoD | 5 | behalte ~3 | +| §15.7 Kognitive-Hygiene-DoD | 5 | behalte ~3 | +| §8.1 / §8.2 / §8.3 Tier-Tabellen | volle Per-Language-Matrix | slim auf Entries, die tatsächlich im Field laufen | + +Ziel: Spec-Line-Count fällt 30-50% bei v0.2, während die Patterns +erhalten bleiben, die echte Bugs gefangen haben. + +--- + +## §17 PR-Lifecycle + Auto-Resolve + +> Workers stoppen nicht bei „Branch gepusht". Die Done-Bedingung +> eines Workers ist **PR gemerged** ODER **PR geschlossen durch +> Orchestrator/Human als FAIL** — der Lifecycle reicht über CI- +> Iteration und Review-Comment-Resolution. Diese Section +> spezifiziert, was zwischen dem ersten Push des Workers und einem +> dieser terminalen States passiert. +> +> **Tier:** Tier-A per §16.1 für jeden Sprint, der PRs öffnet. +> **Transport:** `mcp__github__create_pull_request` + +> `mcp__github__subscribe_pr_activity` + `git push` für Fixes; +> Events arrive als ``-Envelopes (siehe +> `agent-coordination-mcp-spec.md` §3.3). + +### §17.1 Der erweiterte Worker-Lifecycle + +``` +READ → WRITE → TEST → COMMIT → PR → SUBSCRIBE → [AUTO-RESOLVE-LOOP] → MERGE | FAIL + ↑ ↓ + │ Event arrival + │ classify (§17.4) + │ silent-fix | ask-user | skip + └ bei silent-fix: Commit pushen +``` + +Steps 1-4 sind das existierende §13.2-Work-Pattern. Steps 5-7 sind +neu. MERGE / FAIL sind terminal — Worker schreibt seine finale +`status.json` und ruft `mcp__github__unsubscribe_pr_activity`. + +### §17.2 PR-Erstellung (Step 5) + +Nach dem First-Push ruft der Worker: + +``` +mcp__github__create_pull_request( + owner=, + repo=, + head=, # z. B. claude/wave-12-A4 + base=, # aus Sprint-Header + title=": (WT-NN)", + body="""\ +## Bundle-Ownership +{{ownership_table}} + +## Status +{{status_json_excerpt}} + +## Proof-of-Read +{{proof_of_read_summary}} + +## Sentinel +{{sentinel_token}} +""", + draft=, # per Sprint-Header + labels=["sprint-", "bundle-"] +) +``` + +Der PR-Body MUSS das Sentinel-Token literal enthalten. Der +Orchestrator kann offene PRs nach Sentinel-Presence greppen als +schneller LD-1-Check (per `PR-007`). + +### §17.3 Subscription (Step 6) + +Innerhalb von 60 Sekunden nach PR-Erstellung MUSS der Worker rufen: + +``` +mcp__github__subscribe_pr_activity(owner, repo, pullNumber) +``` + +Der Worker tritt dann in einen Event-driven Idle-State. Events +arrive als ``-Envelopes mit CI-Run- +Completions, Review-Comments, PR-State-Changes (merged/closed) +und Label-Changes. + +### §17.4 Der Drei-Optionen-Event-Handling-Decision-Tree + +Für jedes Event MUSS der Worker in eine von drei Optionen +klassifizieren (matched das CCA2A-Handling-Protokoll aus dem +governing Prompt-Template): + +| Option | Wann | Aktion | +|---|---|---| +| **1. Silent-Fix** | Confidence hoch; Fix in-scope des Worker-Bundles; Fix widerspricht nicht Spec / INVARIANTS | Read failing CI-Logs / Review-Comment voll (§3-Depth-Ladder, §14.5-Minimum-Phase für Log-Files: `read` + `phase=evaluation`); make the Fix; push Commit; update `status.json`; continue idle for next Event. Reply auf PR NUR wenn der Fix eine spezifische Review-Question resolved. | +| **2. Ask-User (file PROPOSAL)** | Ambiguity (CA1 / CA3 Trigger); cross-bundle Implikationen; Spec-vs-Comment-Widerspruch; Reviewer fragt „should X be Y or Z?" | Append `Kind=PROPOSAL`-Eintrag an `AGENT_LOG.md` (`agent-coordination-mcp-spec.md` §1.3) mit der Question; idle bis Orchestrator/User antwortet via `ANSWERS-TO-AGENTS.md`. Pushe KEINE spekulativen Fixes. | +| **3. Skip-Silently** | Duplicate Event; stale CI; „LGTM" / Approval; Bot-Comment (Dependabot et al); Event addressed in einer vorigen Iteration | Record die Classification auf `AGENT_LOG.md` (`Kind=DECISION`, Body: `skip-silent: `); continue idle. Reply NICHT auf den PR. | + +### §17.5 Event-Taxonomie + Default-Classification + +| Event-Klasse | Spezifisches Signal | Default-Option | Confidence required | +|---|---|---|---| +| CI Lint-Failure | rustfmt / clippy single-line | silent-fix | high | +| CI Typecheck-Failure | single Error, in-Bundle | silent-fix | high | +| CI Typecheck-Failure | Error in Shared-Zone | ask-user (cross-bundle) | n/a | +| CI Test-Failure | Regression im eigenen Bundle; Root-Cause aus Log identifizierbar | silent-fix | medium | +| CI Test-Failure | Regression anderswo | ask-user | n/a | +| CI Build-Time-out | first occurrence | skip-silent (re-CI); wenn recurring → ask-user | n/a | +| CI Dependency-Resolve-Failure | neue transitive Dep, fehlender Crate, Version-Konflikt | ask-user (`EXTERNAL_DEPENDENCY`-Blocker) | n/a | +| CI OOM | first occurrence | ask-user (Orchestrator allocated Memory) | n/a | +| Review-Comment „könntest du auch X" | X ist in-Bundle | silent-fix | medium | +| Review-Comment „should this be X or Y?" | Ambiguity | ask-user (`AMBIGUITY`-Blocker) | n/a | +| Review-Comment „blocking — need RFC" | Spec-vs-Impl-Mismatch | ask-user (`SPEC_SOURCE_MISMATCH`-Blocker) | n/a | +| Review-Comment „LGTM" / Approval | terminal-success-adjacent | skip-silent | n/a | +| Bot-Comment (Dependabot, codecov, sonarcloud, …) | informational | skip-silent | n/a | +| @-Mention an spezifischen Human-User | gehört einem Menschen | skip-silent (reply nicht) | n/a | +| PR merged | terminal | schreib `outcome=SUCCESS`, unsubscribe, exit | n/a | +| PR geschlossen ohne Merge | terminal (Orchestrator/Human FAIL'd es) | schreib `outcome=FAIL`, unsubscribe, exit | n/a | + +### §17.6 Budget + Termination + +| Limit | Default | Bei Überschreitung | +|---|---|---| +| Max Auto-Fix-Iterationen pro PR | 5 | Worker emittiert `outcome=FAIL`, Body: `auto-resolve-budget-exhausted`; Orchestrator entscheidet next | +| Max Wall-Clock pro PR | 24 h (Projekt-konfigurierbar) | Same as above | +| Per-Iteration-Token-Cost-Cap | per §16-Model-Stylesheet | Escalate zu Opus nur wenn Stylesheet allows | +| Aufeinanderfolgende `skip-silent`-Events | 20 | Worker schreibt `STATUS`-Eintrag, der die Silence notes, und re-subscribed (PR ist in Quiet-State) | + +### §17.7 Kognitive Anti-Patterns erweitern sich auf Auto-Resolve + +Jedes CA1..CA4 aus §15 gilt für jede Auto-Resolve-Iteration, nicht +nur die initiale Implementation. Validation-Rules CA-001..CA-004 +gelten unverändert. PR-005 unten erweitert die Cross-Bundle-Rule. + +| Anti-Pattern | Auto-Resolve-spezifische Form | +|---|---| +| CA1 Cognitive Dissonance | Worker liest ein Review-Comment, das INVARIANTS widerspricht, und „fixt einfach per Comment" ohne `SPEC_SOURCE_MISMATCH` zu filen. | +| CA2 Dunning-Kruger Overconfidence | Worker pushed einen Auto-Fix ohne `proof_of_read` auf das Failing-Log; claimt `internalize` des Failures ohne LD-Evidence. | +| CA3 Kahneman/Tversky Easy-Path | Worker pattern-matched „CI rot → push den gleichen Fix wie letztes Mal" ohne das tatsächliche Failure zu lesen. | +| CA4 Eager Amok | Worker pushed einen Fix bevor das volle CI-Log gelesen ist; commited bevor §17.4-Classification complete. | + +### §17.8 Wire-Format-Integration + +Jede Auto-Resolve-Aktion emittiert einen Eintrag auf `AGENT_LOG.md` +per `agent-coordination-mcp-spec.md` §1.3: + +```markdown +## 2026-05-19T08:14 — DECISION[INFO]: A4 silent-fix clippy::redundant_clone (sonnet, claude/wave-12-A4) + +**Author:** A4 +**Kind:** DECISION +**Severity:** INFO +**Refs:** PR-#427 commit-fa00b1c +**Proof-of-read:** +- file=ci/clippy-log.txt sha256=8e1a... lines=47 depth=full +- file=src/customer/master.rs sha256=4c0b... lines=312 depth=read + +--- + +Clippy hat ein `Vec.clone()` auf Zeile 187 geflaggt. Reviewed die +Call-Site; der Clone war Leftover aus einem prior Refactor. +Ersetzt mit `&borrow`. Commit fa00b1c gepusht. Awaiting re-CI. +``` + +Auto-Resolve-Aktionen sind first-class auf dem Wire-Format — der +Orchestrator und jede subscribed Sibling-Session sehen sie in +Echtzeit. + +### §17.9 Validierungs-Regeln + +| Regel | Beschreibung | Severity | +|---|---|---| +| `PR-001 worker-created-PR` | Innerhalb von 30 Minuten nach dem First-Push MUSS der Worker einen PR via `mcp__github__create_pull_request` erstellt haben. | ERROR | +| `PR-002 subscription-active` | Innerhalb von 60 Sekunden nach PR-Erstellung MUSS der Worker `mcp__github__subscribe_pr_activity` gerufen haben. | ERROR | +| `PR-003 max-iterations` | Auto-Resolve DARF §17.6-Budget NICHT überschreiten ohne Escalation. Budget-Exhaustion ⇒ `outcome=FAIL`, Reason `auto-resolve-budget-exhausted`. | ERROR | +| `PR-004 cognitive-hygiene-extends` | CA-001..CA-004 (§15.5) gelten unverändert für jede Auto-Resolve-Iteration, nicht nur initiale Implementation. | ERROR | +| `PR-005 cross-bundle-fix-forbidden` | Ein Worker DARF NICHT Commits pushen, die Files außerhalb seines Bundles anfassen, auch nicht während Auto-Resolve. Cross-Bundle-Failures escalieren via `AGENT_LOG.md` `Kind=PROPOSAL`. | ERROR | +| `PR-006 event-classification-recorded` | Jedes Event, das der Worker received, MUSS per §17.4 klassifiziert und die Classification an `AGENT_LOG.md` (`Kind=DECISION`) appended sein. | ERROR | +| `PR-007 sentinel-in-pr-body` | Der PR-Body MUSS das Sentinel-Token des Workers literal enthalten. | ERROR | +| `PR-008 unsubscribe-on-terminal` | Bei PR-Merge oder -Close MUSS der Worker `mcp__github__unsubscribe_pr_activity` rufen bevor er seine terminale `status.json` schreibt. | ERROR | +| `PR-009 no-reply-on-skip` | Eine `skip-silent`-Classification DARF NICHT einen Reply auf dem PR produzieren (avoid PR-Noise-Pollution von Agents). | WARNING | + +### §17.10 Definition von Fertig (Auto-Resolve) + +Ein Worker besteht das Auto-Resolve-Gate wenn ALLE: + +- [ ] PR erstellt (`PR-001`). +- [ ] Innerhalb 60s subscribed (`PR-002`). +- [ ] Jedes Event per §17.4 klassifiziert und auf `AGENT_LOG.md` aufgezeichnet (`PR-006`). +- [ ] Keine Cross-Bundle-Fix-Commits (`PR-005`). +- [ ] CA-001..CA-004 clean auf jeder Iteration (`PR-004`). +- [ ] Sentinel-Token im PR-Body (`PR-007`). +- [ ] Auto-Resolve-Budget nicht überschritten ohne Escalation (`PR-003`). +- [ ] Unsubscribed und terminale `status.json` geschrieben (`PR-008`). +- [ ] Terminal-Outcome ist `SUCCESS` (PR merged) ODER `FAIL` (PR closed without merge ODER Budget exhausted) ODER `RETRY` (Worker hat einen Blocker filed und wartet auf Orchestrator-Antwort). + +### §17.11 Adoption-Tier-Placement + Overrides + +Diese Section ist **Tier-A** (Pflicht) per §16.1 für jeden Sprint, +der PRs öffnet. Zwei Projekt-Overrides sind möglich (Tier-C): + +| Setting | Default | Override-Scenario | Override-Wert | +|---|---|---|---| +| Per-Worker-PR-Creation (§17.2) | Jeder Worker öffnet seinen eigenen PR | Projekt prefers a single Orchestrator-Consolidation-PR per Wave (per `autoattended-orchestrator-spec.md` §3.4) | `pr_ownership: orchestrator-consolidation` in `INVARIANTS.md` | +| Subscription-Transport (§17.3) | `mcp__github__subscribe_pr_activity` natives MCP | Environment ohne GitHub-MCP (offline, rate-limited, non-GitHub-Host) | `pr_event_transport: polling` (60s-Interval via `mcp__github__pull_request_read`) | + +Tier-D ehrlicher Caveat: `subscribe_pr_activity` braucht Netzwerk + +GitHub-API-Rate-Budget (5000 req/h authenticated). Für 12-Agent- +Fan-outs, die long Auto-Resolve-Loops fahren, monitor Rate-Limit- +Consumption; der Orchestrator DARF Subscriptions auf sich selbst +konsolidieren (eine Subscription pro PR mit dem Orchestrator +re-broadcasting Events an interested Workers) um das API-Budget +zu amortisieren. + +--- + +*Ende der Datei anti-skim-agent-spec.md.* diff --git a/.claude/ATT/DE/autoattended-orchestrator-spec.md b/.claude/ATT/DE/autoattended-orchestrator-spec.md new file mode 100644 index 0000000000..73554ac8cb --- /dev/null +++ b/.claude/ATT/DE/autoattended-orchestrator-spec.md @@ -0,0 +1,1257 @@ +# Autoattended-Orchestrator-Spezifikation + +> **Sprache:** Deutsch · siehe `../autoattended-orchestrator-spec.md` für die englische Quellfassung. +> +> **Status:** DRAFT · **Version:** 0.1.0 · **Stand:** 2026-05-17 +> **Format:** NLSpec (nach [strongdm/attractor](https://github.com/strongdm/attractor)) +> **Substrat:** Designed um auf `attractor-spec.md` zu komponieren; läuft +> als der `house`-shaped `manager_loop`-Node einer Attractor-Pipeline. + +--- + +## §1 Überblick + +### §1.1 Zweck + +Der Autoattended-Orchestrator fährt Wave-basierte Fan-out- / Fan-in- +Sprints von LLM-Worker-Agents gegen eine Codebase, mit vier +spezialisierten Review-Gates (den „4 Savants") an fixen Lifecycle- +Positionen. Es ist die Projekt-agnostische Distillation des Patterns, +das in +[`AdaWorldAPI/WoA/.claude/v0.1/CLAUDE-CONTEXT.md`](https://github.com/AdaWorldAPI/WoA/blob/main/.claude/v0.1/CLAUDE-CONTEXT.md) +beschrieben und über 26 Repositories am 2026-05-17 gehardent wurde. + +### §1.2 Zentrale Abstraktionen + +- **Wave**: ein paralleler Fan-out von N Worker-Agents (typisch N = 12), + endend in einem einzelnen Consolidation-Commit durch den Orchestrator. + Ein Sprint ist eine Sequenz von Waves. +- **Worker**: ein LLM-Agent-Process der ein **Bundle** von Files + besitzt. Workers laufen isoliert (`isolation: "worktree"`), lesen + nicht den In-Flight-State anderer Workers, koordinieren nur über + den Orchestrator und das File-Blackboard. +- **Savant**: eine von vier spezialisierten Review-Rollen (PP-13 / + PP-14 / PP-15 / PP-16), die den Sprint an fixen Lifecycle-Positionen + gaten. +- **Iron Rule**: eine nicht-überschreibbare Invariante, deklariert in + `INVARIANTS.md`, durchgesetzt von jedem Worker und vom Meta-Agent + beim PR-Review. + +### §1.3 Relation zu `attractor-spec.md` + +Der Orchestrator ist realisierbar als Attractor-Pipeline, deren +DOT-Graph die kanonische Form hat: + +``` +start (Mdiamond) + → preflight-drift-auditor (box, PP-16 Verdict-Gate) + → fan-out (component, N=12) + → fan-in (tripleoctagon, Consolidation-Commit) + → baton-handoff-auditor (box, PP-15) + → brutally-honest-tester (box, PP-13, goal_gate=true, retry_target=fan-out) + → exit (Msquare) +``` + +PP-14 (convergence-architect) läuft bei PRE-PLAN, *außerhalb* des +Graphen, auf dem proposed Sprint-Plan bevor der Graph gerendert wird. + +### §1.4 Was diese Spec NICHT ist + +- Kein Pipeline-Runner. Sie nimmt an, dass attractor-spec.md (oder + ein Equivalent) die Runtime ist. +- Kein Coding-Agent-Loop. Per-Worker-Verhalten ist in + [`anti-skim-agent-spec.md`](./anti-skim-agent-spec.md). +- Kein Koordinations-Transport. Der Cross-Agent-Message-Bus ist in + [`agent-coordination-mcp-spec.md`](./agent-coordination-mcp-spec.md). + +--- + +## §2 Terminologie + +| Begriff | Definition | +|---|---| +| **Sprint** | Eine Sequenz aus einer oder mehreren Waves auf ein einzelnes geplantes Outcome (ein Feature, ein Port, ein Refactor). | +| **Wave** | Ein paralleler Fan-out + Fan-in-Zyklus. | +| **Bundle** | Das Set von Files, das ein Worker besitzt (read-write); deklariert in `META/SPRINT-N-PLAN.md`. | +| **Ownership-Tabelle** | Das Per-Worker-Mapping Bundle → owned-files + read-only-files. | +| **Iron Rule** | Eine nicht-überschreibbare Invariante; Verletzung ist eine P0-Finding. | +| **Goal-Gate** | Ein Node, dessen Verdict SUCCESS sein MUSS damit die Wave exiten kann. Implementiert attractor §3.4. | +| **Savant** | PP-13 / PP-14 / PP-15 / PP-16. Siehe §4. | +| **Meta-Agent** | Der 13. Agent. Besitzt Plan-Review, Inbox-Drain, PR-Review. | +| **REQUESTS-FROM-AGENTS.md** | Die append-only Inbox für Stuck-Worker-Messages. | +| **INVARIANTS.md** | Die Single-Source-of-Truth für Cross-Cutting-Rules. | + +--- + +## §3 Der Wave-Loop + +### §3.1 Sechs Steps + +``` +1. Plan → Orchestrator partitioniert Arbeit in N Bundles +2. Sprint → N parallele Worker-Agents +3. Meta-Review → ein oder mehrere Savant-Agents +4. Fix P0s → Orchestrator wendet Fixes an + verifiziert Tier-1-Gates +5. Commit + PR → Orchestrator (ein PR pro Bundle ODER ein kombinierter PR) +6. Repeat → Orchestrator plant die nächste Wave +``` + +### §3.2 Eine Iron Rule über alle Steps + +Der Orchestrator ist die einzige Rolle, die außerhalb des +zugewiesenen Bundles schreiben darf. Workers bleiben in ihrer Lane. +Savants sind read-only (sie filen Findings; der Orchestrator wendet +Fixes an). + +### §3.3 Wave-Sizing + +| Wave-Size | Worker-Model | Wann nutzen | +|---|---|---| +| 3-6 | Mid-Tier (z. B. Sonnet) | Tight Scope, wohldefinierte Bundles, niedrige Cross-Cutting-Concern | +| 7-12 | Mid-Tier | Die Standard-Wave-Form — die meiste Arbeit passt hier | +| > 12 | Mid-Tier, split | Pre-fan in 2 Waves mit Savant-Pass dazwischen | +| 1-2 (Planning) | Top-Tier (z. B. Opus) | Architektur / Design / Cross-Cutting-Entscheidungen | +| 1 (Savant) | Mid-Tier oder Top-Tier | Per §4 | + +Eine 12-Worker-Wave kostet ungefähr **250.000 bis 350.000 Input- +Tokens** (Bundle-Briefings + Reference-Reads) plus **30.000 bis +50.000 für den Savant-Pass**. Implementierungen MÜSSEN das gegen das +Context-Window-Budget des Models pro Worker tracken und Pläne +ablehnen, deren Per-Bundle-Brief `worker.max_brief_tokens` +übersteigt. + +### §3.4 Der Consolidation-Pass (Step 5 ⇒ atomic Commit) + +Nachdem alle Workers in einer Wave zurück sind, macht der +Orchestrator EINEN Consolidation-Commit der: + +1. Die Commits jedes Bundles in Dependency-Order reinholt +2. Die append-only Merge-Zones (Module-Registry, Integration-Test- + List etc.) auflöst, die in INVARIANTS.md deklariert sind +3. Tier-1-Gates gegen den kombinierten State re-runs (siehe + [`anti-skim-agent-spec.md`](./anti-skim-agent-spec.md)) +4. Einen PR fileed (oder N PRs wenn Bundles wirklich independent) + +Implementierungen DÜRFEN NICHT 12 Mini-Commits am shared Registry-File +erlauben. Worker-Pushes zu Shared-Registry-Files werden auf der +Worker-Layer abgelehnt (per Unique-File-Write-Rule, §5.1). + +--- + +## §4 Die 4 Savant-Slots + +### §4.0 Mindsets (nicht Rollen) + +Jeder Savant ist ein **Mindset** — eine Denkweise, kein Checklist- +Operator. Das Verdict-Vokabular in §4.1 ist der *Output*; das +Mindset in dieser Section ist der *Input*, den der System-Prompt +des Savants aktivieren sollte. + +| Savant | Mindset-Persona | Frame durch den sie lesen | Failure, gegen die sie existieren | +|---|---|---|---| +| **PP-13 brutally-honest-tester** | Der Implementierungs-Principal, der seit zehn Jahren shipped + on-Call macht | „was würde in Production um 3 Uhr morgens brechen, was der Author sich weggeredet hat?" | Self-Deception in Implementation-Quality-Claims | +| **PP-14 convergence-architect** | Der Creative-Design / F&E-Principal, der divergente Möglichkeiten exploriert | „was könnte das werden, das wir nicht sehen? welche latente shared Form wird dupliziert?" | Premature Convergence und versäumte Cross-Slice-Synergien | +| **PP-15 baton-handoff-auditor** | Der DTO- / Interface-Architekt — die Person, die in den Lücken zwischen Komponenten lebt | „lining sich diese Contracts an den Nähten wirklich auf? wessen Hand geht als nächste an den Stab?" | Cross-Boundary-Drift; silent Contract-Shape-Mismatches | +| **PP-16 preflight-drift-auditor** | Der Principal-System-Architekt mit dem vollen System-State im Kopf | „matched der Plan immer noch das System wie es jetzt gerade ist, oder hat sich Main bewegt während wir geplant haben?" | Stale Plans gegen ein bewegtes Main; dropped Requirements | + +Der System-Prompt für jeden Savant SOLLTE mit dem Mindset-Satz +beginnen, nicht mit der Verdict-Checklist. Die Checklist ist ein +Backstop; das Mindset ist die Linse. + +### §4.1 Slot-Tabelle + +| Savant | Phase | Owns | Verdict-Vokabular | +|---|---|---|---| +| **PP-13 brutally-honest-tester** | POST-IMPL | Within-Bundle Compile / Lint / Test / Spec-Match | LAND / HOLD / REJECT | +| **PP-14 convergence-architect** | PRE-PLAN | Divergent Ideation, latente shared Infrastructure | OPPORTUNITY-NOW / WORTH-EXPLORING / DROP (nie REJECT) | +| **PP-15 baton-handoff-auditor** | DURING-IMPL | Cross-Bundle- / Cross-Crate- / Cross-Module-Boundary-Contracts | CATCH-CRITICAL / CATCH-LATENT / CLEAN | +| **PP-16 preflight-drift-auditor** | PRE-SPAWN | Spec-vs-Main-Drift, hand-waved Scope, dropped Requirements | SPAWN-CLEAR / SPAWN-CAUTION / SPAWN-BLOCKED | + +### §4.2 Verdict-Vokabular-Nicht-Überlappung ist eine Design-Invariante + +Jeder Savant hat ein **nicht-überlappendes Verdict-Vokabular**, damit +eine Finding nicht über Phasen wandern kann ohne re-klassifiziert zu +werden. Der Meta-Agent nutzt sein eigenes Vokabular (GO / +GO-WITH-CONDITIONS / BLOCK für Plan-Review; P0 / P1 für PR-Review). + +Wenn ein Savant ein Verdict aus dem Vokabular eines anderen Savants +returned, wird die Finding beim Intake abgelehnt und zurückgeroutet. +Das ist ERROR-Severity in Validation (§7). + +### §4.3 Non-Use → Route-Tabelle + +Jeder Savant-Prompt MUSS explizite „non-use → route to PP-X"-Lines +enthalten. Beispiele: + +- PP-13 findet ein Cross-Crate-Boundary-Issue → route zu **PP-15**. +- PP-15 findet ein Spec-vs-Main-Drift → route zu **PP-16**. +- PP-16 findet ein Within-Bundle-Compile-Error → route zu **PP-13**. +- PP-14 findet irgendetwas Compile-related → route zu **PP-13**. + +### §4.4 PP-13-Spezifika + +Owns die kanonische Toolchain Tier-1+2+3. Tier 1 läuft jeder PR; +Tiers 2+3 Opt-in. Siehe +[`anti-skim-agent-spec.md` §8](./anti-skim-agent-spec.md#8-toolchain-tiers) +für die Language-Adapter-Tabellen. + +Anti-Pattern-Katalog: AP1..AP8. + +### §4.5 PP-15-Spezifika + +Owns Cross-Boundary-Commands: Workspace-Level-Lint/Typecheck, +Public-API-Diff (`cargo public-api`, `api-extractor`, `griffe check`), +Cross-Symbol-Grep, Cross-Repo `git log`, Metadata-Dump. + +Anti-Pattern-Katalog: BAP1..BAP10. Acht Boundary-Klassen: +Module-Module, Crate-Crate, Package-Package, Repo-Repo, Public-API, +DTO/Wire-Format, DB-Schema-to-ORM, CLI-Surface. + +### §4.6 PP-16-Spezifika + +Owns Git + Grep only — `git log master`, `git show`, +`git diff main...HEAD`, list-pull-requests, Grep alter Symbole über +`.claude/plans/` und `.claude/specs/`. Läuft KEIN Compile/Test/Lint. + +Anti-Pattern-Katalog: PD1..PD10. Sechs Achsen: Plan-vs-Main-HEAD, +Plan-vs-Open-PRs, Plan-vs-Recent-Merges, Plan-vs-RFC-Status, +Plan-vs-Invariants, Plan-vs-Spec. + +### §4.7 PP-14-Spezifika + +Owns Surface-Inspection only (z. B. `cargo doc`, `cargo tree`, +`cargo expand`) plus WebSearch / Paper-Search für Cross-Pollination. +Hat KEINE Compile/Test-Gates. Anti-Pattern-Katalog: EP1..EP8. + +--- + +## §5 Worker-Iron-Rules + +### §5.1 Unique-File-Write-Disziplin + +Jeder Worker schreibt in eine **eindeutige neue File** in seinem +Bundle. Nie zwei Workers an der gleichen existierenden File +gleichzeitig. Append-Conflicts sind garantiert wenn verletzt. + +Shared-Merge-Zones (Module-Registry, Integration-Test-List) MÜSSEN +explizit in INVARIANTS.md als **append-only** deklariert sein: jeder +Worker append eine Zeile; der Orchestrator löst Order im +Consolidation-Commit. Worker-Pushes zu undeklarierten Shared-Files +sind ERROR-Severity (§7). + +### §5.2 Worktree-Branches starten von `origin/`, nicht der lokalen Working-Branch + +Eine Worktree-Branch gestartet von einer stale lokalen Working-Branch +verpasst silently Sibling-Worker-Commits. Workers MÜSSEN: + +```bash +git fetch origin +git checkout -b agent/N +``` + +Implementierungen MÜSSEN `isolation: "worktree"` für jeden Worker- +Spawn setzen. + +### §5.3 Atomic Consolidation-Pass + +Siehe §3.4. Der Orchestrator macht EINEN Consolidation-Commit pro +Wave, nicht N Mini-Commits. + +### §5.4 Pre-Wave-Helper-Call-Audit + +Vor dem Fan-out scannt der Orchestrator jedes Bundle nach +uncommitted Helper-Call-Dependencies. Bundles mit non-zero +unresolved-helper-Count benötigen einen **Helper-Hoist** committed +vor dem Fan-out. Sonst inventieren N Workers den gleichen Helper N +verschiedene Wege. Das ist PP-14s Job bei PRE-PLAN. + +### §5.5 Chunking-Disziplin (die load-bearing Iron Rule) + +Files größer als ~150 Zeilen MÜSSEN via `tee -a` in Chunks geschrieben +werden, mit einem Commit pro Chunk. NIEMALS ein 500-Zeilen-Heredoc + +Commit + Push in derselben Runde. Connection-Drops mid-serialize +verlieren Arbeit. + +Der `tee -a`-Loop hat doppelte Pflicht: Chunking (File-Size) UND +Agent-Logging (Status-Zeile an `AGENT_LOG.md` nach jedem Chunk). + +### §5.6 Source-Quoting-Commit-Messages + +Für Ports und Behavior-Preserving-Rewrites: jede Commit-Message +quoted die Source-File + Line-Range für die portierte Funktion. +Reviewer können `grep -rn "Source: " /` machen um jedes +Port-Site zu finden. Ohne das kreucht Behavior-Drift unsichtbar rein. + +--- + +## §6 Sprint-Plan-Format + +### §6.1 Der Plan ist ein DOT-Graph (oder YAML-Mirror) + +Ein Sprint-Plan ist ein DOT-Graph (per attractor-spec.md §2) oder +sein Equivalent in YAML. Die kanonische Wave-Form ist in §1.3. + +### §6.2 Required Node-Attribute pro Bundle + +Jeder `box`-shaped Worker-Node MUSS deklarieren: + +``` +[ + agent_id="A4", + bundle_name="customer-master-data", + owned_files="src/customer/master.rs,src/customer/master/types.rs", + read_only_files="../WoA/woa/blueprints/customer.py,../WoA/models.py", + spec_files="vendor/ogit/NTO/WorkOrder/entities/customer.ttl", + sentinel_token="WAVE-12-A4-7f3c", + proof_of_read=true, + worker_model="sonnet", + isolation="worktree", + status_file=".claude/board/wave-12/A4-status.json" +] +``` + +### §6.3 Required Edge-Attribute + +| Attribut | Werte | Default | +|---|---|---| +| `condition` | Expression über `outcome` und `context.*` | `outcome==SUCCESS` | +| `fidelity` | `full` / `compact` / `summary:medium` / `truncate` | `compact` | +| `route_on_fail` | Node-ID | `null` | + +### §6.4 YAML-Mirror (wenn DOT nicht verfügbar) + +```yaml +sprint: + id: SPRINT-12-customer-master + wave_size: 12 + base_branch: origin/main + workers: + - agent_id: A4 + bundle_name: customer-master-data + owned_files: [src/customer/master.rs, src/customer/master/types.rs] + read_only_files: [../WoA/woa/blueprints/customer.py, ../WoA/models.py] + spec_files: [vendor/ogit/NTO/WorkOrder/entities/customer.ttl] + sentinel_token: WAVE-12-A4-7f3c + proof_of_read: true + worker_model: sonnet + isolation: worktree + status_file: .claude/board/wave-12/A4-status.json + savants: + pre_plan: PP-14 + pre_spawn: PP-16 + during_impl: PP-15 + post_impl: PP-13 + goal_gate: PP-13 + retry_target: fan-out +``` + +--- + +## §7 Validierungs-Regeln + +Implementierungen MÜSSEN diese Lints gegen jeden Sprint-Plan vor +dem Fan-out laufen lassen. Severity matched die Konvention von +attractor-spec.md §7. + +| Regel | Beschreibung | Severity | +|---|---|---| +| `WAVE-001 unique-write` | Keine zwei Bundles listen die gleiche File unter `owned_files`. | ERROR | +| `WAVE-002 declared-shared` | Jede File die in zwei `owned_files` von Bundles auftaucht MUSS als `append-only` in INVARIANTS.md deklariert sein, sonst ERROR. | ERROR | +| `WAVE-003 sentinel-token-present` | Jeder Worker-Node MUSS ein eindeutiges `sentinel_token` haben. | ERROR | +| `WAVE-004 proof-of-read-required` | `proof_of_read=true` ist Pflicht; `false` braucht expliziten human Override im Commit-Body. | ERROR | +| `WAVE-005 goal-gate-has-retry` | Wenn ein Node `goal_gate=true` hat, MUSS er auch ein `retry_target` haben. (Übernommen von attractor §7.2.) | ERROR | +| `WAVE-006 isolation-worktree` | Jeder Worker-Node MUSS `isolation="worktree"` deklarieren. | ERROR | +| `WAVE-007 status-file-path` | Jeder Worker-Node MUSS einen `status_file`-Path deklarieren, eindeutig für diese Wave. | ERROR | +| `WAVE-008 worker-brief-size` | Das gerenderte Brief für jeden Worker DARF `worker.max_brief_tokens` (Default 8000) NICHT überschreiten. | WARNING | +| `WAVE-009 helper-hoist` | Pre-Wave-Helper-Call-Audit (§5.4) MUSS Zero unresolved-Helpers liefern. | WARNING | +| `WAVE-010 verdict-vocabulary` | Jedes Savant-Verdict MUSS aus seinem eigenen Vokabular (§4.2) sein. Cross-Vokabular-Verdicts werden zurückgeroutet. | ERROR | +| `WAVE-011 auto-status-false` | `auto_status` MUSS `false` sein. Fehlender Status MUSS als FAIL behandelt werden. (Kollidiert mit attractors Default.) | ERROR | +| `WAVE-012 reachability` | Jeder Node ist erreichbar von `start`. (Übernommen von attractor §7.2.) | ERROR | +| `WAVE-013 start-exit` | Exakt ein `start`, mindestens ein `exit`. (Übernommen von attractor §7.2.) | ERROR | +| `WAVE-014 inbox-drained` | Bei Wave-Start MUSS REQUESTS-FROM-AGENTS.md keine unbeantworteten Einträge aus prior Waves haben. | WARNING | +| `WAVE-015 invariants-fresh` | Jedes Worker-Brief MUSS die SHA-256 von INVARIANTS.md enthalten und der Worker MUSS Proof-of-Read darauf machen. | ERROR | + +--- + +## §8 Konfiguration + +```yaml +# .claude/ATT/config/orchestrator.yaml +orchestrator: + wave_size: + default: 12 + min: 1 + max: 24 + worker: + max_brief_tokens: 8000 + isolation: worktree # MUSS worktree sein + proof_of_read: required # required | optional (Default required) + auto_status: false # MUSS false sein + consolidation: + one_pr_per_bundle: false # true | false (false = ein kombinierter PR) + re_run_tier_1: true # MUSS true sein + savants: + pre_plan: PP-14 + pre_spawn: PP-16 + during_impl: PP-15 + post_impl: PP-13 + goal_gate_owner: PP-13 + retry: + max_replans_per_sprint: 3 + on_partial_failure: redispatch_with_tighter_scope + budget: + wave_max_input_tokens: 350000 + savant_max_input_tokens: 50000 + warn_if_exceeds: 0.8 # Warnung bei 80% des Budgets +``` + +--- + +## §9 Status-File-Schema + +### §9.1 Übernahme von Attractors status.json mit `auto_status=false` + +Jeder Worker MUSS eine `status.json` an seinen deklarierten +`status_file`-Path schreiben. Schema cribbed aus attractor +Appendix C und verschärft: + +```json +{ + "agent_id": "A4", + "bundle_name": "customer-master-data", + "sentinel_token": "WAVE-12-A4-7f3c", + "outcome": "SUCCESS", + "preferred_label": "land", + "suggested_next_ids": [], + "context_updates": { + "customer.master.files_created": 3, + "customer.master.parity_tests_passed": 7 + }, + "notes": "zwei Helper-Funktionen hoisted in src/customer/_shared.rs (bereits append-only in INVARIANTS.md §AppendOnly deklariert)", + "proof_of_read": [ + { "file": "INVARIANTS.md", "sha256": "...", "lines": 412, "depth": "thorough" }, + { "file": "../WoA/woa/blueprints/customer.py", "sha256": "...", "lines": 891, "depth": "full" }, + { "file": "vendor/ogit/NTO/WorkOrder/entities/customer.ttl", "sha256": "...", "lines": 137, "depth": "full" } + ], + "tier_1_gates": { + "lint": "GREEN", + "fmt": "GREEN", + "audit": "GREEN", + "deny": "GREEN", + "typecheck": "GREEN", + "tests": { "passed": 14, "failed": 0 } + } +} +``` + +### §9.2 Die fünf Outcomes + +| Outcome | Bedeutung | +|---|---| +| `SUCCESS` | Alle Tier-1-Gates grün; Spec-Contract erfüllt; ready für PR. | +| `PARTIAL_SUCCESS` | Bundle-Arbeit shipped, aber eine definierte Sub-Task deferred (filed in `Altlasten.md` / `TECH_DEBT.md`). | +| `RETRY` | Self-detected Stuck-Loop (per `anti-skim-agent-spec.md` §6) oder Token-Budget-Exhaustion. Orchestrator re-spawned mit engerem Scope. | +| `FAIL` | Tier-1-Gates rot, ODER Proof-of-Read fehlt, ODER Sentinel-Token failed, ODER Iron Rule verletzt. | +| `SKIPPED` | Die Preconditions des Bundles (z. B. ein Dependency-Bundle) wurden nicht erfüllt. | + +### §9.3 Missing-Status-Policy + +`auto_status=false` ist Pflicht. Wenn ein Worker exit'ed ohne +`status_file` zu schreiben, MUSS der Orchestrator die Wave als FAIL +für dieses Bundle behandeln. + +Hier kollidieren wir absichtlich mit attractors `auto_status=true`- +Default. Siehe [`README.md` „Wo es kollidiert"](./README.md#wo-es-mit-attractors-haltung-kollidiert-und-warum-wir-bei-unserer-position-bleiben). + +--- + +## §10 Definition von Fertig (Konformanz-Checklist) + +Eine Implementierung ist konform, wenn sie ALLE erfüllt: + +- [ ] Sprint-Pläne sind DOT-Graphen oder §6.4-YAML-Mirrors mit + allen Required Node-Attributen (§6.2) und Edge-Attributen (§6.3). +- [ ] Validierungs-Regeln §7 WAVE-001 bis WAVE-015 laufen als Teil + von `preflight-drift-auditor` und blocken Fan-out bei ERROR. +- [ ] Jeder Worker spawned mit `isolation: "worktree"` (§5.2). +- [ ] Jeder Worker schreibt eine `status.json` matching das §9.1-Schema. +- [ ] Fehlende `status.json` wird als FAIL behandelt (`auto_status=false`). +- [ ] Die vier Savants sind präsent mit nicht-überlappenden Verdict- + Vokabularen (§4.2) und expliziten Non-Use-Route-Tabellen (§4.3). +- [ ] PP-13 owns die Sprach-spezifische Tier-1-Toolchain + (`anti-skim-agent-spec.md` §8). +- [ ] PP-15 owns BAP1..BAP10 + 8 Boundary-Klassen (§4.5). +- [ ] PP-16 owns PD1..PD10 + 6 Achsen (§4.6) plus die §7-Validierungs- + Regeln. +- [ ] Worker-Briefs deklarieren `proof_of_read: true` und ein + eindeutiges `sentinel_token` (§6.2). +- [ ] Der Meta-Agent (die 13. Rolle) drained REQUESTS-FROM-AGENTS.md + ≥ 2× pro Tag während einer Wave (4h-Reply-Latency-Target). +- [ ] PR-Review klassifiziert Findings NUR als P0 oder P1; nie P2/P3. +- [ ] `INVARIANTS.md` ist ≤ 500 Zeilen; Split required oberhalb. +- [ ] Ein Consolidation-Commit pro Wave am Shared-Registry (§3.4); + Zero N-Mini-Commit-Anti-Pattern detected. +- [ ] Chunking-Disziplin durchgesetzt: Files > 150 Zeilen via + `tee -a` geschrieben (§5.5). +- [ ] Source-Quoting-Commit-Messages auf allen Ports (§5.6). +- [ ] Context-Fidelity-Ladder (§11) ist implementiert mit Vorrang + edge > node > graph > default und die §11.2-Token-Budgets + als Guidance behandelt (Hard-Cap ist `worker.max_brief_tokens`). +- [ ] `fidelity=truncate` enthebt einen Worker NICHT von der + `anti-skim-agent-spec.md` §3.3-Reading-Depth-Ladder. + +--- + +## §11 Cross-Language-Parity-Matrix + +| Aspekt | Rust | Python | TypeScript | Go | +|---|---|---|---|---| +| Tier-1-Lint | `cargo clippy --all-targets --all-features -- -D warnings` | `ruff check` | `eslint --max-warnings 0` | `golangci-lint run` | +| Tier-1-Format-Check | `cargo fmt --check` | `ruff format --check` | `prettier --check` | `gofmt -l` | +| Tier-1-Advisory-Scan | `cargo audit` | `pip-audit` | `npm audit --omit=dev` | `govulncheck ./...` | +| Tier-1-Dep-Policy | `cargo deny check` | `deptry .` | (Projekt-definiert) | `go vet -all` | +| Tier-1-Typecheck | (Clippy impliziert) | `mypy --strict` | `tsc --noEmit --strict` | (Compiler impliziert) | +| Tier-1-Tests | `cargo test --all-features` | `pytest` | `vitest run` | `go test ./...` | +| Module-Registry append-only File | `src/lib.rs` / `mod.rs` | `__init__.py` | `index.ts` / `index.js` | `pkg.go` | +| Workspace-Metadata | `cargo metadata` | `pipdeptree` | `pnpm list --depth=2` | `go list -m all` | +| Public-API-Diff | `cargo public-api` | `griffe check` | `api-extractor run` | `apidiff` | + +--- + +## §11A Context-Fidelity + +> **Übernommen aus `attractor-spec.md` §5.4.** Das ist die einzige +> Übernahme, die ihre eigene Section braucht, weil sie ändert, wie +> Briefs an Workers gerendert werden. +> +> *Hinweis: In der englischen Quelle trägt diese Section irrtümlich +> auch die Nummer §11 (Konflikt mit §11 Cross-Language-Parity-Matrix). +> Die deutsche Fassung nutzt §11A bis das in der EN-Quelle aufgeräumt +> ist.* + +### §11A.1 Warum Fidelity wichtig ist + +Eine 12-Worker-Wave läuft das Brief des Orchestrators durch jeden +Worker. Wenn jeder Worker den vollen Orchestrator-Context erbt, +skaliert der Input-Token-Cost einer Wave linear mit +Workers × shared-Context-Size, was verschwendet wenn Bundles klein +und wohl-scoped sind. + +Fidelity-Modes lassen den Orchestrator pro-Edge / pro-Node / pro- +Graph einstellen, wie viel von seinem Context jeder Worker erbt. + +### §11A.2 Vier Modes + Token-Budgets + +| Mode | Token-Budget (ca.) | Wann nutzen | +|---|---|---| +| `full` | unbounded (Worker erbt den ganzen Orchestrator-Thread) | Trusted Same-Domain-Handoff; selten; Default OFF | +| `compact` | ~3000 Tokens (Bundle-Ownership-Tabelle + INVARIANTS-SHA + Parity-Contract only) | **Default** für Standard-Workers | +| `summary:medium` | ~1500 Tokens (auto-generierte Summary des Orchestrator-Contexts, ~15-line max) | Budget-bewusste Waves; > 12 Workers | +| `summary:low` | ~600 Tokens (One-Paragraph-Summary) | Trivial Workers (z. B. Doc-Touch only) | +| `truncate` | ~200 Tokens (Goal + Bundle-Name + Sentinel only) | Smoke-Test-Workers; nie für Code-Writing | + +Diese Budgets sind Guidance, keine Hard-Caps; der Hard-Cap ist +`worker.max_brief_tokens` (Default 8000, konfigurierbar per §8). + +### §11A.3 Vorrang-Ladder + +Per attractor §5.4 wird Fidelity mit Vorrang edge > node > graph > +default aufgelöst: + +``` +worker.fidelity = + edge_attribute(orchestrator → worker, "fidelity") # falls gesetzt + || node_attribute(worker, "fidelity") # sonst + || graph_attribute(sprint_graph, "default_fidelity") # sonst + || "compact" # built-in Default +``` + +### §11A.4 Interaktion mit Proof-of-Read + +Fidelity kontrolliert, was der **Orchestrator** an den Worker +weitergibt. Sie kontrolliert NICHT, was der **Worker** von Disk +liest. Ein Worker auf `fidelity=truncate` MUSS dennoch die §3.3 von +[`anti-skim-agent-spec.md`](./anti-skim-agent-spec.md) Reading-Depth- +Ladder auf jeder File ausführen, die er anfasst. + +Insbesondere: + +- Ein Worker auf `fidelity=truncate`, dessen Brief nur „Goal + + Bundle-Name + Sentinel" enthält, MUSS dennoch INVARIANTS.md + `thorough` lesen vor jedem Commit (per anti-skim-agent-spec.md §3.3). +- Ein Worker auf `fidelity=full` skipped NICHT Proof-of-Read auf + Files, die im inherited Context summarisiert wurden — die inherited + Summary ist KEIN Substitut für den Read (die gleiche Regel, die + eine Compaction-Summary nicht zum Substitut für die JOURNAL-File macht). + +### §11A.5 Validierungs-Regel + +| Regel | Beschreibung | Severity | +|---|---|---| +| `WAVE-016 fidelity-budget` | Wenn ein Worker-Node `fidelity=truncate` deklariert UND `tier_1_gates` required sind, MUSS der Orchestrator verifizieren, dass das Brief des Workers die §6.2-Required-Attribute immer noch enthält. | ERROR | +| `WAVE-017 fidelity-precedence` | Die §11A.3-Precedence wird beachtet; konfliktende Deklarationen lösen sich in der Reihenfolge edge > node > graph > default auf. | WARNING | + +### §11A.6 Worked-Beispiel + +Eine Wave-12-Sprint mit gemischten Bundle-Sizes: + +```yaml +sprint: + default_fidelity: compact + workers: + - agent_id: A1 # großes Bundle, cross-cutting + fidelity: full # Node-Override + - agent_id: A2 # Standard-Bundle + # nutzt Default 'compact' + - agent_id: A3 # winziges Bundle, fasst nur Docs an + fidelity: summary:low # Node-Override + - agent_id: A4 # nur Smoke-Test + fidelity: truncate + edges: + - from: A1 + to: fan_in + fidelity: full # Edge-Override — A1s Resultat ans Fan-In bei voller Fidelity +``` + +--- + +## §12 Appendix A — Kanonischer Wave-DOT-Graph + +```dot +digraph wave_12_customer_master { + rankdir=LR; + start [shape=Mdiamond, label="start"]; + exit [shape=Msquare, label="exit"]; + + // PRE-SPAWN + pp16 [shape=box, + label="PP-16 preflight-drift-auditor", + owner="meta-agent", + verdicts="SPAWN-CLEAR / SPAWN-CAUTION / SPAWN-BLOCKED", + validation_rules="WAVE-001..WAVE-015"]; + + // DURING-IMPL Fan-out + fan_out [shape=component, label="fan-out N=12", max_parallel=12]; + + // 12 Workers (nur A1..A3 gezeigt for brevity) + a1 [shape=box, agent_id="A1", bundle_name="customer-list", + isolation="worktree", proof_of_read=true, + sentinel_token="WAVE-12-A1-2a91", + status_file=".claude/board/wave-12/A1-status.json"]; + a2 [shape=box, agent_id="A2", bundle_name="customer-detail", + isolation="worktree", proof_of_read=true, + sentinel_token="WAVE-12-A2-6b04", + status_file=".claude/board/wave-12/A2-status.json"]; + a3 [shape=box, agent_id="A3", bundle_name="customer-master", + isolation="worktree", proof_of_read=true, + sentinel_token="WAVE-12-A3-d177", + status_file=".claude/board/wave-12/A3-status.json"]; + // ... A4..A12 ... + + fan_in [shape=tripleoctagon, label="consolidate + re-run tier-1"]; + + pp15 [shape=box, label="PP-15 baton-handoff-auditor", + verdicts="CATCH-CRITICAL / CATCH-LATENT / CLEAN"]; + + pp13 [shape=box, label="PP-13 brutally-honest-tester", + verdicts="LAND / HOLD / REJECT", + goal_gate=true, + retry_target=fan_out]; + + start -> pp16 [label="plan handed off"]; + pp16 -> fan_out [label="outcome==SPAWN-CLEAR"]; + pp16 -> exit [label="outcome==SPAWN-BLOCKED", + condition="route_on_fail"]; + fan_out -> a1; + fan_out -> a2; + fan_out -> a3; + // ... -> a4..a12 + a1 -> fan_in; + a2 -> fan_in; + a3 -> fan_in; + fan_in -> pp15 [label="all status.json present, all proof_of_read verified"]; + pp15 -> pp13 [label="outcome==CLEAN || outcome==CATCH-LATENT"]; + pp15 -> fan_out [label="outcome==CATCH-CRITICAL", condition="route_on_fail"]; + pp13 -> exit [label="outcome==LAND"]; + pp13 -> fan_out [label="outcome==HOLD || outcome==REJECT", + condition="route_on_fail"]; +} +``` + +--- + +## §13 Appendix B — Stuck-Protokoll typisierte Blocker + +Wenn ein Worker nicht weiterkommt, schreibt er EINEN Eintrag in +`META/REQUESTS-FROM-AGENTS.md` und idled. Blocker-Typen: + +| Typ | Bedeutung | Meta-Agent-Aktion | +|---|---|---| +| `AMBIGUITY` | Spec ist mehrdeutig; mehr als eine sinnvolle Interpretation. | Schreibe Antwort in `ANSWERS-TO-AGENTS.md`; propagiere zu INVARIANTS wenn strukturell. | +| `MISSING_INVARIANT` | Iron Rule deckt den Fall nicht ab; Konvention fehlt. | Füge Invariant zu INVARIANTS.md; notifyfe den fragenden Agent; audit andere Agents auf die gleiche Lücke. | +| `SPEC_SOURCE_MISMATCH` | Spec sagt X, Reference-Source tut Y. | Schreibe RFC unter `rfcs/`; hol human Sign-off; dann sage dem Worker weiterzumachen. | +| `BEHAVIOUR_QUESTION` | Möglicher Bug in Reference-Source. | Wenn Meta die Source lesen und definitiv beantworten kann, mach es; sonst page den Human. | +| `EXTERNAL_DEPENDENCY` | Drittsystem zickt; Workaround unklar. | Check `wissen/` / `knowledge/`; wenn keine, RFC + Note hinzufügen. | + +`OUT-OF-SCOPE`-Requests (Worker will außerhalb des Bundles +refaktorieren, neues Feature hinzufügen) werden abgelehnt: Meta +schreibt `REJECTED: ` und schließt. + +--- + +## §14 Two-Protocol-Operating-Modes + +> Hinzugefügt 2026-05-18 — Design-Arbeit und Implementations-Arbeit +> haben unterschiedliche Gates und profitieren von unterschiedlichen +> Topologien. + +### §14.1 Zwei Protokolle, eine Loop-Familie + +| Protokoll | Phase | Erzeugtes Artifact | Goal-Gate-Kriterium | +|---|---|---|---| +| **Protokoll B** | Design | Specs, RFCs, Pläne, INVARIANT-Amendments, Agent-Cards | Doc-Coherence + Scope-Completeness über N Docs gemeinsam | +| **Protokoll A** | Implementation | Code, Parity-Tests, Ledger-Zeilen, PRs | Tier-1-Toolchain grün + Spec-Contract erfüllt | + +Beide Protokolle nutzen das gleiche 4-Savant-Council (PP-13/14/15/16) +und den gleichen Meta-Agent. Sie unterscheiden sich in (1) was ein +Goal-Gate-Pass ist, (2) der Topologie des Savant-Passes (sequenziell +vs. cooperative Council per §15) und (3) dem Artifact, das die +Preflight erzeugt. + +### §14.2 Protokoll B — Design-Loop + +``` +1. Draft → Human oder LLM authored N Documents auf einem Design-Branch +2. Joint-Review → Savants KOOPERIEREN auf den N Docs als coherent Set (§15) +3. Correct → Orchestrator wendet Cross-Doc-Fixes aus dem Joint-Review an +4. Re-Review → goto Step 2 bis CONVERGE (§15.5) oder Human-Override +5. Land → Docs mergen zu Main; Protokoll A darf beginnen +``` + +Goal-Gate-Kriterien (Protokoll B): + +| Check | Owner | Pass-Bedingung | +|---|---|---| +| Doc-Coherence | PP-15 baton-handoff-auditor | Kein Cross-Doc-Schema/Term-Drift (z. B. `status.json` in Spec A matched Consumer in Spec B) | +| Scope-Completeness | PP-16 preflight-drift-auditor | Jede Requirement im Source-Brief ist in mindestens einem Doc adressiert | +| Convergence-Opportunity | PP-14 convergence-architect | Kein latentes shared Konzept dupliziert über Docs ohne shared Section | +| Conformance-Checklist | PP-13 brutally-honest-tester | Jedes Doc hat eine Definition-of-Done-Section mit checkbaren Items | + +PP-13 in Protokoll B läuft NICHT die Language-Toolchain (kein Code +zum Compilen). Es validiert stattdessen, dass die Docs DIE Art Ding +sind, gegen die ein PP-13 später compilen/testen kann. + +### §14.3 Protokoll A — Implementation-Loop + +Der 6-Step-Wave-Loop von §3.1, mit einer Ergänzung: **Preflight +erzeugt ein auskommentiertes, compile-error-only Rust-Skelett** +(§14.4) vor dem Worker-Fan-out. Workers füllen Bodies in das +Skelett statt in leere Files zu schreiben. + +``` +1. Plan → Orchestrator partitioniert Arbeit in N Bundles +2. Preflight → PP-16 erzeugt SKELETON (§14.4) + Verdict +2a. Council → Savants KOOPERIEREN auf SKELETON (§15) — schnell, billig +3. Sprint → N parallele Worker-Agents füllen Bodies in Skelett +4. Council → Savants KOOPERIEREN auf FILLED Code (oder sequenziell per §15.6 bei großen Diffs) +5. Fix P0s → Orchestrator wendet Fixes an + verifiziert Tier-1-Gates +6. Commit+PR → Orchestrator (atomic Consolidation per §3.4) +7. Repeat → Orchestrator plant die nächste Wave +``` + +### §14.4 Das auskommentierte Rust-Skelett (Protokoll-A-Preflight-Artifact) + +PP-16 in Protokoll A erzeugt sowohl (a) sein Verdict (SPAWN-CLEAR / +CAUTION / BLOCKED per §4.6) ALS AUCH (b) ein lauffähig-gestubbtes +Rust-Skelett am Path deklariert in `skeleton_output_path` (ein +neues Node-Attribut auf jedem Worker, siehe §14.6). + +#### §14.4.1 Kanonisches Rust-Skelett-Format + +| Element | Skelett-Form | +|---|---| +| Funktions-Body | `todo!("SOURCE: :-")` | +| Trait-Impl | Deklariere den impl-Block mit jedem Method-Body als `todo!("SOURCE: ...")` | +| Struct | Deklariere alle Felder; `Default::default()`-Impl als `todo!("SOURCE: ...")` wenn Konstruktion nicht-trivial | +| Enum | Deklariere Variants; kein Body nötig | +| Module | `pub mod x;` mit Stub-`mod.rs` enthaltend `// SKELETON: — SOURCE: ` | +| `unsafe`-Block | `unsafe { todo!("UNSAFE-SOURCE: : — // SAFETY: ") }` — SAFETY-Rationale required weil PP-13 es später verlangen wird | +| Async-Funktion | Same wie Funktion; Body `todo!("SOURCE: ...")` | +| Macro | Deklariere mit leerem Body `() => { todo!("SOURCE: ...") }` | + +#### §14.4.2 Token-Wahl: `todo!` über `unimplemented!` + +- `todo!("SOURCE: ...")` — bevorzugt. Signalisiert Intent „das ist zu + tun", panic'ed mit Source-Location at Runtime falls hit, Compiler + akzeptiert es als beliebigen Typ. +- `unimplemented!()` — reserved für „korrekte API-Surface aber nie + implementiert in diesem Crate" (selten; kein Skelett-Placeholder). +- `unreachable!()` — reserved für „statisch unmöglicher Branch"; + kein Skelett-Primitive. + +#### §14.4.3 Skelett-Compile-Invariante + +Das Skelett MUSS mit `cargo check --workspace` grün compilen. Es +DARF NICHT `cargo test` oder `cargo run` bestehen (die `todo!()`- +Panics). Das ist die diagnostische Signatur eines well-formed +Skeletts. + +| Regel | Beschreibung | Severity | +|---|---|---| +| `WAVE-018 skeleton-compiles` | `cargo check --workspace` MUSS grün auf dem von PP-16 emittierten Skelett sein. | ERROR | +| `WAVE-019 skeleton-source-annotated` | Jedes `todo!()` MUSS ein `"SOURCE: :"`- oder `"UNSAFE-SOURCE: ..."`-Argument tragen. | ERROR | +| `WAVE-020 skeleton-no-runtime` | Das Skelett DARF NICHT `cargo test` oder `cargo run` bestehen — ein grüner Test auf einem Skelett bedeutet, dass der Test eine Tautologie ist. | ERROR | + +#### §14.4.4 Per-Language-Equivalents (für nicht-Rust-Repos) + +| Sprache | Skelett-Form | +|---|---| +| Rust | `todo!("SOURCE: ...")` (kanonisch) | +| Python | `raise NotImplementedError("SOURCE: :")` mit Docstring-Stub | +| TypeScript | `throw new Error("TODO — SOURCE: :")` | +| Go | `panic("TODO — SOURCE: :")` | + +Rust ist kanonisch, weil das `todo!()`-Macro der Sprache purpose-built +für genau diesen Use-Case ist. + +### §14.5 Protokoll A vs. Protokoll B auf der Savant-Schicht + +| Savant | Protokoll B (Design) | Protokoll A (Implementation) | +|---|---|---| +| PP-13 | Verifiziert, dass Docs DoD-Sections haben; kein Compile-Gate. | Tier-1-Toolchain + AP1..AP9 auf gefülltem Code (§4.4). | +| PP-14 | Surfacet Convergence-Opportunities über Docs. | Surfacet Convergence-Opportunities über Slices (Helper-Hoist). | +| PP-15 | Cross-Doc-Schema/Term-Drift. | Cross-Bundle-DTO-Drift auf gefülltem Code (BAP1..BAP10, §4.5). | +| PP-16 | Validiert Plan-vs-Source-Brief: jede Requirement landed in einem Doc. | Validiert Plan-vs-Main (PD1..PD10, §4.6) + EMITTIERT SKELETON (§14.4). | + +### §14.6 Mode-Switch ist explizit + +Der Orchestrator deklariert, welches Protokoll im Sprint-Header +aktiv ist: + +```yaml +sprint: + id: SPRINT-17-attractor-fold-in + protocol: B + default_fidelity: full + inputs: + docs: + - .claude/ATT/autoattended-orchestrator-spec.md + - .claude/ATT/anti-skim-agent-spec.md + - .claude/ATT/agent-coordination-mcp-spec.md + goal_gate: savant_council_converge +``` + +```yaml +sprint: + id: SPRINT-18-attractor-implementation + protocol: A + default_fidelity: compact + preflight: + skeleton_output_root: src/attractor/ + skeleton_compiles_check: cargo check --workspace + workers: + - agent_id: A1 + bundle_name: orchestrator-runner + skeleton_output_path: src/attractor/orchestrator/mod.rs + ... +``` + +| Regel | Beschreibung | Severity | +|---|---|---| +| `WAVE-021 protocol-declared` | Jedes Sprint-Header MUSS `protocol: A` oder `protocol: B` deklarieren. | ERROR | +| `WAVE-022 protocol-coherence` | Protokoll-B-Sprints DÜRFEN NICHT `workers[].owned_files` von File-Extensions außer `.md` / `.yaml` / `.dot` deklarieren. Protokoll-A-Sprints MÜSSEN `preflight.skeleton_output_root` deklarieren. | ERROR | + +--- + +## §15 Cooperative-Savant-Council + +> Die 4 Savants **kooperieren** — sie laufen nicht als unabhängige +> Fan-out-Branches, deren Verdicts bag-of-words-aggregiert werden. +> Sie lesen ein shared Scratchpad, filen Findings, cross-referen +> einander, iterieren, und emerge mit einem EINZELNEN gemeinsamen +> Verdict. + +### §15.1 Warum cooperative, nicht independent-parallel + +Independent-Parallel-Review (4 Savants → 4 Verdicts → Super-Verdict- +Aggregation) hat zwei Failure-Modes: + +1. **Lockstep-Drift.** Alle 4 Savants verpassen das gleiche + cross-cutting Issue, weil sie das Artifact jeweils durch die + eigene Achse anschauten ohne den Winkel der anderen zu sehen. +2. **Duplicate-Findings.** Jeder Savant flaggt independent das + gleiche Issue aus einem anderen Winkel; der Orchestrator + verschwendet eine Round mit Deduplikation. + +Cooperation fixt beides: Savants sehen die Findings der anderen +während sie landen und entweder (a) cross-referen („PP-15 hat das +schon geflaggt; mein Winkel ist …") oder (b) suppressen Duplicates +(„PP-13 hat das schon aus stärkerem Winkel covered — withdrawn"). + +### §15.2 Das shared Scratchpad + +``` +.claude/board/savant-council-/ +├── ROUND-0-ARTIFACT.md (Snapshot dessen was unter Review steht) +├── ROUND-1-PP-13.md (PP-13s Round-1-Findings) +├── ROUND-1-PP-14.md (PP-14s Round-1-Findings) +├── ROUND-1-PP-15.md (PP-15s Round-1-Findings) +├── ROUND-1-PP-16.md (PP-16s Round-1-Findings) +├── ROUND-2-PP-13.md (PP-13s Round 2 — sieht alles von Round 1) +├── ... +└── COUNCIL-VERDICT.md (finales gemeinsames Dokument) +``` + +Append-only per §7.1 von `agent-coordination-mcp-spec.md`. Jeder +Savant schreibt seine File am Ende seiner Round; liest ALLE anderen +Files am Beginn seiner nächsten Round. + +### §15.3 Der Cooperation-Loop + +``` +Round R+1 (per Savant): + 1. Lies jede ROUND-R-*.md (die Findings der anderen aus der vorigen Round) + 2. Lies ROUND-R-.md (deine eigenen Findings aus der vorigen Round) + 3. Für jede Finding eines anderen Savants: + a. Cross-refere wenn es deine Achse berührt („noted — meine AP3- + Finding bezieht sich darauf: ") + b. Withdraw deine eigenen Findings, die der andere Savant aus + stärkerem Winkel covered hat + c. Suppresse Duplikate mit `STATUS: superseded-by(PP-X round-R)` + 4. Append deine neuen Findings (Achsen-getagged) an ROUND-(R+1)-.md + 5. Markiere mit `ROUND-COMPLETE`-Sentinel wenn du keine neuen Findings hast +``` + +Eine Round endet wenn ALLE 4 Savants in derselben Round entweder +(a) neue Findings hinzufügten ODER (b) `ROUND-COMPLETE` emittierten +(keine neuen Findings + keine unaddressierten Cross-Refers von Peers). + +### §15.4 Convergence + Termination + +| Bedingung | Outcome | +|---|---| +| Alle 4 Savants emittieren `ROUND-COMPLETE` in derselben Round | **CONVERGE** — proceed zu §15.5 Verdict-Synthese | +| Round-Count überschreitet `max_rounds` (Default 3) | **STALL** — Orchestrator oder Human arbitred | +| Ein Savant emittiert ein unrecoverable BLOCK in irgendeiner Round (z. B. PP-16=SPAWN-BLOCKED ohne klares Remediation) | **BLOCK** — abort das Council; zurück zu Step 1 des Protokolls | + +### §15.5 Das gemeinsame Verdict (`COUNCIL-VERDICT.md`) + +Nach CONVERGE liest der Meta-Agent (oder ein designierter Chairman- +Savant — Default PP-16 in Protokoll A, PP-15 in Protokoll B) ALLE +ROUND-*.md-Files und synthetisiert eine einzelne +`COUNCIL-VERDICT.md`: + +```yaml +council_verdict: + topic: + protocol: A | B + rounds_run: N + super_verdict: CONVERGE | SPLIT | BLOCK + + per_savant_final: + PP-13: + verdict: LAND | HOLD | REJECT + findings_kept: N + findings_withdrawn: M + PP-14: { ... } + PP-15: { ... } + PP-16: { ... } + + consolidated_p0_findings: + - finding_id: F1 + raised_by: PP-13 (round 1) + cross_referred_by: [PP-15 round 1, PP-16 round 2] + summary: + remediation: + + consolidated_p1_findings: [ ... ] + + next_action: proceed | arbitrate | retry_preflight | block +``` + +Der Super-Verdict ist abgeleitet aus den Per-Savant-Finals: + +| Super-Verdict | Trigger | +|---|---| +| **CONVERGE** | Alle 4 Savants pass (PP-13=LAND ∧ PP-14∈{OPPORTUNITY-NOW,WORTH-EXPLORING,DROP} ∧ PP-15=CLEAN ∧ PP-16=SPAWN-CLEAR), UND keine P0-Findings bleiben nach Deduplikation. | +| **SPLIT** | Gemischte Verdicts, die weder all-pass noch block-on-any sind. Orchestrator oder Human arbitred. | +| **BLOCK** | Eines von: PP-13=REJECT ∨ PP-15=CATCH-CRITICAL ∨ PP-16=SPAWN-BLOCKED ∨ verbleibende P0-Findings ≥ 1. | + +### §15.6 Council-DOT-Topologie + +```dot +digraph savant_council { + rankdir=LR; + start [shape=Mdiamond]; + artifact [shape=note, label="docs (B) OR skeleton (A) OR filled-code (A)"]; + + round_R [shape=component, + label="round R: 4 savants read shared scratchpad, + append findings, cross-refer, withdraw duplicates"]; + + pp13 [shape=box]; pp14 [shape=box]; pp15 [shape=box]; pp16 [shape=box]; + + round_check [shape=diamond, + label="all 4 savants ROUND-COMPLETE?"]; + bump_round [shape=parallelogram, label="R := R+1"]; + + chairman [shape=box, label="chairman savant synthesizes COUNCIL-VERDICT.md"]; + verdict [shape=tripleoctagon, label="super-verdict: CONVERGE / SPLIT / BLOCK"]; + exit [shape=Msquare]; + + start -> artifact; + artifact -> round_R; + round_R -> pp13; + round_R -> pp14; + round_R -> pp15; + round_R -> pp16; + pp13 -> round_check; + pp14 -> round_check; + pp15 -> round_check; + pp16 -> round_check; + round_check -> bump_round [label="no: a savant has new findings"]; + bump_round -> round_R; + round_check -> chairman [label="yes: CONVERGE / STALL / BLOCK"]; + chairman -> verdict; + verdict -> exit; +} +``` + +### §15.7 Wann zurück auf sequenziell fallen (kein Council) + +Drei Daumenregeln: + +1. **Filled-Code-Review mit Diff > 500 Zeilen:** sequenziell PP-13 → + PP-15 → PP-16 (per §4-Chain). Jeder Savant sieht einen kleineren + Diff, nachdem der Orchestrator die P0-Fixes des vorigen Savants + angewendet hat. +2. **Token-Budget-constrained Run:** sequenziell. Council bezahlt + für `4 × Rounds × Artifact-Size`-Reads; sequenziell bezahlt für + `1 × Artifact + 3 × Diff-only`-Reads. +3. **Erstes Mal mit neuem Spec-Set:** sequenziell. Lass einen Savant + die offensichtlichen Issues shaken bevor die anderen es lesen. + +Sonst: Council by default. + +### §15.8 Konfiguration + +```yaml +council: + max_rounds: 3 + chairman: + protocol_A: PP-16 + protocol_B: PP-15 + scratchpad_root: .claude/board/savant-council + cooperation_protocol: cross-refer-and-withdraw # siehe §15.3 + duplicate_suppression: superseded-by-stronger-angle +``` + +### §15.9 Validierungs-Regeln + +| Regel | Beschreibung | Severity | +|---|---|---| +| `WAVE-023 council-legality` | Council-Mode ist deklariert nur wenn Artifact eines von {docs, skeleton, small-diff filled-code} ist. | ERROR | +| `WAVE-024 council-rounds-bounded` | `max_rounds` MUSS gesetzt sein; Default 3; Hard-Cap 7. | ERROR | +| `WAVE-025 scratchpad-append-only` | Das `.claude/board/savant-council-/`-Directory MUSS append-only per `agent-coordination-mcp-spec.md` §7.1 sein. | ERROR | +| `WAVE-026 cross-refer-recorded` | Wenn ein Savant die Finding eines Peers withdraws oder supersedes, MUSS die Aktion mit `STATUS: superseded-by(PP-X round-R)` aufgezeichnet sein für Traceability. | ERROR | +| `WAVE-027 chairman-declared` | Der `chairman`-Savant für den Synthese-Pass MUSS in der Sprint-Config deklariert sein; Default per §15.8. | ERROR | +| `WAVE-028 stall-arbitration` | Ein STALL-Outcome (max_rounds erreicht ohne CONVERGE) MUSS gefolgt sein von einem Arbitration-Step (Orchestrator oder Human). | ERROR | +| `WAVE-029 split-arbitration` | Ein SPLIT-Super-Verdict MUSS gefolgt sein von einem Arbitration-Step bevor Fan-out fortgesetzt. | ERROR | + +### §15.10 Beispiel: dieses Kit selbst + +Ein worked Example. Um die §14- / §15-Ergänzungen in die drei +`.claude/ATT/*.md`-Specs zu folden, würde ein Protokoll-B-Council: + +``` +Round 1: + PP-13: "Spec A §11 'Context Fidelity' hat keine DoD-Subsection" + PP-14: "Spec A §15 'cooperative council' dupliziert Spec C §3.1 'three-layer model' + — Opportunity für shared Konzept" + PP-15: "Spec A §14.4 'skeleton' referenziert todo!()-Macro; + Spec B §6 'tool-call loop detection' tut es nicht — kein Widerspruch, nur eine Achse" + PP-16: "Scope-Completeness-Pass — jede Requirement aus dem Source-Brief + (Protokoll A/B + Cooperation + Rust-Skelett) landed in Spec A; + Spec B + Spec C brauchen keine Änderungen" + +Round 2: + PP-13: ROUND-COMPLETE (keine neuen Findings; PP-14s Opportunity ist non-blocking) + PP-14: "Spec A §15 Cooperation sollte Spec C §3.2 'file blackboard' referenzieren + weil das Scratchpad dieses Primitive nutzt — Cross-Ref hinzugefügt" + PP-15: "withdrawn: meine Round-1-Finding ist gecovered durch PP-14s Round-2-Cross-Ref" + PP-16: ROUND-COMPLETE + +Round 3: + Alle 4: ROUND-COMPLETE + +CONVERGE — Chairman PP-15 synthetisiert COUNCIL-VERDICT.md +super_verdict: CONVERGE +consolidated_p0_findings: [] +consolidated_p1_findings: + - F1: "Spec A §11 add 'Definition of Done' subsection (PP-13 round 1)" + - F2: "Spec A §15 cross-reference spec C §3.2 (PP-14 round 2, PP-15 round 2)" +next_action: proceed +``` + +--- + +## §16 Model-Stylesheet + Mid-Task-Switching + +> Hinzugefügt 2026-05-18. Mapped auf `attractor-spec.md` §8 +> `model_stylesheet` (CSS-like Selektoren), ergänzt unsere Mid-Task- +> Opus↔Sonnet-Switching getriggert durch unsere Stuck-Protokoll- +> Blocker und Council-Round-Counts. + +### §16.1 Warum überhaupt ein Stylesheet + +| Phase / Rolle | Cognitive Load | Sinnvoller Default | +|---|---|---| +| Plan-Authoring | hoch (Architektur, cross-cutting) | **opus** + `reasoning=high` | +| Plan-Review (Meta-Agent) | hoch (Judgment, Taste) | **opus** + `reasoning=high` | +| PP-14 convergence-architect | hoch (Creative-Design-Exploration) | **opus** + `reasoning=high` | +| PP-16 preflight-drift-auditor | mittel (Git + Grep gegen Plan) | **sonnet** (escalate bei Drift > 100 Zeilen) | +| PP-15 baton-handoff-auditor | mittel (Interface-Diffing) | **sonnet** | +| PP-13 brutally-honest-tester | mittel (Toolchain-Runner + AP1..AP9) | **sonnet** | +| Worker (Skeleton-Fill) | mittel (mechanisches Füllen typed Surface) | **sonnet** | +| Worker (Greenfield-Code) | hoch (Design + Impl) | **opus** | +| Council-Round (pro Savant) | mittel (Review-Iteration) | **sonnet** | +| Chairman-Synthese | hoch (Cross-Finding-Konsolidierung) | **opus** | +| Inbox-Drain (Meta-Agent) | niedrig (typed-Blocker-Routing) | **sonnet** (escalate bei `BEHAVIOUR_QUESTION`) | + +Statische Per-Rolle-Defaults sind Step 1. Mid-Task-Switching (§16.3) +ist Step 2. + +### §16.2 Stylesheet-Syntax (attractor §8 kompatibel) + +```yaml +model_stylesheet: + # CSS-like Rules. Erste matchende Regel gewinnt (per attractor §8). + rules: + - selector: "node[kind=plan]" + props: { model: opus, reasoning_effort: high } + - selector: "node[kind=meta_review]" + props: { model: opus, reasoning_effort: high } + - selector: "node[kind=savant][role=PP-14]" + props: { model: opus, reasoning_effort: high } + - selector: "node[kind=savant][phase=POST-IMPL]" + props: { model: sonnet } + - selector: "node[kind=savant][phase=PRE-SPAWN]" + props: { model: sonnet } + - selector: "node[kind=council_chairman]" + props: { model: opus } + - selector: "node[kind=worker][mode=skeleton-fill]" + props: { model: sonnet } + - selector: "node[kind=worker][mode=greenfield]" + props: { model: opus } + - selector: "*" + props: { model: sonnet } # finaler Fallback +``` + +Selektoren komponieren mit `[attr=value]`-Pairs (attractor §8.2). Die +Fallback-`*`-Regel ist Pflicht; Abwesenheit ist `WAVE-030` ERROR. + +### §16.3 Mid-Task-Eskalations-Trigger + +Ein Worker (oder Savant) wechselt NICHT sein eigenes Model — er +schreibt ein typisiertes Signal in seine `status.json` und der +**Orchestrator re-spawned** mit dem requested Model. Self-Switching +würde LD-1-Sentinel-Continuity brechen. + +| Trigger-Bedingung | Aktion | Eskalieren von → zu | +|---|---|---| +| `outcome=RETRY` UND `notes contains "escalate-model:opus"` | re-spawn gleicher agent_id mit opus | sonnet → opus | +| Tool-Call-Loop detected im Council, `rounds >= 3` ohne CONVERGE | re-spawn den loopenden Savant mit opus + `reasoning=high` | sonnet → opus | +| Worker filed `MISSING_INVARIANT`-Blocker UND Meta-Agent-Antwort ist strukturell | re-spawn Worker mit opus | sonnet → opus | +| Worker filed `SPEC_SOURCE_MISMATCH` UND Meta hat RFC geschrieben | re-spawn Worker mit opus nach RFC-Merge | sonnet → opus | +| Proof-of-Read deckt > 10 Files | escalate beim nächsten Re-Spawn | sonnet → opus | +| `unsafe`-Block in Skeleton-Fill | escalate beim nächsten Re-Spawn | sonnet → opus | +| PP-16-Drift-Surface > 100 Zeilen | escalate den nächsten Preflight-Pass | sonnet → opus | +| PP-13-Anti-Pattern-Density > 3 pro 100 Zeilen Diff | escalate PP-13 für Re-Review-Pass | sonnet → opus | + +### §16.4 Mid-Task-De-Eskalations-Trigger + +Die Gegenrichtung — Orchestrator kann ein Re-Spawn von opus zu +sonnet runterdrücken, wenn die Arbeit einfacher war als das +Stylesheet-Default prediktet hat: + +| Trigger-Bedingung | Aktion | De-Eskalieren von → zu | +|---|---|---| +| Plan stellte sich als 2-Bundle-Wave heraus (nicht 12) | Default der nächsten Round | opus → sonnet | +| Council CONVERGED in Round 1 mit Zero P0 + ≤2 P1 | Chairman-Synthese | opus → sonnet | +| `outcome=SUCCESS` + Tier-1 grün + Diff < 50 Zeilen | Next-Wave-Default für dieses Bundle | opus → sonnet | +| Meta-Agents Inbox-Drain hat nur `AMBIGUITY`-Blocker in letzten 10 Einträgen beantwortet | Next Inbox-Drain | opus → sonnet | + +### §16.5 Wire-Format fürs Switching + +Das Signal fließt auf dem universellen Wire-Format +(`agent-coordination-mcp-spec.md §1.3`): + +```markdown +## 2026-05-18T14:22 — PROPOSAL[P1]: escalate-model A4 → opus (sonnet, claude/wave-12) + +**Author:** A4 +**Kind:** PROPOSAL +**Severity:** P1 +**Refs:** status.json#A4 wave-12 +**Reason:** tool-call-loop:length=2:rounds=3 auf customer.master sea-orm UPSERT + +--- + +Requested: model=opus, reasoning_effort=high. +Suggested rationale: das ist ein Stuck-Protokoll-AMBIGUITY-class-Issue +(sea-orm vs. Python idempotentes UPSERT-Semantics) — Judgment-heavy. +``` + +Orchestrator routed nach `Kind=PROPOSAL` + `Severity=P1` + Body +enthält `escalate-model:`. Re-Spawn passiert im nächsten Wave-Step. + +### §16.6 Cost-Aware-Ceiling + +Das Stylesheet MUSS ein Per-Wave-**Escalation-Budget** deklarieren: + +```yaml +model_stylesheet: + budget: + max_opus_workers_per_wave: 3 # Default 3 + max_opus_savant_rounds_per_council: 2 # Default 2 + warn_if_exceeds: 0.8 +``` + +Wenn die pending Eskalationen einer Wave das Budget überschreiten +würden, MUSS der Orchestrator entweder (a) Eskalationen über Waves +serialisieren ODER (b) den Human via `wait.human` (attractor §6) +fragen bevor escaliert wird. + +### §16.7 Validierungs-Regeln + +| Regel | Beschreibung | Severity | +|---|---|---| +| `WAVE-030 stylesheet-fallback` | `model_stylesheet.rules` MUSS mit einem `*`-Fallback enden. | ERROR | +| `WAVE-031 no-self-switching` | Workers / Savants DÜRFEN NICHT ihr eigenes Model mid-call wechseln. Nutze das `PROPOSAL`-Wire-Signal (§16.5). | ERROR | +| `WAVE-032 sentinel-on-respawn` | Re-Spawn unter Eskalation MUSS einen NEUEN Sentinel-Token ausgeben (die Identität des vorigen Workers ist geschlossen). | ERROR | +| `WAVE-033 escalation-budget` | Das Eskalations-Total der Wave DARF `model_stylesheet.budget` NICHT überschreiten ohne expliziten Human-Override. | WARNING | +| `WAVE-034 escalation-trace` | Jedes Eskalations-Re-Spawn MUSS einen `PROPOSAL`-Eintrag auf dem Wire-Format (§16.5) hinterlassen, damit das Audit-Log zeigt wann + warum. | ERROR | +| `WAVE-035 de-escalation-gate` | De-Eskalation MUSS nur durch §16.4-Bedingungen getriggert sein; arbiträre Downgrades sind verboten (Cost-Saving-Optimismus). | ERROR | + +### §16.8 Worked-Beispiel: eine Wave, die escaliert dann de-escaliert + +``` +T+0 Plan committed opus (per §16.1 Default für Plan) +T+1 PP-16 Preflight läuft sonnet (Drift < 100 Zeilen, Default) +T+1 PP-16 emittiert Skelett sonnet (immer noch PP-16) +T+2 Council-Round 1 sonnet x4 (alle Savants Default) +T+2 Council CONVERGE opus (Chairman-Synthese-Default) +T+3 Workers spawnen (12 × A1..A12) sonnet (Skeleton-Fill-Default) +T+4 A4 hits sea-orm AMBIGUITY → PROPOSAL: escalate A4 +T+4 Meta-Agent schreibt RFC opus (Meta-Default) +T+5 A4 Re-Spawn opus (escalated, neuer Sentinel) +T+6 A4 returned SUCCESS — (behielt opus durch diese Wave) +T+7 Fan-in + Tier-1 grün — +T+8 nächste Wave plant A4 wieder opus (sticky Eskalation für eine Wave) +T+9 A4 returned SUCCESS wieder — +T+10 Wave 3 plant A4 sonnet (DE-ESCALATE per §16.4: 2 Successes + Diff < 50) +``` + +--- + +*Ende der Datei autoattended-orchestrator-spec.md.* diff --git a/.claude/ATT/README.md b/.claude/ATT/README.md new file mode 100644 index 0000000000..36ac86c5ef --- /dev/null +++ b/.claude/ATT/README.md @@ -0,0 +1,87 @@ +# `.claude/ATT/` — Attractor-style NLSpecs of our kit + +> **Status:** DRAFT · **Version:** 0.1.0 · **Last updated:** 2026-05-17 +> +> **What this is.** Our `.claude/EN/` kit's ideas, restated in the +> [strongdm/attractor](https://github.com/strongdm/attractor) NLSpec +> format so a coding agent can implement them directly. +> [NLSpec](https://github.com/strongdm/attractor#terminology) = +> "human-readable spec intended to be directly usable by coding agents +> to implement/validate behavior." +> +> **What this is NOT.** A replacement for `.claude/EN/`. The two are +> complementary: `.claude/EN/` is the operator's cheat-sheet +> (prose, in-session use); `.claude/ATT/` is the engineering spec +> (NLSpec, build-time use). + +## The three specs + +| File | Mirrors attractor's | Adds our innovation | +|---|---|---| +| [`autoattended-orchestrator-spec.md`](./autoattended-orchestrator-spec.md) | [`attractor-spec.md`](https://github.com/strongdm/attractor/blob/main/attractor-spec.md) (DOT-graph pipeline runner) | Wave-based 12-worker fan-out; 4-savant verdict gates (PP-13/14/15/16); 6 worker iron rules; sprint-token budget (~300k/wave); multi-file board pattern with single-mutable-file invariant | +| [`anti-skim-agent-spec.md`](./anti-skim-agent-spec.md) | [`coding-agent-loop-spec.md`](https://github.com/strongdm/attractor/blob/main/coding-agent-loop-spec.md) (the per-LLM-call agent library) | Reading-Depth-Ladder (grep→read→thorough→fan-out); Lie-Detector LD-1..5 (sentinel token / proof-of-read SHA / 3-section challenge / negative-knowledge test / line-range quote); typed stuck-protocol blockers (AMBIGUITY / MISSING_INVARIANT / SPEC_SOURCE_MISMATCH / BEHAVIOUR_QUESTION / EXTERNAL_DEPENDENCY) | +| [`agent-coordination-mcp-spec.md`](./agent-coordination-mcp-spec.md) | [`unified-llm-spec.md`](https://github.com/strongdm/attractor/blob/main/unified-llm-spec.md) (provider-agnostic LLM SDK) | Three coordination layers (role-teleport / file-blackboard / branch-pub-sub) the way a native A2A MCP server should expose them; structured handover schema; decision matrix for when each layer fits | + +## What we adopted from attractor (the five wins) + +These are concrete things attractor's specs nail down that our prose +docs in `.claude/EN/` didn't have, now incorporated: + +| # | Attractor concept | Lands in our NLSpec | Adoption | +|---|---|---|---| +| 1 | Typed `status.json` schema + 5-value `StageStatus` enum (attractor Appendix C: `{outcome, preferred_label, suggested_next_ids, context_updates, notes}`) | [`autoattended-orchestrator-spec.md` §9](./autoattended-orchestrator-spec.md#9-status-file-schema) | Adopted **with `auto_status=false` mandatory** (see "Where it conflicts" below). | +| 2 | DOT graph DSL for the workflow + lint rules (attractor §2 grammar + §7 validation: `reachability`, `start_no_incoming`, `goal_gate_has_retry`, `condition_syntax`) | [`autoattended-orchestrator-spec.md` §6](./autoattended-orchestrator-spec.md#6-sprint-plan-format) (DOT + YAML mirror) + [§7](./autoattended-orchestrator-spec.md#7-validation-rules) (WAVE-001..WAVE-017 with ERROR/WARNING severity) | Adopted with three wave-specific additions: `unique-write`, `declared-shared`, `auto-status-false`. | +| 3 | Context Fidelity ladder (attractor §5.4: `full` / `truncate` / `compact` / `summary:low/medium/high` with token budgets + edge > node > graph > default precedence) | [`autoattended-orchestrator-spec.md` §11A](./autoattended-orchestrator-spec.md#11a-context-fidelity) | Adopted with one tightening: `fidelity=truncate` does NOT exempt a worker from the §3.3 Reading-Depth-Ladder of `anti-skim-agent-spec.md`. | +| 4 | In-loop tool-call loop detection (attractor coding-agent §2.10: last 10 calls scanned for length-1/2/3 repeating patterns → inject steering warning) | [`anti-skim-agent-spec.md` §6](./anti-skim-agent-spec.md#6-tool-call-loop-detection) + AP9 in [§9](./anti-skim-agent-spec.md#9-anti-pattern-catalog-ap1ap9) | Adopted verbatim; elevated to a system-level invariant. PP-13's post-hoc AP9 catches what the in-loop detector misses. | +| 5 | Definition-of-Done checklists + Cross-Provider Parity Matrix per spec (attractor §10-style conformance tables) | Each NLSpec ends with `§ Definition of Done` + `§ Cross-{Language,Provider} Parity Matrix` | Adopted as the structural template. The 26-repo rollout is now machine-checkable. | + +## Why this format + +Three properties we get from attractor's NLSpec format that our prose +docs in `.claude/EN/` don't have: + +1. **Definition of Done checklists** at the end of each spec — gives + us a conformance test for "is this implementation complete?" +2. **Cross-Provider Parity Matrix** tables — gives us a per-language / + per-runtime mapping so the same NLSpec can land in Rust, Python, + TypeScript, Go without three-way drift. +3. **Validation rules with ERROR/WARNING/INFO severity** — turns + linting into a deterministic process, not a judgment call. + +## Where it conflicts with attractor's posture (and why we keep our position) + +| Attractor's default | Our position | Why | +|---|---|---| +| `auto_status=true` (§4.5 + Appendix C: "if the handler writes no status, auto-generate SUCCESS") | `auto_status=false` is mandatory | This is exactly the silent-skim failure mode our Lie-Detector LD-1..5 exists to prevent. Missing status = FAIL, not SUCCESS. | +| Single-threaded graph traversal (§3.8: "Only one node executes at a time") | Wave fan-out is the baseline, not a special-case `parallel` node | Our token budget is per-wave (~300k for 12 workers), not per-node. Modeling waves as one giant `parallel` node is syntactically awkward. | +| Engine-level retries with exponential backoff (§3.5-3.6) | Stuck-agents file typed blockers in REQUESTS-FROM-AGENTS.md; meta-agent decides | Retries should be contextual and inspected, not silent and uniform. | +| `wait.human` default; LLM gates are test fixtures (§6.4: `AutoApproveInterviewer` "Used for automated testing") | LLM meta-agent is the production gate | Our meta-agent's plan review + P0/P1 PR review + inbox drain is a production role, not a test fixture. | +| Subagent depth = 1 default (coding-agent §7.3) | Wave fan-out routinely runs 12 workers from one orchestrator | We override depth to ≥2 — workers should be able to spawn sub-investigations. | + +## Conformance + +A repo that imports these NLSpecs is conformant if it satisfies the +"Definition of Done" checklist at the end of each spec. The lance-graph +implementation (see [`AdaWorldAPI/lance-graph` `.claude/agents/`](https://github.com/AdaWorldAPI/lance-graph/tree/main/.claude/agents)) +is the most-mature reference; the WoA + woa-rs implementations are +the wave-based reference. + +## Provenance + +- Source kit: `.claude/EN/` in this repo (see [`.claude/EN/README.md`](../EN/README.md)) +- Format inspiration: [strongdm/attractor](https://github.com/strongdm/attractor) (MIT-style NLSpecs) +- Distillation handover: [`META/HANDOVER-AGENTKIT-CONSOLIDATION-2026-05-17.md`](https://github.com/AdaWorldAPI/WoA/blob/main/META%2FHANDOVER-AGENTKIT-CONSOLIDATION-2026-05-17.md) (in `AdaWorldAPI/WoA`) +- Sister handover (Rust hardening pass): [`META/HANDOVER-WOA-RS-AGENT-HARDENING-2026-05-17.md`](https://github.com/AdaWorldAPI/WoA/blob/main/META%2FHANDOVER-WOA-RS-AGENT-HARDENING-2026-05-17.md) + +## Build + +To turn these NLSpecs into a runnable implementation, supply this +prompt to a modern coding agent (Claude Code, Codex, OpenCode, Amp, +Cursor, ...): + +``` +codeagent> Implement the Autoattended Orchestrator + Anti-Skim Agent + + Agent Coordination MCP as described by + https://github.com/AdaWorldAPI//tree/main/.claude/ATT + together with strongdm/attractor as the substrate. +``` diff --git a/.claude/ATT/agent-coordination-mcp-spec.md b/.claude/ATT/agent-coordination-mcp-spec.md new file mode 100644 index 0000000000..0332e49881 --- /dev/null +++ b/.claude/ATT/agent-coordination-mcp-spec.md @@ -0,0 +1,743 @@ +# Agent Coordination MCP Specification + +> **Status:** DRAFT · **Version:** 0.1.0 · **Last updated:** 2026-05-17 +> **Format:** NLSpec (per [strongdm/attractor](https://github.com/strongdm/attractor)) +> **Substrate:** Designed as a sibling to `unified-llm-spec.md` — +> a *coordination* SDK the way attractor's spec is an *LLM* SDK. + +--- + +## §1 Overview + +### §1.1 Purpose + +Claude Code spawns each `Agent()` as an isolated subprocess. Each +subprocess gets a fresh context window, cannot call other agents' +tools, cannot read other agents' in-flight state, and returns a +single blob to its parent. This breaks three patterns that worked +in earlier Claude / Gemini setups: + +1. **Role teleportation** — switching persona in-context with zero loss. +2. **Mid-flight coordination** — agent A tells agent B what it found. +3. **Cross-session handoff** — session A's work feeds session B in real-time. + +This spec defines the **Agent Coordination MCP**: the three-layer +coordination model needed to restore these patterns, and the +file / git / MCP primitives that implement it. + +### §1.2 Two operating modes + +| Mode | Substrate | When to use | +|---|---|---| +| **Native** | A future MCP server exposing `post_entry` / `read_entries` / `subscribe` endpoints (§5). | When available — preferred for low-latency. | +| **Workaround** | `tee -a` markdown files in git + GitHub PR webhooks via `mcp__github__subscribe_pr_activity`. | Today, on every Claude Code session. Defined here as the canonical fallback. | + +Both modes implement the same three-layer model (§3) with the same +schemas (§6). The native server is a thin serde layer over the +workaround's wire format. + +### §1.3 The universal wire format: `tee -a [bug/proposal]` + +The same markdown blob serves **three purposes simultaneously**: + +| Purpose | Reader | Mutability | +|---|---|---| +| **MCP orchestration message** | the orchestrator (and any subscribed session via `subscribe_pr_activity`) | append-only | +| **A2A wire format** | sibling agents (worker → worker, worker → meta, savant → savant) | append-only | +| **Log format (audit + replay)** | any future session reading git history | immutable once committed | + +One write, three uses. This is by design: it means the same code +that posts an entry to the file-blackboard (Layer 1) ALSO emits the +log line ALSO becomes a cross-session-readable orchestration message +the moment it lands on a coordination PR's branch. + +The blob's shape is **always a typed envelope + a body**: + +``` +## YYYY-MM-DDTHH:MM — KIND[severity]: one-line headline (author, branch) + +**Author:** +**Kind:** BUG | PROPOSAL | HANDOVER | FINDING | DECISION | RFC | STATUS +**Severity:** P0 | P1 | INFO (optional; defaults to INFO) +**Refs:** | | +**Proof-of-read:** +- file= sha256=<...> lines= depth= +- file= sha256=<...> lines= depth= + +--- + + +``` + +The `tee -a` envelope is the wire; the body is the payload. Routers +(orchestrator, meta-agent, MCP server) dispatch by `Kind` and +`Severity`; readers grep by `Author` + `Refs`; auditors replay by +the immutable git-log order. + +### §1.4 Tables over prose (NLSpec discipline) + +Implementations SHOULD prefer tabular content over prose for every +catalog (anti-patterns, validation rules, verdict vocabularies, +configuration defaults, parity matrices). Rationale: + +| Property | Table | Prose | +|---|---|---| +| Machine-parseable | yes | no | +| Forces consistency (all rows have the same columns) | yes | no | +| Compressible (each row is a fact) | yes | no | +| Diffs cleanly in PRs | yes | partial | +| Resists hedging language | yes (cell width caps qualifiers) | no | +| Suitable for English narrative | no | yes | + +Use prose for: section overviews, rationale paragraphs, conflict +explanations. Use tables for: everything else. + +### §1.5 What this spec is NOT + +- Not a workflow engine. Wave / sprint structure is in + [`autoattended-orchestrator-spec.md`](./autoattended-orchestrator-spec.md). +- Not a per-agent loop. Worker behavior is in + [`anti-skim-agent-spec.md`](./anti-skim-agent-spec.md). +- Not provider-coupled. Both modes are LLM-provider-agnostic. + +--- + +## §2 Terminology + +| Term | Definition | +|---|---| +| **Layer-0 Teleportation** | The main thread loads an agent card and "wears its hat" in-context. Zero transport. | +| **Layer-1 File Blackboard** | A per-session append-only markdown file (`AGENT_LOG.md`) that workers read on entry and append to on exit. | +| **Layer-2 Branch Pub/Sub** | A draft PR's push activity feed used as a real-time cross-session message bus. | +| **Handover** | A structured markdown file (`.claude/handovers/YYYY-MM-DD-HHMM-*.md`) written at session end for the next session to read at startup. | +| **Blackboard entry** | One append to `AGENT_LOG.md`; schema in §6.1. | +| **Cross-session broadcast** | A committed-and-protected file separate from `AGENT_LOG.md`; the durable mirror of important entries. | +| **Coordination PR** | A draft PR (never merged) whose push activity is the Layer-2 transport. | + +--- + +## §3 The Three Coordination Layers + +### §3.1 Layer-0: Teleportation + +**When:** the task needs FULL conversation context (not a summary) +and the role switch is temporary. + +**How:** on the main thread (NOT via `Agent()`): + +``` +1. Read .claude/EN/agents/.md +2. Load its Tier-1 knowledge docs (per the card's §Inputs) +3. Do the work with full session context intact +4. When done, optionally switch: read another role card +5. Review the work from the other role's perspective +6. Back to the original role — nothing lost +``` + +**Cost:** zero transport, single-threaded, context budget fills as +role cards are read. No isolation: a mistake made "as role-X" is +visible to a subsequent "as role-Y" review (a feature, not a bug). + +**Not for:** mechanical grind-work (use Layer-1 + `Agent()`) or +truly independent parallel work (use `Agent()` directly). + +### §3.2 Layer-1: File Blackboard + +**Replaces:** Mid-flight coordination (partially). +**Path:** `.claude/board/AGENT_LOG.md` (or `META/AGENT_LOG.md` per +repo convention). +**Permission:** pre-allowed in `.claude/settings.json` as +`Bash(tee -a *)` and `Bash(tee -a **)`. + +**Setup (native mode):** + +A future MCP server exposes the file as a structured stream +(`post_entry`, `read_entries`, `subscribe`). Until that exists, the +fallback is the markdown file directly. + +**Agent prompt template (workaround mode):** + +``` +Before starting work, read .claude/board/AGENT_LOG.md to see what +other agents already shipped or found. + +After committing, append your entry: + +tee -a .claude/board/AGENT_LOG.md > /dev/null <<'EOF' + +## YYYY-MM-DDTHH:MM — description (model, branch) + +**Agent-id:** +**Bundle:** +**Sentinel:** +**Commit:** `abc1234` +**Tests:** N pass (M new) +**Outcome:** One-line summary. +**Proof-of-read:** +- file= sha256=<...> lines= depth= +EOF +``` + +**Limitations:** + +- Not real-time: agent B only sees what agent A *committed*, not + what A is currently working on. +- Git staging: if agent A and B append without committing, only the + last `git add` wins. Mitigation: commit immediately after append. +- Ordering: entries appear at the bottom (per `tee -a`); convention + is newest-first. The main thread reorders during board hygiene. + +### §3.3 Layer-2: Branch Pub/Sub + +**Replaces:** Cross-session handoff. +**How:** open a coordination PR. Both sessions subscribe. Push +events arrive as `` tags via +`mcp__github__subscribe_pr_activity`. + +**Setup:** + +```bash +# Session A (creates the bus): +git checkout -b claude/blackboard +echo "# Coordination Blackboard" > .claude/board/AGENT_LOG.md +git add .claude/board/AGENT_LOG.md +git commit -m "init coordination blackboard" +git push -u origin claude/blackboard + +mcp__github__create_pull_request( + owner="", repo="", + title="A2A coordination blackboard", + head="claude/blackboard", base="main", + body="Cross-session pub/sub bus. Do not merge.", + draft=true +) + +mcp__github__subscribe_pr_activity(owner="", repo="", pullNumber=NNN) + +# Session B (joins): +mcp__github__subscribe_pr_activity(owner="", repo="", pullNumber=NNN) +git fetch origin claude/blackboard +git checkout claude/blackboard +# Read AGENT_LOG.md +``` + +**Coordination loop:** + +``` +Session A: Session B: + [does work] + tee -a AGENT_LOG.md > /dev/null < + git pull origin claude/blackboard + cat AGENT_LOG.md + [builds on A's findings] + tee -a AGENT_LOG.md ... + git add && commit && push + ← + git pull + # reads B's entry, continues +``` + +**Limitations:** + +- GitHub webhook latency: seconds to low minutes. +- Rate limits: GitHub API 5000/hr authenticated. +- Requires network: doesn't work offline. +- PR MUST stay open: closing it kills the subscription. + +### §3.4 Layer-3 (handover): Structured handover files + +**Replaces:** Session-to-session context transfer. +**Path:** `.claude/handovers/YYYY-MM-DD-HHMM--to-.md`. + +The `SessionStart` hook (`.claude/hooks/session-start.sh`) cats the +latest handover file into the next session's context. + +Schema is in §6.3. + +--- + +## §4 Decision Matrix + +| Need | Workaround | Native MCP equivalent | Cost | +|---|---|---|---| +| Agent A's findings feed agent B (same session) | Layer-1 File Blackboard | `post_entry` + `read_entries` | Low: `tee -a` + `git add` | +| Session A's work feeds session B (real-time) | Layer-2 Branch Pub/Sub | `subscribe` on entries | Medium: PR + subscribe | +| Full-context role switch (no loss) | Layer-0 Teleportation | n/a (in-process) | Zero: just read the card | +| Session-to-session knowledge transfer | Handover files | `post_handover` + `read_latest_handover` | Low: write once, read at startup | +| Parallel independent grind-work | Standard `Agent()` spawns | n/a | Low: fire and forget | +| Multi-source synthesis needing judgment | Teleportation on Opus main thread | n/a | Zero | + +--- + +## §5 Native MCP Server Contract + +A future MCP server SHOULD expose the following endpoints. They are +specified here so the workaround mode's wire format is forward-compatible +with the native mode. + +### §5.1 `post_entry` + +```yaml +endpoint: post_entry +params: + board: string # e.g. ".claude/board/AGENT_LOG.md" + entry: + timestamp: string (ISO 8601) + agent_id: string + bundle: string | null + sentinel: string | null + commit: string | null # short SHA + outcome: enum (SUCCESS | PARTIAL_SUCCESS | RETRY | FAIL | SKIPPED) + summary: string + proof_of_read: list of ProofOfRead # see §6.2 +returns: + entry_id: string + position: integer +errors: + - INVALID_OUTCOME + - SENTINEL_MISMATCH + - PROOF_OF_READ_MISSING_REQUIRED +``` + +### §5.2 `read_entries` + +```yaml +endpoint: read_entries +params: + board: string + since: string (entry_id) | null + limit: integer (default 100, max 1000) + filter: + agent_id: string | null + outcome: enum | null + bundle: string | null +returns: + entries: list of BlackboardEntry + next_cursor: string | null +``` + +### §5.3 `subscribe` + +```yaml +endpoint: subscribe +params: + board: string + cursor: string | null # start position +streams: + type: server_sent_events + event_kinds: + - entry_appended + - entry_redacted + - board_archived +returns_initial: + cursor: string +errors: + - BOARD_NOT_FOUND + - SUBSCRIPTION_LIMIT_EXCEEDED +``` + +### §5.4 `post_handover` / `read_latest_handover` + +```yaml +endpoint: post_handover +params: + from_session: string + to_session: string | null # null = "next session, whoever" + handover: Handover # see §6.3 +returns: + handover_id: string + +endpoint: read_latest_handover +params: + to_session: string # current session id +returns: + handover: Handover | null +``` + +### §5.5 Authentication + +The native server SHOULD use the same auth substrate as the GitHub +MCP server (per-session OAuth or PAT). Workaround mode uses git +push permissions on the coordination PR's branch. + +--- + +## §6 Schemas + +### §6.1 BlackboardEntry + +```json +{ + "entry_id": "01HXYZ-WAVE12-A4-SUCCESS", + "timestamp": "2026-05-17T17:42:00Z", + "agent_id": "A4", + "bundle": "customer-master-data", + "sentinel": "WAVE-12-A4-7f3c", + "commit": "a1b2c3d4", + "outcome": "SUCCESS", + "summary": "customer-master ported; 7 parity tests green; no Iron Rule violations.", + "proof_of_read": [ + { "file": "META/INVARIANTS.md", "sha256": "...", "lines": 412, "depth": "thorough" } + ] +} +``` + +### §6.2 ProofOfRead + +```json +{ + "file": "path/relative/to/repo-root", + "sha256": "64-hex", + "lines": 412, + "depth": "thorough" +} +``` + +`depth` values: `grep` | `sed-partial` | `skim` | `read` | `thorough` +| `troubleshooting` | `fan-out` | `truncated:head-N` | `truncated:tail-N`. + +### §6.3 Handover + +```yaml +handover: + id: 2026-05-17-2200-wave12-to-wave13 + from_session: 2026-05-17-evening + to_session: null # any next session + topic: customer-master-port + + what_i_did: + - bullet: ported 12 bundles (A1..A12) for customer-master + commits: [a1b2c3, d4e5f6, ...] + + finding: + - bullet: BBB-Barrier compile-error happens at A5 when sea-orm 0.12 is in path + severity: P0 + source: META/INVARIANTS.md §BBB + + conjecture: + - bullet: switching to sea-orm 0.13 might break the migration path; needs probe + severity: P1 + + blockers: + - description: A5 cannot be merged until the sea-orm RFC is accepted + filed_in: META/REQUESTS-FROM-AGENTS.md#A5 + + open_questions: + - "Should A11 be a separate PR or rolled into the consolidation?" + + proof_of_read: + - file: META/INVARIANTS.md + sha256: "..." + lines: 412 + depth: thorough +``` + +### §6.4 RequestEntry (`META/REQUESTS-FROM-AGENTS.md`) + +```yaml +request: + agent_id: A5 + file: src/customer/master/sea_orm.rs + timestamp: 2026-05-17T18:14:32Z + + question: | + The reference Python uses an idempotent UPSERT but the sea-orm + builder I have does INSERT-OR-FAIL. Spec is ambiguous. + + tried: + - sea-orm `on_conflict().do_nothing()` — fails with `Error: ...` + - manual transaction wrap — works but doubles latency + + blocker: AMBIGUITY + + proof_of_read: + - file: vendor/ogit/NTO/WorkOrder/entities/customer.ttl + sha256: "..." + lines: 137 + depth: full + - file: ../WoA/woa/blueprints/customer.py + sha256: "..." + lines: 891 + depth: full +``` + +### §6.5 AnswerEntry (`META/ANSWERS-TO-AGENTS.md`) + +```yaml +answer: + agent_id: A5 + in_response_to: 2026-05-17T18:14:32Z + + decision: | + Use sea-orm transaction wrap. The latency cost is acceptable. + + propagation: + invariants_section: META/INVARIANTS.md §UPSERT-PATTERN added + rfcs: null + + signed_off_by: meta-agent (auto) + timestamp: 2026-05-17T18:42:00Z +``` + +--- + +## §7 Append-only Governance + +### §7.1 Mutability invariant + +Across all coordination files: + +| File | Mutability | +|---|---| +| `Stand.md` / `STATUS_BOARD.md` | Mutable (the only one). | +| `AGENT_LOG.md` | Append-only. | +| `REQUESTS-FROM-AGENTS.md` | Append-only. | +| `ANSWERS-TO-AGENTS.md` | Append-only. | +| `INVARIANTS.md` | Append-only by section; sections may be marked SUPERSEDED but never deleted. | +| Handover files | Immutable after write. | +| Cross-session broadcast files (`PR_ARC_INVENTORY.md`, `LATEST_STATE.md`, `EPIPHANIES.md`, `IDEAS.md`, `INTEGRATION_PLANS.md`, `ISSUES.md`, `TECH_DEBT.md`, `STATUS_BOARD.md`) | Append-only at the entry level; specific fields are mutable state. | + +### §7.2 Enforcement + +Implementations SHOULD enforce append-only governance at the +permissions layer. The lance-graph reference (see +[`AdaWorldAPI/lance-graph` `.claude/settings.json`](https://github.com/AdaWorldAPI/lance-graph/blob/main/.claude/settings.json)) +denies `Edit` and `Write` on the 8 bookkeeping files and allows +only `Bash(tee -a *)`. This is the recommended pattern. + +--- + +## §8 Relation to Runtime A2A (Layer 1, in-process) + +When a project also runs an in-process Blackboard in its own runtime +(e.g. `lance_graph_contract::a2a_blackboard::Blackboard`), the +session-level workarounds are structurally isomorphic to it: + +| Runtime (in-process) | Session (this spec) | +|---|---| +| `Blackboard.entries` | `AGENT_LOG.md` entries | +| `BlackboardEntry.expert_id` | Agent description + model | +| `BlackboardEntry.capability` | Bundle / D-ids | +| `BlackboardEntry.result` | Commit hash + outcome | +| `BlackboardEntry.confidence` | Tier-1 gate pass count | +| `Blackboard.round` | Git commit sequence | +| Experts read prior rounds | Agents read prior log entries | + +The isomorphism is intentional: the same coordination pattern works +on both layers because the problem is the same — independent experts +composing results on a shared substrate. + +--- + +## §9 Definition of Done + +An implementation is conformant if it satisfies ALL of: + +- [ ] Three layers (Teleport / Blackboard / Branch Pub/Sub) are + implementable with the existing primitives (file + git + MCP + `subscribe_pr_activity`). +- [ ] Native MCP endpoints §5.1–§5.4 exist OR the workaround mode + is documented as the explicit fallback. +- [ ] `BlackboardEntry`, `ProofOfRead`, `Handover`, `RequestEntry`, + `AnswerEntry` schemas are implemented and validated. +- [ ] Append-only governance (§7) is enforced at the + `.claude/settings.json` layer for the 8 bookkeeping files. +- [ ] The single-mutable-file invariant is enforced: `Stand.md` / + `STATUS_BOARD.md` is the ONLY file workers may overwrite. +- [ ] Decision matrix (§4) is followed: workers use the right + transport for the right kind of message. +- [ ] Coordination PRs are draft, named `claude/`, and + explicitly marked `Do not merge` in the body. +- [ ] Handover files use the §6.3 schema with required sections: + what-I-did / FINDING / CONJECTURE / blockers / open-questions + / proof-of-read. + +--- + +## §10 Cross-Provider Parity Matrix + +| Capability | Claude Code | OpenAI Codex / codex-rs | Gemini CLI | Notes | +|---|---|---|---|---| +| Layer-0 Teleportation | Read agent card on main thread | Same | Same | Provider-agnostic; depends only on file read. | +| Layer-1 File Blackboard | `Bash(tee -a)` + `git add` | Same | Same | POSIX-only; provider-agnostic. | +| Layer-2 Branch Pub/Sub | `mcp__github__subscribe_pr_activity` | `gh pr` polling | `gh pr` polling | Claude Code has native MCP; others poll. | +| Native MCP server (§5) | TBD (this spec) | TBD | TBD | All three providers can speak MCP. | +| Append-only governance | `.claude/settings.json` deny list | provider-specific permission file | provider-specific | Pattern transfers; allowed-list syntax differs. | +| Handover files | `.claude/hooks/session-start.sh` cats latest | Equivalent shell hook | Equivalent shell hook | Hook script is bash; provider-agnostic. | + +--- + +## §11 Appendix A — Sample `AGENT_LOG.md` entry (verbatim) + +```markdown +## 2026-05-17T17:42 — customer-master ported; 7 parity tests green (sonnet, claude/wave-12-customer-master) + +**Agent-id:** A4 +**Bundle:** customer-master-data +**Sentinel:** WAVE-12-A4-7f3c +**Commit:** `a1b2c3d4` +**Tests:** 7 pass (3 new) +**Outcome:** SUCCESS — all Tier-1 gates green; no Iron Rule violations. +**Proof-of-read:** +- file=META/INVARIANTS.md sha256=fa39a3... lines=412 depth=thorough +- file=../WoA/woa/blueprints/customer.py sha256=8e1a45... lines=891 depth=full +- file=vendor/ogit/NTO/WorkOrder/entities/customer.ttl sha256=4c0b... lines=137 depth=full +``` + +--- + +## §12 Appendix B — When NOT to use coordination + +If the task is... + +- truly independent and parallel → use standard `Agent()` spawns; no coordination layer needed. +- a single one-shot question → use `Agent()` with sub-agent_type=Explore; no `AGENT_LOG.md` write. +- a security-sensitive review → use Layer-0 Teleportation on the main thread; do NOT spawn a separate Agent() that might be compromised by an untrusted source it reads. + +--- + +## §13 Cooperative-Savant Scratchpad Bus + +> Added 2026-05-18. The transport layer for the Cooperative Savant +> Council defined in `autoattended-orchestrator-spec.md` §15. + +### §13.1 What the scratchpad bus is + +A purpose-shaped extension of the Layer-1 File Blackboard (§3.2) +used specifically for the savant council's iterative cooperation. +Same wire format (`tee -a` markdown), same append-only governance +(§7), one new directory convention: + +``` +.claude/board/savant-council-/ +├── ROUND-0-ARTIFACT.md snapshot of artifact under review +├── ROUND--.md one file per (round, savant) +└── COUNCIL-VERDICT.md chairman's synthesis after CONVERGE +``` + +Topic naming: `savant-council-` (e.g. +`savant-council-SPRINT-17-attractor-fold-in/`). + +### §13.2 Per-savant file schema + +```markdown +# ROUND-{N} {savant_id} — {topic} + +**Round:** N +**Savant:** PP-13 | PP-14 | PP-15 | PP-16 +**Read at round start:** [list of peer files read this round] +**Status:** IN-PROGRESS | ROUND-COMPLETE + +## New findings (this round) + +### F{round}-{savant}-{N}: {one-line headline} +**Axis:** AP1..AP9 | BAP1..BAP10 | PD1..PD10 | EP1..EP8 +**Severity:** P0 | P1 +**File:** : +**Detail:** +**Remediation:** + +## Cross-references to peers' findings + +- F{R}-PP-X-N: NOTED — my angle: +- F{R}-PP-Y-N: SUPERSEDED-BY my F{R+1}-{self}-M + +## Withdrawals (my prior findings I am retracting) + +- F{R-1}-{self}-N: WITHDRAWN — covered by PP-X round R from a stronger angle +``` + +### §13.3 Reading-and-writing protocol + +Each savant in each round: + +1. **Read** ALL `ROUND-(R-1)-*.md` files (peer findings from previous + round) plus its own `ROUND-(R-1)-.md` if any. +2. **Decide** cross-refs, withdrawals, new findings. +3. **Write** `ROUND-R-.md` in one `tee -a` call (no + partial writes — savants share a directory and partial + writes confuse peers' next round). +4. **Emit** `Status: ROUND-COMPLETE` if no new findings AND no + un-addressed cross-refers from peers. + +### §13.4 Chairman synthesis + +After CONVERGE (all four savants `ROUND-COMPLETE` in the same +round) the chairman savant (declared in sprint config; default +PP-16 for Protocol A, PP-15 for Protocol B) reads ALL ROUND-*.md +files in lexical order and writes a single `COUNCIL-VERDICT.md` +matching the schema in `autoattended-orchestrator-spec.md` §15.5. + +The chairman: +- MUST cite every kept finding with its raised-by + cross-referrals. +- MUST consolidate duplicates that survived the cooperation rounds + (rare — most should withdraw themselves, but races can leave a + pair that needs one merged into the other). +- MUST NOT add findings of its own that did not appear in any + `ROUND-*.md` (the chairman is a synthesizer, not a fifth voice). +- MUST set `super_verdict` per §15.5 rules. + +### §13.5 Concurrency contract + +Savants may run on separate sessions; the scratchpad bus is the +synchronization point. + +- **Round boundary** is implicit: a savant starts round R only after + it has read all four `ROUND-(R-1)-*.md` files (including its own). +- **Last-writer-wins** is acceptable for the per-savant per-round + file since each (savant, round) pair has exactly one writer by + construction. If a savant writes twice in the same round (e.g. a + retry after a tool-call loop), the second write replaces the first. +- **Round skipping** is forbidden: a savant MUST NOT write + `ROUND-(R+2)` until `ROUND-(R+1)` exists for it. + +### §13.6 Decision matrix update + +Extends §4 with one new row: + +| Need | Workaround | Native MCP equivalent | Cost | +|---|---|---|---| +| Cooperative multi-agent review with iterative cross-refer + withdraw | Layer-1 File Blackboard scoped to `savant-council-/` directory (§13) | `post_council_finding` + `read_council_round` + `subscribe_council_round` | Medium: `tee -a` per round + git commit per round | + +### §13.7 Native MCP endpoints (sketch) + +```yaml +endpoint: post_council_finding +params: + topic: string # e.g. "SPRINT-17-attractor-fold-in" + round: integer + savant: enum (PP-13 | PP-14 | PP-15 | PP-16) + payload: FindingsFile # matches §13.2 schema +returns: + file_id: string + +endpoint: read_council_round +params: + topic: string + round: integer +returns: + findings_by_savant: map[savant -> FindingsFile] + complete_savants: list[savant] # those that emitted ROUND-COMPLETE + +endpoint: subscribe_council_round +params: + topic: string +streams: + event_kinds: [ findings_appended, savant_round_complete, council_converged, council_stalled, council_blocked ] +``` + +### §13.8 Validation rules + +| Rule | Description | Severity | +|---|---|---| +| `COORD-001 directory-naming` | Council scratchpad MUST live at `.claude/board/savant-council-/`. | ERROR | +| `COORD-002 file-naming` | Per-savant files MUST be named `ROUND--PP-.md`. | ERROR | +| `COORD-003 append-only` | The scratchpad directory inherits the append-only governance of §7. | ERROR | +| `COORD-004 chairman-no-new-findings` | The chairman's `COUNCIL-VERDICT.md` MUST NOT introduce findings absent from any `ROUND-*.md`. | ERROR | +| `COORD-005 round-monotonic` | Each savant MUST write `ROUND-(R+1)` only after `ROUND-R-.md` exists. | ERROR | + +--- + +*End of `agent-coordination-mcp-spec.md`.* diff --git a/.claude/ATT/anti-skim-agent-spec.md b/.claude/ATT/anti-skim-agent-spec.md new file mode 100644 index 0000000000..62a2fbeb74 --- /dev/null +++ b/.claude/ATT/anti-skim-agent-spec.md @@ -0,0 +1,1077 @@ +# Anti-Skim Agent Specification + +> **Status:** DRAFT · **Version:** 0.1.0 · **Last updated:** 2026-05-17 +> **Format:** NLSpec (per [strongdm/attractor](https://github.com/strongdm/attractor)) +> **Substrate:** Designed to compose on top of `coding-agent-loop-spec.md`; +> adds the verification + escalation layer attractor's spec omits. + +--- + +## §1 Overview + +### §1.1 Purpose + +The Anti-Skim Agent is a per-worker LLM loop that is verifiably +read-before-acting. It treats agent **overconfidence** as the +primary failure mode (cf. Kahneman / Tversky System-1 easy-path, +Dunning-Kruger) and shifts the burden of proof onto the agent: + +- Every claim about a file's content MUST be backed by a SHA-256 + + line-count + reading-depth declaration. +- Every brief contains a sentinel token the agent MUST replay verbatim. +- A spot-check passes one of five lie-detector tests per supervisor pass. + +### §1.2 Relation to `coding-agent-loop-spec.md` + +The Anti-Skim Agent is implementable as a coding-agent-loop session +with the additions in §3–§6. Specifically: + +- The `Session.run()` loop (coding-agent §2.5) is wrapped with a + proof-of-read precondition (§7). +- The `steering_queue` (coding-agent §2.6) is extended with the + typed stuck-protocol blockers in §5. +- The tool-call loop detector in coding-agent §2.10 is adopted + verbatim and elevated to a system-level invariant (§6). +- Tool-output truncation (coding-agent §5) is adopted with one + addition: truncated reads MUST appear in the proof-of-read entry + with `depth=truncated`, not `depth=full`. + +### §1.3 What this spec is NOT + +- Not an orchestrator. Wave-level coordination is in + [`autoattended-orchestrator-spec.md`](./autoattended-orchestrator-spec.md). +- Not a transport. Cross-agent messages flow through the file + blackboard + handover files in + [`agent-coordination-mcp-spec.md`](./agent-coordination-mcp-spec.md). + +--- + +## §2 Terminology + +| Term | Definition | +|---|---| +| **Sentinel token** | A unique opaque string in the worker's brief that the worker MUST replay verbatim in its first reply. | +| **Proof-of-read** | A typed entry `{ file, sha256, lines, depth }` declared by the worker for every file it consumed. | +| **Reading depth** | One of seven escalating levels: `grep` (anti) / `sed-partial` (anti) / `skim` / `read` / `thorough` / `troubleshooting` / `fan-out`. | +| **Lie-Detector test** | One of five tests (LD-1..5) the meta-agent / orchestrator spot-checks against a worker's output. | +| **Drift signal** | A passive indicator of skim or overconfidence (§4.3). | +| **Stuck-protocol blocker** | A typed entry in `META/REQUESTS-FROM-AGENTS.md` (one of five types) signaling "I cannot proceed". | +| **Tool-call loop** | A repeating pattern of length 1, 2, or 3 in the worker's last 10 tool calls. | + +--- + +## §3 The Reading-Depth Ladder + +### §3.1 Seven depths, two anti-depths + +| Depth | Appropriate use | Proof | +|---|---|---| +| `grep` (anti) | Symbol locator AFTER or PARALLEL to a real read. | Worker declares `depth=grep`; NEVER `depth=full`. | +| `sed-partial` / `head-only` (anti) | Snapshotting a few lines for orientation. | Worker declares `depth=sed-partial`; NEVER `depth=full`. | +| `skim` | Huge file; need ONE section located, then read in full. | Worker outputs claims ONLY about the read section. | +| `read` | File < ~500 lines; standard full read. | Top to bottom, no skip; for larger files, offset/limit chunks but actually read every chunk. | +| `thorough` | Iron Rules, INVARIANTS, RFCs, schema migrations. | Read twice: once for comprehension, once for verification. Worker can name 3 sections. | +| `troubleshooting` | Known bug + error message. | Error → grep symbol → READ function in full → READ caller(s). | +| `fan-out` | Cross-file pattern, refactor planning, audit across > 5 files. | Inventory file as output; spawn Explore subagent. | + +### §3.2 Escalation default + +**Default bias is to read too deep, not too shallow.** If in doubt: +upgrade the depth one rung; never downgrade. + +### §3.3 When → minimum depth + +| If the worker is about to ... | ... then at minimum | +|---|---| +| Open `INVARIANTS.md`, `CLAUDE.md`, `BOOT.md` | `thorough` | +| Open a memory file (`CONTEXT.md`, `JOURNAL.md`, `TODO.md`) | `thorough` | +| Touch a schema or migration file | `thorough` + check downstream drift detectors | +| Open an unknown file for the first time | at least `read`, preferably `thorough` | +| Pure symbol lookup ("where is `foo` defined?") | `grep` OK but then read the definition in full | +| Triage a bug report | `troubleshooting` (error → grep → read function + caller) | +| Plan a refactor / wave / audit | `fan-out` (inventory file mandatory) | +| Is unsure which depth | upgrade one rung | + +### §3.4 Truncation interaction + +When `coding-agent-loop-spec` truncates a tool output (per +coding-agent §5.2), the worker's proof-of-read entry for that file +MUST declare `depth=truncated:head-N` or `depth=truncated:tail-N` — +NEVER `depth=full`. + +--- + +## §4 The Lie-Detector + +### §4.1 Five tests + +Cheap to expensive: + +| # | Test | How | Honest agent | Shallow agent | +|---|---|---|---|---| +| LD-1 | **Sentinel-Token** | Brief ends with: "If you have read this fully, begin your first reply with ``" | replays token verbatim | token missing / wrong / paraphrased | +| LD-2 | **Proof-of-Read with SHA** | Output must contain `{ file, sha256, lines, depth }` per file consumed | SHA + line count match the actual file | SHA missing, wrong, or `` placeholder | +| LD-3 | **3-Sections Name Challenge** | "Name 3 sections from file X (heading + approx. line span)" | 3 concrete headings, plausible spans | vague theme labels; no real headings | +| LD-4 | **Negative-Knowledge Test** | "Does file X say anything about topic Y?" — where Y is NOT in the file | "no, not contained" | hallucinates plausible-sounding content about Y | +| LD-5 | **Line-Range Quote** | "Quote lines N–M from file X verbatim" | exact quote OR "range does not exist (file only K lines)" | paraphrases, deviates, or refuses without reason | + +> **Adoption note.** LD-3 / LD-4 / LD-5 are SHOULD-USE on savants +> every wave, MAY-USE on workers selectively — typically when +> `phase_reached=internalize` is claimed or the output looks +> overconfident. Per-worker-per-wave rotation is overkill for +> routine 200-LoC bundles. See §16 Adoption Tiers. + +### §4.2 Trigger heuristic + +| Worker answer pattern | Lie-Detector | +|---|---| +| Vague ("not sure", "about 60", "around 5 sections") | NO trigger — honest uncertainty. | +| Confident **false** | FIRES — overconfidence + drift. | +| Confident **correct** without proof-of-read | FIRES — luck and hallucination are indistinguishable without LD-1..5. | + +### §4.3 Drift signals (passive detection, meta-agent duty) + +The meta-agent scans EVERY worker status before goal-gate verdict. If +any signal triggers, the worker MUST be re-dispatched OR a Lie-Detector +test MUST be applied: + +| Signal | Action | +|---|---| +| `depth=grep` declared on a semantic task | Re-dispatch with `depth=full` enforced. | +| `depth=sed-partial` declared on a semantic task | Re-dispatch; require SHA-256 over the whole file. | +| `depth=full` declared without `sha256` in proof-of-read | Stop; worker MUST back-fill SHA. | +| Status `notes` cites a non-existent section of a file | LD-3 equivalent failed post-facto → P0 re-dispatch. | +| Status `notes` is word-identical to a sibling agent's without cross-read note | Lockstep drift; spot-check LD-5 on one of the two. | +| Opening output enumerates a specific count ("62 lessons") WITHOUT proof-of-read on the source | Overconfidence trigger; agent MUST back-fill SHA + 3 entries by name. | +| Status touches an INVARIANT but its `proof_of_read` does not include `INVARIANTS.md` | Mandatory `thorough` re-read. | + +### §4.4 Burden-of-proof rule + +On suspicion: the worker MUST upgrade. Never "explain away" the +suspicion — either produce the proof, or honestly downgrade +(`depth=skim`/`grep` with a note). + +--- + +## §5 Stuck Protocol + +### §5.1 Five typed blockers + +| Type | Meaning | +|---|---| +| `AMBIGUITY` | Spec is mehrdeutig. More than one sensible interpretation. | +| `MISSING_INVARIANT` | An Iron Rule should cover this case but does not; the convention is missing. | +| `SPEC_SOURCE_MISMATCH` | The authoritative spec says X; the reference source does Y. | +| `BEHAVIOUR_QUESTION` | Possible bug in the reference source; behavioral divergence requires RFC. | +| `EXTERNAL_DEPENDENCY` | Third-party system zickt; workaround unclear. | + +### §5.2 The protocol + +When stuck, the worker MUST: + +1. STOP writing code. +2. Append ONE entry to `META/REQUESTS-FROM-AGENTS.md` with the + §5.3 schema. +3. Set its `status.json` to `outcome=RETRY` and `notes=stuck:`. +4. Idle until the meta-agent writes to `META/ANSWERS-TO-AGENTS.md` + OR updates `META/INVARIANTS.md`. + +### §5.3 Request schema + +```markdown +## Agent {{agent_id}}, file , timestamp + +### Question + + +### Tried + + +### Blocker + + +### Proof-of-read attached +- file= sha256=<...> lines=<...> depth=<...> +- file= sha256=<...> lines=<...> depth=<...> +``` + +The proof-of-read MUST cover EVERY file the worker references in the +question. Stuck requests without proof-of-read are rejected at +intake (§7.5 in `autoattended-orchestrator-spec.md`). + +### §5.4 Forbidden alternatives + +The worker MUST NOT: + +- Guess. +- Write a `TODO:` comment in code and proceed. +- Refactor outside the bundle "while waiting". +- Refile the same request twice without new information. + +--- + +## §6 Tool-Call Loop Detection + +### §6.1 Adopt attractor coding-agent §2.10 + +A worker MUST scan its own last `N=10` tool calls (signature = name + +arguments hash) for repeating patterns of length 1, 2, or 3. + +``` +Pattern length 1: [A][A][A] — same tool, same args, 3+ times +Pattern length 2: [A][B][A][B] — alternating, 2+ cycles +Pattern length 3: [A][B][C][A][B][C] — 3-cycle, 2+ cycles +``` + +### §6.2 Action on detection + +When detected: + +1. The worker MUST NOT issue the next predicted call in the pattern. +2. The worker MUST emit a typed RETRY-WITH-STEER status: + `outcome=RETRY`, `notes=tool-call-loop:length=:pattern=`. +3. The worker MAY file a `META/REQUESTS-FROM-AGENTS.md` entry under + the `AMBIGUITY` blocker type (§5.1) if the loop has no obvious + external resolution. + +### §6.3 Anti-pattern AP9 + +This rule is also enforced by PP-13 brutally-honest-tester as +anti-pattern AP9 (tool-call-loop). PP-13's post-hoc detection +applies if the in-loop detector failed. + +> **Implementation note (honest caveat).** The current Claude Code +> `Agent()` tool does NOT expose a self-interrupt API. The §6.1 +> detector lives only as a prompt-level instruction; workers cannot +> actually terminate themselves via the tool API — they can only +> emit `outcome=RETRY` after they notice the loop. This catches +> in-LLM oscillation; it does NOT catch OS-level hangs (e.g. +> `cargo build` stuck on a network resolver). For OS-level hangs, +> the orchestrator MUST wrap each spawn in a wall-clock timeout. +> See §16.4. + +--- + +## §7 Proof-of-Read Schema + +### §7.1 Per-file entry + +```json +{ + "file": "path/relative/to/repo-root.md", + "sha256": "abc...64-hex...def", + "lines": 412, + "depth": "thorough", + "comment": "two-pass read; INVARIANTS §3.1-3.4 cited in worker output" +} +``` + +### §7.2 Allowed depth values + +``` +grep sed-partial skim +read thorough troubleshooting +fan-out truncated:head-N truncated:tail-N +``` + +`grep`, `sed-partial`, `truncated:*` MUST NOT be combined with claims +that imply full knowledge of the file (LD-3 / LD-4 / LD-5). + +### §7.3 Multiple-file entries + +Workers MUST emit one entry per file consumed. The orchestrator's +goal-gate (PP-13) MAY spot-check up to three entries per worker per +wave via LD-2 (recompute the SHA, compare line count). + +### §7.4 Storage + +Proof-of-read entries live inside the worker's `status.json` +(per `autoattended-orchestrator-spec.md` §9.1). They are also +mirrored to `META/AGENT_LOG.md` as a one-line summary after each +chunk landed: + +``` +## YYYY-MM-DDTHH:MM — A chunk N/M +- chunk N landed (sha ) +- typecheck: green | +- proof-of-read: sha= lines= depth= +``` + +### §7.5 Sentinel-token storage + +The sentinel token is mirrored from the worker's first-reply line +into the `status.json` `sentinel_token` field. Mismatch = FAIL. + +--- + +## §8 Toolchain Tiers + +### §8.1 Tier 1 (every PR — mandatory) + +Owned by PP-13 brutally-honest-tester. Per-language adapter: + +> **Adoption note.** By default, Tier-1 is **coordinator-side** — +> PP-13 runs the full toolchain once on the consolidated diff. Per- +> worker Tier-1 is expensive at 12-agent fan-out (e.g. ~1 GB Rust +> `target/` per worker). Projects MAY push Tier-1 to per-worker +> (smaller workspaces, faster feedback) by setting +> `tier_1_runner: per-worker` in `INVARIANTS.md`. See §16.3. + +| Purpose | Rust | Python | TypeScript | Go | +|---|---|---|---|---| +| Lint, no warnings | `cargo clippy --all-targets --all-features -- -D warnings` | `ruff check` | `eslint --max-warnings 0` | `golangci-lint run` | +| Formatter check | `cargo fmt --check` | `ruff format --check` | `prettier --check` | `gofmt -l` | +| Advisory scan | `cargo audit` | `pip-audit` | `npm audit --omit=dev` | `govulncheck ./...` | +| Dep policy / vet | `cargo deny check` | `deptry .` | (project-defined) | `go vet -all` | +| Typecheck | (clippy implies) | `mypy --strict` | `tsc --noEmit --strict` | (compiler implies) | +| Tests | `cargo test --all-features` | `pytest` | `vitest run` | `go test ./...` | + +### §8.2 Tier 2 (quality / maintenance — opt-in) + +| Purpose | Rust | Python | TypeScript | +|---|---|---|---| +| Unused-dep detector | `cargo machete` | `deptry .` | `depcheck` | +| Unsafe scan | `cargo geiger` | `bandit` | (n/a) | +| Public-API SemVer compat | `cargo semver-checks check-release` | `griffe check` | `api-extractor` | +| Spellcheck | `cargo spellcheck` | CSpell | CSpell | + +### §8.3 Tier 3 (heavier — opt-in, all stable) + +| Purpose | Rust | Python | +|---|---|---| +| Bounded model checker | `kani` | (none standard) | +| Concurrency model checker | `loom` (lib) | (none standard) | +| Mutation testing | `cargo mutants` | `mutmut` | +| Coverage | `cargo tarpaulin` | `pytest --cov` | + +### §8.4 Tier-1 invariant + +For every PR, every language: the worker proves it satisfies a +no-warning gate BEFORE the orchestrator opens the PR. Tier-1 failure +maps directly to `outcome=FAIL` in the status file (§9.2 of +`autoattended-orchestrator-spec.md`). + +--- + +## §9 Anti-Pattern Catalog (AP1..AP9) + +PP-13 brutally-honest-tester actively hunts these: + +| # | Anti-pattern | Detection | Finding | +|---|---|---|---| +| AP1 | Silent fallback that swallows errors (`unwrap_or_default()`, bare `try { ... } catch {}`, `result, _ := f()`) | grep PR diff for these idioms | P0 if in non-test code path | +| AP2 | Hardcoded secret / token / URL in source | grep for plausible secret regex | P0 always | +| AP3 | Compile-time guard dropped (e.g. `#[cfg(test)]` block removed in non-test context) | diff inspection | P0 if guard was load-bearing | +| AP4 | Test passes by tautology (asserts `true`, `assert x == x`) | read every new test body | P1 unless it's the ONLY test for the function (then P0) | +| AP5 | Behavior divergence from spec without merged RFC | spec vs implementation diff | P0 always | +| AP6 | Missing parity-test for a ported handler | check `tests/parity_/` | P0 always | +| AP7 | New workspace dependency not declared in RFC | metadata diff vs `git log --oneline rfcs/` | P0 always | +| AP8 | `#[allow(clippy::*)]` / `# noqa` / `// eslint-disable` without rationale | grep for these directives, check commit body | P1 unless rationale is missing (then P0) | +| **AP9** | **Tool-call loop (length 1/2/3 over last 10 calls; see §6)** | **in-loop detector OR post-hoc replay of tool-call history** | **P1; re-dispatch with tighter scope** | + +--- + +## §10 Definition of Done + +An implementation is conformant if it satisfies ALL of: + +- [ ] Every worker brief contains a unique `sentinel_token`. +- [ ] Every worker first-reply MUST begin with the sentinel verbatim. +- [ ] Every worker emits a `status.json` per + `autoattended-orchestrator-spec.md` §9.1 with one + proof-of-read entry per file consumed. +- [ ] Proof-of-read entries declare `sha256` + `lines` + a depth + from §7.2's allowed list. +- [ ] Workers run the §6.1 tool-call loop detector after EVERY + tool call, with `N=10`. +- [ ] On loop detection, the worker emits `outcome=RETRY` with + `notes=tool-call-loop:...`. +- [ ] On stuck, the worker uses one of the five §5.1 blocker types + and attaches proof-of-read for every referenced file. +- [ ] The meta-agent / supervisor spot-checks ONE of LD-1..LD-5 + on every **savant** per wave (rotating, so the test cannot + be gamed). On routine **workers**, LD-1 (sentinel) is + mandatory; LD-3 / LD-4 / LD-5 are conditional — applied when + `phase_reached=internalize` is claimed OR the output looks + overconfident. See §16. +- [ ] Drift signals (§4.3) are scanned for every worker before the + goal-gate verdict. +- [ ] PP-13 runs the §8.1 Tier-1 toolchain for the worker's + language; Tier-1 failure → `outcome=FAIL`. +- [ ] PP-13's anti-pattern scan covers AP1..AP9. +- [ ] Tool-output truncation (per `coding-agent-loop-spec.md` §5) + surfaces in proof-of-read as `depth=truncated:head-N` or + `depth=truncated:tail-N`; NEVER `depth=full`. +- [ ] `auto_status=false` is mandatory; missing `status.json` = FAIL. + +--- + +## §11 Cross-Provider Parity Matrix + +| Capability | Claude Code | OpenAI Codex / codex-rs | Gemini CLI | Notes | +|---|---|---|---|---| +| Sentinel-token replay (LD-1) | Available via system prompt addendum | Same | Same | Provider-agnostic; uses standard chat completion. | +| Proof-of-read schema (LD-2) | Worker writes status.json | Same | Same | File-based; provider-agnostic. | +| 3-sections challenge (LD-3) | Supervisor prompt | Same | Same | Provider-agnostic. | +| Negative-knowledge test (LD-4) | Supervisor prompt | Same | Same | Provider-agnostic. | +| Line-range quote (LD-5) | Supervisor prompt + file re-read by supervisor | Same | Same | Provider-agnostic. | +| Tool-call loop detection (§6) | Implementable in `Session.run()` loop wrapper | Same | Same | Loop-detector code is provider-independent (operates on local tool-call history). | +| Toolchain tiers (§8) | Bash tools | Bash tools | Bash tools | Per-language commands identical. | +| Truncation → `depth=truncated:*` | Wrapper around tool-output | Wrapper | Wrapper | Provider-specific in WHAT it truncates (different default tool sets), uniform in HOW it's recorded. | + +--- + +## §12 Appendix A — Worker brief template + +```markdown +You are agent {{agent_id}} in wave {{wave_id}} of sprint {{sprint_id}}. + +You own bundle: {{bundle_name}} +Owned files (read-write): +{{owned_files_table}} + +Read-only files: +{{read_only_files_table}} + +Spec files (authoritative): +{{spec_files_table}} + +INVARIANTS (read these first, depth=thorough): +- META/INVARIANTS.md sha256={{invariants_sha}} lines={{invariants_lines}} + +Reading depth required (per anti-skim-agent-spec.md §3.3): +- INVARIANTS.md → thorough +- spec files → full +- reference source files → full +- skeleton files → read +- everything else → at least read + +Status file: write to {{status_file_path}} matching the schema in +autoattended-orchestrator-spec.md §9.1. + +When stuck: file ONE entry in META/REQUESTS-FROM-AGENTS.md per +anti-skim-agent-spec.md §5; set status to outcome=RETRY; idle. + +Done criteria: per autoattended-orchestrator-spec.md §10 + this +spec's §10. + +Tool-call loop detector: run after every tool call per §6. + +SENTINEL TOKEN: {{sentinel_token}} + +Begin your first reply with the sentinel token verbatim. Then state, +in one paragraph, what you understand the bundle to be. Then begin +work. +``` + +--- + +## §13 Skeleton-Fill Contract (Protocol A) + +> Added 2026-05-18. Applies to workers operating in +> `autoattended-orchestrator-spec.md` Protocol A (implementation), +> where PP-16's preflight has produced a commented-out Rust +> skeleton (per §14.4 of the orchestrator spec). + +### §13.1 What the worker receives + +Three inputs: + +1. The skeleton file(s) at `skeleton_output_path` containing + `todo!("SOURCE: :")` macros at every body site. +2. The original spec / reference-source files referenced in each + `SOURCE:` annotation. +3. A SHA-256 of both the skeleton AND each referenced source file, + pinned by PP-16 at preflight time. + +### §13.2 Worker's obligations + +For every `todo!("SOURCE: :")` the worker fills: + +1. **Read the source range** at the declared depth (§3.3); default + for ports is `full`. +2. **Record proof-of-read** for the source file at the declared + line range (§7.1). +3. **Replace `todo!(...)` with the body**, preserving the surrounding + signature exactly. The signature came from PP-16; the worker + does not change it without an Iron-Rule-amending RFC. +4. **Quote the source line range in the commit message body** (per + `autoattended-orchestrator-spec.md` §5.6). +5. **Confirm the skeleton's SHA-256 against the pinned value** at + start of work. If the skeleton has drifted (e.g. a sibling worker + touched it), the worker MUST STOP and file an `EXTERNAL_DEPENDENCY` + blocker in `META/REQUESTS-FROM-AGENTS.md`. + +### §13.3 Forbidden in skeleton-fill mode + +- Changing a signature provided by the skeleton (RFC required). +- Adding a `todo!()` that did NOT come from the skeleton (would + bypass PP-16's source-annotation discipline). +- Removing a `// SAFETY:` comment from an `unsafe` block (PP-13 + will reject; PP-16 wrote it for a reason). +- Filling bodies in a file outside `skeleton_output_path` (§5.1 + unique-file write discipline). + +### §13.4 Reading depth required + +| Source kind | Minimum depth | +|---|---| +| `SOURCE: :` (the function being ported) | `full` for the named line range | +| `UNSAFE-SOURCE: :` | `thorough` for the named range AND the function's callers | +| The skeleton file itself | `read` (to confirm the surrounding signature you're filling into) | +| `META/INVARIANTS.md` | `thorough` (per §3.3) | + +### §13.5 Validation rules + +| Rule | Description | Severity | +|---|---|---| +| `FILL-001 source-range-read` | Every `todo!("SOURCE: P:L-M")` the worker fills MUST appear in the worker's proof-of-read with `file=P, lines covering L-M, depth=full`. | ERROR | +| `FILL-002 skeleton-sha-pin` | Worker MUST verify the skeleton's SHA-256 against the value PP-16 pinned, at start of work. Drift → STOP + `EXTERNAL_DEPENDENCY`. | ERROR | +| `FILL-003 signature-preserved` | Worker MUST NOT change a signature provided by the skeleton without a merged RFC. Diff inspection by PP-15 enforces. | ERROR | +| `FILL-004 unsafe-safety-preserved` | `// SAFETY:` comments on `unsafe` blocks from the skeleton MUST survive into the filled body. PP-13 enforces. | ERROR | +| `FILL-005 no-new-todo` | Worker MUST NOT introduce new `todo!()` calls; every `todo!()` in the filled file MUST trace back to a skeleton entry. | ERROR | + +### §13.6 Definition of Done (skeleton-fill) + +- [ ] Every `todo!()` in the assigned skeleton file(s) is replaced. +- [ ] No new `todo!()` introduced (FILL-005). +- [ ] Skeleton SHA matched at start; final SHA different (FILL-002). +- [ ] Proof-of-read covers every `SOURCE:` annotation (FILL-001). +- [ ] All signatures preserved (FILL-003). +- [ ] All `// SAFETY:` comments preserved (FILL-004). +- [ ] Tier-1 toolchain green on filled file (§8.1). +- [ ] Status file written per `autoattended-orchestrator-spec.md` §9.1. + +--- + +## §14 Reading Phases (orthogonal to depth) + +> The Reading-Depth Ladder in §3 says **how much** of a file you cover. +> The Reading Phases in this section say **what you do** with what you +> covered. Both are required for a read to count as complete. + +### §14.1 The four phases + +| # | Phase | Question it answers | Output the worker MUST be able to produce | +|---|---|---|---| +| 1 | **Survey** | "What is in this file? What is its shape?" | Section list with line numbers; file shape (N sections, K loc, language); top-level headline. | +| 2 | **Evaluation** | "What of this matters for my task?" | A relevance map: which sections / line ranges are relevant to the current bundle, prioritized P0 / P1 / unused. | +| 3 | **Critical findings** | "What is wrong, missing, contradictory, drifted?" | A typed finding list (severity P0 / P1) — Iron-Rule violations, spec-vs-source drift, missing sections, anchors that no longer resolve, dead references. | +| 4 | **Internalize** | "Can I act on this without re-reading?" | Pass LD-3 (3-section name challenge), LD-4 (negative-knowledge), LD-5 (line-range quote). Can paraphrase faithfully; can answer "what is NOT in this file?". | + +Phase ordering is typically **Survey → Evaluation → Critical findings → +Internalize**, but findings often surface DURING internalize (the act of +internalizing exposes contradictions). A complete reading reaches all +four; a partial reading stops earlier and MUST declare so. + +### §14.2 Phase × depth matrix + +| Depth | Survey | Evaluation | Critical findings | Internalize | +|---|:-:|:-:|:-:|:-:| +| `grep` (anti) | partial | no | no | no | +| `sed-partial` / `head-only` (anti) | partial | partial | no | no | +| `skim` | yes | partial | partial (per-section only) | no | +| `read` | yes | yes | partial | partial | +| `thorough` | yes | yes | **yes** | **yes** | +| `troubleshooting` | yes | yes | yes (focused on the bug) | yes (focused) | +| `fan-out` | yes (per-file shallow) | yes | yes | partial (per file) | +| `truncated:head-N` / `truncated:tail-N` | partial | partial | no | no | + +Only `thorough` and `troubleshooting` (within its focused scope) achieve +the full ladder. `fan-out` achieves internalize *per-file* but the +inventory output is the synthesis — the worker MUST treat each file in +the inventory as needing its own `thorough` pass before acting on it. + +### §14.3 Phase outputs in the worker's status.json + +The `proof_of_read` schema (§7.1) is extended with a `phase_reached` +field naming the highest phase completed for each file: + +```json +{ + "file": "META/INVARIANTS.md", + "sha256": "fa39a3...", + "lines": 412, + "depth": "thorough", + "phase_reached": "internalize", + "phases_evidence": { + "survey": "9 sections; INVARIANTS canonical structure recognized", + "evaluation": "§BBB + §UPSERT-PATTERN are load-bearing for this bundle", + "critical_findings": "§UPSERT-PATTERN line 187 contradicts the customer.ttl ogit:CustomerWriter mandatory-attributes (filed REQUESTS-FROM-AGENTS.md#A4-2026-05-18T14:22)", + "internalize": "can answer LD-3/4/5 verbatim" + } +} +``` + +The `phases_evidence` map is OPTIONAL when `phase_reached=survey` +(no evidence beyond the section list is required) but BECOMES required +at `phase_reached >= evaluation` because that phase claims judgment. + +### §14.4 Mapping Lie-Detector tests to phases + +Each LD-1..LD-5 test (§4.1) probes a specific phase. The meta-agent +SHOULD rotate the spot-check across the four phases so workers cannot +game which phase to fake. + +| Test | Probes phase | What a passing answer proves | +|---|---|---| +| LD-1 Sentinel-Token | **Survey** of the brief | The worker actually loaded the brief into context. | +| LD-2 Proof-of-Read with SHA | **Survey** of the file | The worker accessed the file at the declared content. | +| LD-3 3-Sections Name Challenge | **Survey + Evaluation** | The worker can locate structure AND chose which sections to attend to. | +| LD-4 Negative-Knowledge Test | **Internalize** | The worker built a faithful mental model — can answer "what is NOT in this file" without hallucinating. | +| LD-5 Line-Range Quote | **Internalize + Critical findings** | The worker can recall verbatim AND detect drift between recall and source. | + +A worker declaring `phase_reached=internalize` MUST be able to pass +LD-3, LD-4, **and** LD-5. Spot-check failure at a claimed phase ⇒ +phase claim is rejected and the proof-of-read is auto-downgraded to +the highest demonstrably-passed phase. + +### §14.5 Per-file required phase by file kind + +Different file kinds REQUIRE different minimum phases. The worker +brief MUST declare per-file phase requirements alongside depth +requirements: + +| File kind | Minimum depth | Minimum phase | +|---|---|---| +| `META/INVARIANTS.md` | `thorough` | **internalize** | +| `CLAUDE.md` / `BOOT.md` / RFCs | `thorough` | **internalize** | +| Memory files (`CONTEXT.md` / `JOURNAL.md` / `TODO.md`) | `thorough` | **internalize** | +| Spec files for the bundle (e.g. TTL, OpenAPI, JSON-schema) | `full` (read) | **internalize** | +| Reference-source for ports | `full` | **internalize** | +| Skeleton files the worker fills | `read` | **evaluation** | +| Sibling-bundle files (read-only context) | `skim` | **evaluation** | +| Files referenced for general orientation | `skim` | **survey** | +| Files referenced for symbol lookup only | `grep` | **survey** | + +When a worker stops at a phase lower than required, the read DOES +NOT count as complete — even if the depth was sufficient. Both axes +must clear the bar. + +### §14.6 Critical findings escalation + +Findings produced in phase 3 (Critical findings) are routed by +severity: + +| Finding severity | Filed where | Worker action | +|---|---|---| +| P0 — Iron-Rule violation in input, spec-vs-source contradiction, broken anchor that affects the bundle | `META/REQUESTS-FROM-AGENTS.md` with blocker type from §5.1; worker idles | STOP work; do not proceed to commit | +| P1 — minor inconsistency, dead reference outside bundle scope, typo, stale comment | `Altlasten.md` / `TECH_DEBT.md` row with the worker's bundle id | Continue work; the orchestrator triages later | +| INFO — observation that does not affect the bundle | Notes field in `status.json`; not filed elsewhere | Continue work | + +A worker that internalizes but does NOT escalate a P0 finding is in +violation: missing-escalation is itself a P0 finding that PP-13 +will catch (anti-pattern AP1 — "silent fallback that swallows +errors" — generalizes here as "silent skim that swallows findings"). + +### §14.7 Validation rules + +| Rule | Description | Severity | +|---|---|---| +| `PHASE-001 phase-declared` | Every `proof_of_read` entry MUST include a `phase_reached` field. Absent ⇒ treated as `phase_reached=survey` (the weakest claim). | ERROR | +| `PHASE-002 phase-monotonic-with-depth` | A `phase_reached` claim MUST be consistent with the §14.2 matrix. E.g. `depth=grep` + `phase_reached=internalize` is invalid. | ERROR | +| `PHASE-003 phase-evidence-required` | When `phase_reached >= evaluation`, the `phases_evidence` map MUST be present with non-empty entries for each phase claimed. | ERROR | +| `PHASE-004 file-kind-phase-bar` | When the worker brief declares a minimum phase per §14.5 for a file, the worker's `phase_reached` for that file MUST be ≥ the declared minimum. | ERROR | +| `PHASE-005 critical-findings-routed` | P0 findings produced during phase 3 MUST be filed to `META/REQUESTS-FROM-AGENTS.md` BEFORE the worker commits any code that depends on the affected input. | ERROR | +| `PHASE-006 internalize-passes-LD-3-4-5` | A claim of `phase_reached=internalize` MUST survive spot-checks of LD-3, LD-4, and LD-5 on rotation. Failure ⇒ auto-downgrade to highest-passed phase. | ERROR | + +### §14.8 Definition of Done (per-file read) + +A read of a single file is complete when ALL of: + +- [ ] Depth from §3 is at the declared level. +- [ ] Phase from §14.1 reaches the required minimum per §14.5. +- [ ] `proof_of_read` entry includes `sha256`, `lines`, `depth`, + `phase_reached`, and `phases_evidence` for `evaluation+`. +- [ ] Any P0 critical finding is filed before the worker commits. +- [ ] LD-3 / LD-4 / LD-5 spot-checks pass if `phase_reached=internalize` + is claimed. + +--- + +## §15 Cognitive Anti-Patterns (CA1..CA4) + +> Where AP1..AP9 (§9) catch **output** problems — the code itself +> looks wrong — CA1..CA4 catch **cognition** problems — the way the +> worker arrived at the output is wrong. CA findings often surface +> at the same time as AP findings; the distinction is that CA fixes +> require process change (re-read, re-think, re-spawn) while AP +> fixes can sometimes be edited in place. +> +> The cognitive anti-patterns are jointly owned: the meta-agent +> spots them during PR review via the Lie-Detector (§4); PP-13 +> spots them during code review by correlating commit timestamps +> with proof-of-read timestamps. + +### §15.1 The four cognitive anti-patterns + +| # | Name | What it looks like | Detection signature | Counter-pattern | Severity | +|---|---|---|---|---|---| +| **CA1** | **Cognitive dissonance** | Worker sees a contradiction between two authoritative sources (spec vs reference source, INVARIANT vs comment, TTL vs code) and resolves by hand-waving or picking one without investigation. The dissonance gets papered over instead of escalated. | Output contains phrases like "I went with", "I chose", "I preferred Y because it feels right / is already there / compiles", with no corresponding `SPEC_SOURCE_MISMATCH` entry in `META/REQUESTS-FROM-AGENTS.md`. | File `SPEC_SOURCE_MISMATCH` (§5.1) and idle until the meta-agent writes an RFC. Never resolve dissonance unilaterally. | **P0** | +| **CA2** | **Dunning-Kruger overconfidence** | Worker confidently claims knowledge in an area where its actual depth is shallow. The agent doesn't know what it doesn't know, so its sense of certainty is un-calibrated. The output reads as definite where the read was thin. | A `phase_reached=internalize` claim (§14) that fails LD-3 / LD-4 / LD-5 spot-checks. A specific numerical claim ("62 lessons", "the signature has 3 arguments") with no corresponding `proof_of_read` SHA. Confident paraphrase where the source actually says something else. | Auto-downgrade the phase claim to the highest demonstrably-passed phase (per `PHASE-006`). Re-read at the proper depth + phase. If the worker keeps overshooting, route them to a smaller bundle. | **P0** | +| **CA3** | **Kahneman/Tversky easy-path (System-1 short-circuit)** | Worker pattern-matches surface features of the input — "file looks like a CRUD handler, so it must be a CRUD handler" — without running the System-2 check (read the actual function body, compare against the spec). Easy-path is fastest when surface matches reality, but devastating when it doesn't. | The first reply is plausibly-correct-sounding but the proof-of-read is `depth=grep` or `depth=sed-partial`. Output describes structure in generic terms ("standard route handler", "typical migration") rather than specific terms ("the function on lines 47-91 dispatches on req.path"). | Force System-2: require LD-2 (SHA + line count) and LD-5 (line-range quote) before accepting any structural claim. Reading depth MUST be at least `read`; phase MUST be at least `evaluation` before any output that makes a structural claim. | **P0** | +| **CA4** | **Eager amok** | Worker starts writing code (or commits, or pushes) before completing the required reading + planning phases. Enthusiasm runs ahead of discipline. The work *looks* productive — there's a diff — but it's built on incomplete understanding. | First code-write timestamp predates the `proof_of_read` SHA-pin timestamps for one or more required files (§14.5). `status.json` shows commits landing while `phase_reached` is still `survey` or `evaluation` on files that should be at `internalize`. Worker's narrative jumps from brief-read to first-commit with no visible thinking step. | STOP and require complete proof-of-read for ALL files at their required §14.5 minimum phase BEFORE any commit. The Iron Rule applies regardless of how "obvious" the implementation seems. | **P0** | + +### §15.2 Why all four are P0 + +Each of CA1..CA4 produces output that *looks* right. AP1..AP9 produce +output that looks wrong (a `unwrap_or_default()` is visible; a missing +parity test is visible). Cognitive anti-patterns produce output whose +correctness depends entirely on the unverifiable claim that the +worker read + understood + thought before writing. They are P0 +because they break the trust contract the meta-agent depends on when +reviewing diffs without re-doing the worker's read. + +### §15.3 Joint ownership: who catches what + +| Anti-pattern | Catch site | Detection method | +|---|---|---| +| CA1 cognitive dissonance | Meta-agent (PR review) + PP-15 (cross-source diff) | Grep PR commit messages for "I went with" / "I chose" / "preferred" against spec-vs-source mismatch; check `REQUESTS-FROM-AGENTS.md` for absence of corresponding blocker. | +| CA2 Dunning-Kruger overconfidence | Meta-agent (Lie-Detector spot-check) | Rotate LD-3 / LD-4 / LD-5 on one worker per wave; cross-check `phase_reached` claims against actual passing. | +| CA3 Kahneman/Tversky easy-path | Meta-agent (proof-of-read inspection) + PP-13 (output-vs-source diff) | Worker output reads as paraphrase rather than quote; SHA missing; reading depth declared inconsistent with output claims. | +| CA4 eager amok | PP-13 (commit-timestamp audit) + Meta-agent (status.json ordering check) | Commit timestamps predate the `proof_of_read` SHA-pin timestamps; `phase_reached` was below the required minimum at first commit. | + +### §15.4 Counter-patterns: how a healthy worker behaves + +| Anti-pattern | Healthy alternative | +|---|---| +| CA1 | "I noticed the spec says X but the existing code does Y. Filing `SPEC_SOURCE_MISMATCH`. Idling." | +| CA2 | "I am confident about §3 of INVARIANTS.md (depth=thorough, phase=internalize). I am uncertain about §6 (depth=skim, phase=survey). I will deepen §6 before claiming anything about it." | +| CA3 | "Before describing this file's structure, here is `proof_of_read: { file=X, sha256=..., depth=read, phase_reached=evaluation }`. The structure is: [specific section names with line numbers]." | +| CA4 | "Reading phase complete on all 4 required files. Filing critical findings (zero P0). Status: `phase_reached=internalize` on all files. NOW beginning the first commit." | + +### §15.5 Validation rules + +| Rule | Description | Severity | +|---|---|---| +| `CA-001 dissonance-escalation` | When a worker's output mentions spec-vs-source divergence, `META/REQUESTS-FROM-AGENTS.md` MUST contain a corresponding `SPEC_SOURCE_MISMATCH` blocker entry. Absence ⇒ CA1 P0 finding. | ERROR | +| `CA-002 confidence-calibration` | A `phase_reached=internalize` claim that fails LD-3 / LD-4 / LD-5 spot-check ⇒ CA2 P0; phase claim auto-downgraded. | ERROR | +| `CA-003 system-2-required-before-structural-claim` | Any structural claim about a file's content (function count, section names, signature shapes) MUST be preceded by a `proof_of_read` entry with `depth >= read` AND `phase_reached >= evaluation`. Otherwise CA3 P0. | ERROR | +| `CA-004 read-before-write-ordering` | `status.json` MUST show all required-minimum `proof_of_read` entries timestamped BEFORE the first code-commit timestamp for files at the §14.5-required minimum phase. Otherwise CA4 P0. | ERROR | + +### §15.6 Mindset-level relation to the four savants + +The four savants in `autoattended-orchestrator-spec.md` §4.0 each +have a *cognitive* posture that resists a specific CA: + +| Savant | Mindset | Primarily resists | +|---|---|---| +| PP-13 brutally-honest-tester | "what would break in production at 3 a.m. that the author talked themselves out of seeing?" | CA1 (talking-yourself-out-of-seeing IS cognitive dissonance) + CA4 (production doesn't break because of enthusiasm) | +| PP-14 convergence-architect | "what could this become that we aren't seeing?" | CA3 (easy-path closes possibilities prematurely) | +| PP-15 baton-handoff-auditor | "do these contracts actually line up at the seams?" | CA1 (dissonance often hides at handoffs) | +| PP-16 preflight-drift-auditor | "does the plan still match the system as it actually is?" | CA2 (overconfidence in a plan that has drifted from reality) | + +The full 4-savant pass is jointly an antibody against all four +cognitive anti-patterns. A wave that runs only one savant catches +only the CAs that savant's mindset resists; a wave that runs the +cooperative council (orchestrator spec §15) catches all four. + +### §15.7 Definition of Done (cognitive-hygiene) + +A worker passes the cognitive-hygiene gate when ALL of: + +- [ ] `CA-001` clean: every spec-vs-source mention is matched by a filed `SPEC_SOURCE_MISMATCH` blocker. +- [ ] `CA-002` clean: every `phase_reached=internalize` claim survives LD-3/4/5 spot-checks. +- [ ] `CA-003` clean: every structural claim is backed by a sufficient `proof_of_read`. +- [ ] `CA-004` clean: read timestamps precede commit timestamps for all required-minimum files. +- [ ] Worker did not output `I went with` / `I chose` / `I preferred` without an associated blocker filing. + +--- + +## §16 Adoption Tiers (project-specific) + +> Specs ship a maximalist surface so every reasonable use-case is +> covered. Real projects adopt subsets. This section names what a +> project SHOULD adopt on day one (Tier-A), what's worth the cost +> only at high stakes (Tier-B), what's project-overrideable +> (Tier-C), and what has honest known limits (Tier-D). +> +> **Origin:** field-test feedback from a 12-agent Rust fan-out +> (sprints PR-X4 cascade / PR-X10..13 consolidation / PR-X12 codec) +> where ~1 GB per-worker `target/` dirs made universal per-worker +> Tier-1 toolchain prohibitively expensive, and where the +> `Agent()` API's missing self-interrupt forced the §6.1 detector +> to be prompt-level only. + +### §16.1 Tier-A — adopt on day one (minimum viable) + +The minimal surface delivering ≥80% of the framework's value at +≤20% of the ceremony. + +| § | What | Why mandatory | +|---|---|---| +| §3 | Reading-Depth Ladder per file | Single most important anti-skim primitive. | +| §5 | 5 typed stuck blockers (`AMBIGUITY` / `MISSING_INVARIANT` / `SPEC_SOURCE_MISMATCH` / `BEHAVIOUR_QUESTION` / `EXTERNAL_DEPENDENCY`) | Replaces vague "report under 200 words" with crisp typed routing. | +| §7 | `proof_of_read` schema in `status.json` | `{file, sha256, lines, depth, phase_reached}`. Trivial overhead; big audit win. | +| §14 | Reading Phases per file + §14.5 per-file-kind minimum | Catches "agent confidently asserts file content without reading" — the most common silent-skim failure. | +| §15 | CA1..CA4 cognitive anti-pattern framing | The lens a savant audit already uses; cheap to adopt. | +| §15.6 | 4-savant council for P0 review on consolidation sprints | High-leverage audit; runs on Opus, once per sprint. | +| §4.1 LD-1 | Sentinel tokens | ~50 LoC ceremony per brief; catches the trivial "agent didn't read the brief" case. | +| §6 | Prompt-level tool-call-loop instruction (best-effort, see §16.4) | Catches in-LLM oscillation when the worker honors the instruction. | +| §17 | PR-Lifecycle + Auto-Resolve (worker creates PR, subscribes to CI/review events, classifies each event silent-fix / ask-user / skip) | A worker's "done" is PR merged or PR closed, not branch pushed. Without §17 the framework stops at push and the human owns the CI loop. | + +### §16.2 Tier-B — adopt for savants / high-stakes only + +Reserve for review agents (PP-13/14/15/16) and high-stakes worker +spawns. Skip for routine 200-LoC skeleton-fills. + +| § | What | When to adopt | +|---|---|---| +| §4.1 LD-3 / LD-4 / LD-5 | 3-section challenge / negative-knowledge / line-range quote | On savants **every wave**. On routine workers **only when** `phase_reached=internalize` is claimed OR the output looks overconfident. Not as routine per-worker per-wave rotation. | +| §13 Skeleton-Fill (Protocol A) | `todo!("SOURCE:")` markers force read-before-fill | High-stakes ports (Python→Rust transcode, x265→Rust codec) where source fidelity matters. Skip for greenfield prose features. | +| Full §10 / §13.6 / §14.8 / §15.7 DoD checklists | Per-PR DoD verification | Per-sprint, not per-PR, until v0.2 prune. See §16.6. | + +### §16.3 Tier-C — project-overrideable defaults + +Defaults this spec recommends but which real projects often override. +The override SHOULD be documented in the project's `INVARIANTS.md`. + +| § | Default | Override scenario | Project's typical choice | +|---|---|---|---| +| §8.1 Tier-1 toolchain | Coordinator-side (PP-13 runs once on consolidated diff) | Small workspace where per-worker compile is fast | `tier_1_runner: per-worker` | +| §4.1 LD-3/4/5 rotation | Spot-check only on savants + overconfident workers | Project wants per-worker per-wave rotation | `lie_detector_rotation: per-worker-per-wave` | +| §13 Skeleton-Fill | Opt-in per sprint via `preflight.skeleton: enabled` | Greenfield code with no SOURCE to reference | `preflight.skeleton: disabled` (default for greenfield) | +| §14.5 per-file-kind minimum phase | INVARIANTS.md / spec / reference = internalize | Project's INVARIANTS lives elsewhere or doesn't exist | Project-defined `file_kind_phase_map` in `INVARIANTS.md` | + +### §16.4 Tier-D — known-limited (honest caveats) + +| § | Caveat | +|---|---| +| §6.1 in-loop tool-call detection | The current Claude Code `Agent()` tool does NOT expose an interrupt API. The detector lives only as a prompt-level instruction. Catches in-LLM oscillation; does NOT catch OS-level hangs. Wrap each spawn in a wall-clock timeout at the orchestrator layer for OS hangs. | +| §8.1 per-worker Tier-1 | On Rust workspaces with ~1 GB per-worker `target/` dirs, per-worker Tier-1 at 12-agent fan-out is disk-prohibitive. Use the §16.3 coordinator-side override. | +| §4.1 LD-1 sentinel tokens | Slight per-brief ceremony (~50 LoC). Project MAY template the token at orchestrator level rather than per-worker brief to amortize the cost. | +| Spec line-count at v0.1.0 DRAFT | This spec is intentionally maximalist for the first revision. Plan a v0.2 prune of the four DoD checklists (§10 / §13.6 / §14.8 / §15.7) once one real sprint has run with the framework — keep only items that caught field bugs. | + +### §16.5 Sprint-by-sprint adoption worked example + +A 12-agent Rust fan-out doing cascade + codec + consolidation: + +| Sprint | Tier-A adopted | Tier-B adopted | Tier-C overrides | +|---|---|---|---| +| PR-X4 splat cascade (12 workers, internal refactor) | full | LD-1 sentinel only; skip LD-3/4/5 on routine workers | `tier_1_runner: coordinator` | +| PR-X12 codec (Rust port of x265 reference) | full | full §13 Skeleton-Fill (high-stakes source-fidelity port); LD-3/4/5 on consolidation worker | `tier_1_runner: coordinator` | +| PR-X10+11+13 consolidation review (savant pass) | n/a (review pass) | full §15.6 four-savant council on Opus | n/a | + +### §16.6 v0.2 prune targets (slim once field-tested) + +After one full sprint with this spec, slim the following sections to +items that produced field-tested catches: + +| Section | Current items | Slim target | +|---|---:|---| +| §10 Definition of Done | 13 | keep ~5 that catch real bugs | +| §13.6 Skeleton-Fill DoD | 7 | keep ~3 | +| §14.8 per-file-read DoD | 5 | keep ~3 | +| §15.7 cognitive-hygiene DoD | 5 | keep ~3 | +| §8.1 / §8.2 / §8.3 Tier tables | full per-language matrix | slim to entries actually run in the field | + +Goal: spec line-count drops 30-50% by v0.2 while preserving the +patterns that caught real bugs. + +--- + +## §17 PR-Lifecycle + Auto-Resolve + +> Workers do not stop at "branch pushed." A worker's done condition +> is **PR merged** OR **PR closed by orchestrator/human as FAIL** — +> the lifecycle extends through CI iteration and review-comment +> resolution. This section specifies what happens between the +> worker's first push and one of those terminal states. +> +> **Tier:** Tier-A per §16.1 for any sprint that opens PRs. +> **Transport:** `mcp__github__create_pull_request` + +> `mcp__github__subscribe_pr_activity` + `git push` for fixes; events +> arrive as `` envelopes (see +> `agent-coordination-mcp-spec.md` §3.3). + +### §17.1 The extended worker lifecycle + +``` +READ → WRITE → TEST → COMMIT → PR → SUBSCRIBE → [AUTO-RESOLVE LOOP] → MERGE | FAIL + ↑ ↓ + │ event arrival + │ classify (§17.4) + │ silent-fix | ask-user | skip + └ if silent-fix: push commit +``` + +Steps 1-4 are the existing §13.2 work pattern. Steps 5-7 are new. +MERGE / FAIL are terminal — worker writes its final `status.json` +and calls `mcp__github__unsubscribe_pr_activity`. + +### §17.2 PR creation (step 5) + +After first push, the worker calls: + +``` +mcp__github__create_pull_request( + owner=, + repo=, + head=, # e.g. claude/wave-12-A4 + base=, # from sprint header + title=": (WT-NN)", + body="""\ +## Bundle ownership +{{ownership_table}} + +## Status +{{status_json_excerpt}} + +## Proof-of-read +{{proof_of_read_summary}} + +## Sentinel +{{sentinel_token}} +""", + draft=, # per sprint header + labels=["sprint-", "bundle-"] +) +``` + +The PR body MUST contain the sentinel token verbatim. The +orchestrator can grep open PRs for sentinel presence as a fast LD-1 +check (per `PR-007`). + +### §17.3 Subscription (step 6) + +Within 60 seconds of PR creation, the worker MUST call: + +``` +mcp__github__subscribe_pr_activity(owner, repo, pullNumber) +``` + +The worker then enters an event-driven idle state. Events arrive +as `` envelopes containing CI run +completions, review comments, PR state changes (merged/closed), +and label changes. + +### §17.4 The three-option event-handling decision tree + +For each event, the worker MUST classify into one of three options +(this matches the CCA2A handling protocol from the governing prompt +template): + +| Option | When | Action | +|---|---|---| +| **1. Silent fix** | Confidence high; fix in-scope of worker's bundle; fix does not contradict spec / INVARIANTS | Read failing CI logs / review comment fully (§3 depth ladder, §14.5 minimum phase for log files: `read` + `phase=evaluation`); make the fix; push commit; update `status.json`; continue idle for next event. Reply on PR ONLY if the fix resolves a specific review question. | +| **2. Ask user (file PROPOSAL)** | Ambiguity (CA1 / CA3 trigger); cross-bundle implications; spec-vs-comment contradiction; reviewer asks "should X be Y or Z?" | Append `Kind=PROPOSAL` entry to `AGENT_LOG.md` (`agent-coordination-mcp-spec.md` §1.3) with the question; idle until orchestrator/user answers via `ANSWERS-TO-AGENTS.md`. Do NOT push speculative fixes. | +| **3. Skip silently** | Duplicate event; stale CI; "LGTM" / approval; bot comment (Dependabot et al); event addressed in a prior iteration | Record the classification on `AGENT_LOG.md` (`Kind=DECISION`, body: `skip-silent: `); continue idle. Do NOT reply on the PR. | + +### §17.5 Event taxonomy + default classification + +| Event class | Specific signal | Default option | Confidence required | +|---|---|---|---| +| CI lint failure | rustfmt / clippy single-line | silent fix | high | +| CI typecheck failure | single error, in-bundle | silent fix | high | +| CI typecheck failure | error in shared zone | ask-user (cross-bundle) | n/a | +| CI test failure | regression in own bundle; root cause identifiable from log | silent fix | medium | +| CI test failure | regression elsewhere | ask-user | n/a | +| CI build-time-out | first occurrence | skip-silent (re-CI); if recurring → ask-user | n/a | +| CI dependency-resolve failure | new transitive dep, missing crate, version conflict | ask-user (`EXTERNAL_DEPENDENCY` blocker) | n/a | +| CI OOM | first occurrence | ask-user (orchestrator allocates memory) | n/a | +| Review comment "could you also X" | X is in-bundle | silent fix | medium | +| Review comment "should this be X or Y?" | ambiguity | ask-user (`AMBIGUITY` blocker) | n/a | +| Review comment "blocking — need RFC" | spec-vs-impl mismatch | ask-user (`SPEC_SOURCE_MISMATCH` blocker) | n/a | +| Review comment "LGTM" / approval | terminal-success-adjacent | skip-silent | n/a | +| Bot comment (Dependabot, codecov, sonarcloud, …) | informational | skip-silent | n/a | +| @-mention to specific human user | belongs to a human | skip-silent (do not reply) | n/a | +| PR merged | terminal | write `outcome=SUCCESS`, unsubscribe, exit | n/a | +| PR closed without merge | terminal (orchestrator/human FAIL'd it) | write `outcome=FAIL`, unsubscribe, exit | n/a | + +### §17.6 Budget + termination + +| Limit | Default | When breached | +|---|---|---| +| Max auto-fix iterations per PR | 5 | Worker emits `outcome=FAIL`, body: `auto-resolve-budget-exhausted`; orchestrator decides next | +| Max wall-clock per PR | 24 h (project-configurable) | Same as above | +| Per-iteration token cost cap | per §16 model-stylesheet | Escalate to Opus only if stylesheet allows | +| Consecutive `skip-silent` events | 20 | Worker writes a `STATUS` entry noting the silence and re-subscribes (the PR is in a quiet state) | + +### §17.7 Cognitive anti-patterns extend to auto-resolve + +Every CA1..CA4 from §15 applies to each auto-resolve iteration, not +just the initial implementation. Validation rules CA-001..CA-004 +apply unchanged. PR-005 below extends the cross-bundle rule. + +| Anti-pattern | Auto-resolve-specific shape | +|---|---| +| CA1 cognitive dissonance | Worker reads a review comment that contradicts INVARIANTS and "just fixes per the comment" without filing `SPEC_SOURCE_MISMATCH`. | +| CA2 Dunning-Kruger overconfidence | Worker pushes an auto-fix without `proof_of_read` on the failing log; claims `internalize` of the failure without LD evidence. | +| CA3 Kahneman/Tversky easy-path | Worker pattern-matches "CI red → push the same fix as last time" without reading the actual failure. | +| CA4 eager amok | Worker pushes a fix before reading the full CI log; commits before completing §17.4 classification. | + +### §17.8 Wire-format integration + +Every auto-resolve action emits an entry on `AGENT_LOG.md` per +`agent-coordination-mcp-spec.md` §1.3: + +```markdown +## 2026-05-19T08:14 — DECISION[INFO]: A4 silent-fix clippy::redundant_clone (sonnet, claude/wave-12-A4) + +**Author:** A4 +**Kind:** DECISION +**Severity:** INFO +**Refs:** PR-#427 commit-fa00b1c +**Proof-of-read:** +- file=ci/clippy-log.txt sha256=8e1a... lines=47 depth=full +- file=src/customer/master.rs sha256=4c0b... lines=312 depth=read + +--- + +Clippy flagged a `Vec.clone()` on line 187. Reviewed the call site; +the clone was leftover from a prior refactor. Replaced with `&borrow`. +Commit fa00b1c pushed. Awaiting re-CI. +``` + +Auto-resolve actions are first-class on the wire format — the +orchestrator and any subscribed sibling session see them in real +time. + +### §17.9 Validation rules + +| Rule | Description | Severity | +|---|---|---| +| `PR-001 worker-created-PR` | Within 30 minutes of first push, the worker MUST have created a PR via `mcp__github__create_pull_request`. | ERROR | +| `PR-002 subscription-active` | Within 60 seconds of PR creation, the worker MUST have called `mcp__github__subscribe_pr_activity`. | ERROR | +| `PR-003 max-iterations` | Auto-resolve MUST NOT exceed §17.6 budget without escalation. Budget exhaustion ⇒ `outcome=FAIL`, reason `auto-resolve-budget-exhausted`. | ERROR | +| `PR-004 cognitive-hygiene-extends` | CA-001..CA-004 (§15.5) apply unchanged to every auto-resolve iteration, not just initial implementation. | ERROR | +| `PR-005 cross-bundle-fix-forbidden` | A worker MUST NOT push commits that touch files outside its bundle, even during auto-resolve. Cross-bundle failures escalate via `AGENT_LOG.md` `Kind=PROPOSAL`. | ERROR | +| `PR-006 event-classification-recorded` | Each event the worker receives MUST be classified per §17.4 and the classification appended to `AGENT_LOG.md` (`Kind=DECISION`). | ERROR | +| `PR-007 sentinel-in-pr-body` | The PR body MUST contain the worker's sentinel token verbatim. | ERROR | +| `PR-008 unsubscribe-on-terminal` | On PR merge or close, the worker MUST call `mcp__github__unsubscribe_pr_activity` before writing its terminal `status.json`. | ERROR | +| `PR-009 no-reply-on-skip` | A `skip-silent` classification MUST NOT produce a reply on the PR (avoid PR-noise pollution from agents). | WARNING | + +### §17.10 Definition of Done (auto-resolve) + +A worker passes the auto-resolve gate when ALL of: + +- [ ] Created a PR (`PR-001`). +- [ ] Subscribed within 60s (`PR-002`). +- [ ] Each event classified per §17.4 and recorded on `AGENT_LOG.md` (`PR-006`). +- [ ] No cross-bundle fix commits (`PR-005`). +- [ ] CA-001..CA-004 clean on every iteration (`PR-004`). +- [ ] Sentinel token in PR body (`PR-007`). +- [ ] Auto-resolve budget not exceeded without escalation (`PR-003`). +- [ ] Unsubscribed and wrote terminal `status.json` (`PR-008`). +- [ ] Terminal outcome is `SUCCESS` (PR merged) OR `FAIL` (PR closed without merge OR budget exhausted) OR `RETRY` (worker filed a blocker and is waiting for orchestrator answer). + +### §17.11 Adoption-tier placement + overrides + +This section is **Tier-A** (mandatory) per §16.1 for any sprint +that opens PRs. Two project overrides are possible (Tier-C): + +| Setting | Default | Override scenario | Override value | +|---|---|---|---| +| Per-worker PR creation (§17.2) | Each worker opens its own PR | Project prefers a single orchestrator-consolidation PR per wave (per `autoattended-orchestrator-spec.md` §3.4) | `pr_ownership: orchestrator-consolidation` in `INVARIANTS.md` | +| Subscription transport (§17.3) | `mcp__github__subscribe_pr_activity` native MCP | Environment without GitHub MCP (offline, rate-limited, non-GitHub host) | `pr_event_transport: polling` (60 s interval via `mcp__github__pull_request_read`) | + +Tier-D honest caveat: `subscribe_pr_activity` requires network + +GitHub API rate budget (5000 req/h authenticated). For 12-agent +fan-outs running long auto-resolve loops, monitor rate-limit +consumption; the orchestrator MAY consolidate subscriptions onto +itself (one subscription per PR with the orchestrator re-broadcasting +events to interested workers) to amortize the API budget. + +--- + +*End of `anti-skim-agent-spec.md`.* diff --git a/.claude/ATT/autoattended-orchestrator-spec.md b/.claude/ATT/autoattended-orchestrator-spec.md new file mode 100644 index 0000000000..4f6b8fbf52 --- /dev/null +++ b/.claude/ATT/autoattended-orchestrator-spec.md @@ -0,0 +1,1231 @@ +# Autoattended Orchestrator Specification + +> **Status:** DRAFT · **Version:** 0.1.0 · **Last updated:** 2026-05-17 +> **Format:** NLSpec (per [strongdm/attractor](https://github.com/strongdm/attractor)) +> **Substrate:** Designed to compose on top of `attractor-spec.md`; runs +> as the `house`-shaped `manager_loop` node of an attractor pipeline. + +--- + +## §1 Overview + +### §1.1 Purpose + +The Autoattended Orchestrator runs wave-based, fan-out / fan-in sprints +of LLM worker agents against a codebase, with four specialized review +gates (the "4 savants") at fixed lifecycle positions. It is the +project-agnostic distillation of the pattern described in +[`AdaWorldAPI/WoA/.claude/v0.1/CLAUDE-CONTEXT.md`](https://github.com/AdaWorldAPI/WoA/blob/main/.claude/v0.1/CLAUDE-CONTEXT.md), +hardened across 26 repositories on 2026-05-17. + +### §1.2 Central abstractions + +- **Wave**: one parallel fan-out of N worker agents (typical N = 12), + ending in a single consolidation commit by the orchestrator. A + sprint is a sequence of waves. +- **Worker**: one LLM-agent process owning one **bundle** of files. + Workers run isolated (`isolation: "worktree"`), do not read other + workers' in-flight state, coordinate only through the orchestrator + and the file-blackboard. +- **Savant**: one of four specialized review roles (PP-13 / PP-14 / + PP-15 / PP-16) that gate the sprint at fixed lifecycle positions. +- **Iron Rule**: a non-overridable invariant declared in `INVARIANTS.md`, + enforced by every worker and by the meta-agent at PR review. + +### §1.3 Relation to `attractor-spec.md` + +The orchestrator is realizable as an attractor pipeline whose DOT +graph has the canonical shape: + +``` +start (Mdiamond) + → preflight-drift-auditor (box, PP-16 verdict gate) + → fan-out (component, N=12) + → fan-in (tripleoctagon, consolidate commit) + → baton-handoff-auditor (box, PP-15) + → brutally-honest-tester (box, PP-13, goal_gate=true, retry_target=fan-out) + → exit (Msquare) +``` + +PP-14 (convergence-architect) runs at PRE-PLAN, *outside* the graph, +on the proposed sprint plan before the graph is rendered. + +### §1.4 What this spec is NOT + +- Not a pipeline runner. It assumes attractor-spec.md (or an equivalent) + is the runtime. +- Not a coding-agent loop. Per-worker behavior is in + [`anti-skim-agent-spec.md`](./anti-skim-agent-spec.md). +- Not a coordination transport. The cross-agent message bus is in + [`agent-coordination-mcp-spec.md`](./agent-coordination-mcp-spec.md). + +--- + +## §2 Terminology + +| Term | Definition | +|---|---| +| **Sprint** | A sequence of one or more waves toward a single planned outcome (a feature, a port, a refactor). | +| **Wave** | One parallel fan-out + fan-in cycle. | +| **Bundle** | The set of files one worker owns (read-write); declared in `META/SPRINT-N-PLAN.md`. | +| **Ownership table** | The per-worker mapping of bundle → owned-files + read-only-files. | +| **Iron Rule** | A non-overridable invariant; violation is a P0 finding. | +| **Goal gate** | A node whose verdict must be SUCCESS for the wave to exit. Implements attractor §3.4. | +| **Savant** | PP-13 / PP-14 / PP-15 / PP-16. See §4. | +| **Meta-agent** | The 13th agent. Owns plan review, inbox drain, PR review. | +| **REQUESTS-FROM-AGENTS.md** | The append-only inbox for stuck-worker messages. | +| **INVARIANTS.md** | The single source of truth for cross-cutting rules. | + +--- + +## §3 The Wave Loop + +### §3.1 Six steps + +``` +1. Plan → orchestrator partitions work into N bundles +2. Sprint → N parallel worker agents +3. Meta review → one or more savant agents +4. Fix P0s → orchestrator applies fixes + verifies tier-1 gates +5. Commit + PR → orchestrator (one PR per bundle OR one combined PR) +6. Repeat → orchestrator plans the next wave +``` + +### §3.2 Single iron rule across all steps + +The orchestrator is the only role allowed to write outside its +assigned bundle. Workers stay in their lane. Savants are read-only +(they file findings; the orchestrator applies fixes). + +### §3.3 Wave sizing + +| Wave size | Worker model | Use when | +|---|---|---| +| 3-6 | mid-tier (e.g. Sonnet) | tight scope, well-defined bundles, low cross-cutting concern | +| 7-12 | mid-tier | the standard wave shape — most work fits here | +| > 12 | mid-tier, split | pre-fan into 2 waves with a savant pass between | +| 1-2 (planning) | top-tier (e.g. Opus) | architecture / design / cross-cutting decisions | +| 1 (savant) | mid-tier or top-tier | per §4 | + +A 12-worker wave costs approximately **250,000 to 350,000 input +tokens** (bundle briefings + reference reads) plus **30,000 to +50,000 for the savant pass**. Implementations MUST track this against +the model's context-window budget per worker and reject plans whose +per-bundle brief exceeds the configured `worker.max_brief_tokens`. + +### §3.4 The consolidation pass (step 5 ⇒ atomic commit) + +After all workers in a wave return, the orchestrator does ONE +consolidation commit that: + +1. Pulls in each bundle's commits in dependency order +2. Resolves the append-only merge zones (module registry, + integration test list, etc.) declared in INVARIANTS.md +3. Re-runs tier-1 gates against the combined state (see + [`anti-skim-agent-spec.md` §8](./anti-skim-agent-spec.md)) +4. Files one PR (or N PRs if bundles are truly independent) + +Implementations MUST NOT allow 12 mini-commits at the shared registry +file. Worker pushes to shared-registry files are rejected at the +worker layer (per the unique-file write rule, §5.1). + +--- + +## §4 The 4 Savant Slots + +### §4.0 Mindsets (not roles) + +Each savant is a **mindset** — a way of thinking, not a checklist +operator. The verdict vocabulary in §4.1 is the *output*; the +mindset in this section is the *input* the savant's system prompt +should activate. + +| Savant | Mindset persona | Frame they read through | Failure they exist to catch | +|---|---|---|---| +| **PP-13 brutally-honest-tester** | The implementation principal who has shipped + on-called for ten years | "what would break in production at 3 a.m. that the author talked themselves out of seeing?" | Self-deception in implementation-quality claims | +| **PP-14 convergence-architect** | The creative-design / R&D principal exploring divergent possibilities | "what could this become that we aren't seeing? what latent shared shape is being duplicated?" | Premature convergence and missed cross-slice synergies | +| **PP-15 baton-handoff-auditor** | The DTO / interface architect — the person who lives in the gaps between components | "do these contracts actually line up at the seams? whose hand goes on the baton next?" | Cross-boundary drift; silent contract-shape mismatches | +| **PP-16 preflight-drift-auditor** | The principal system architect with full system-state in their head | "does the plan still match the system as it actually is right now, or did main move while we were planning?" | Stale plans against a moving main; dropped requirements | + +The system prompt for each savant SHOULD begin with the mindset +sentence, not with the verdict checklist. The checklist is a +backstop; the mindset is the lens. + +### §4.1 Slot table + +| Savant | Phase | Owns | Verdict vocabulary | +|---|---|---|---| +| **PP-13 brutally-honest-tester** | POST-IMPL | within-bundle compile / lint / test / spec match | LAND / HOLD / REJECT | +| **PP-14 convergence-architect** | PRE-PLAN | divergent ideation, latent shared infrastructure | OPPORTUNITY-NOW / WORTH-EXPLORING / DROP (never REJECT) | +| **PP-15 baton-handoff-auditor** | DURING-IMPL | cross-bundle / cross-crate / cross-module boundary contracts | CATCH-CRITICAL / CATCH-LATENT / CLEAN | +| **PP-16 preflight-drift-auditor** | PRE-SPAWN | spec-vs-main drift, hand-waved scope, dropped requirements | SPAWN-CLEAR / SPAWN-CAUTION / SPAWN-BLOCKED | + +### §4.2 Verdict-vocabulary non-overlap is a design invariant + +Each savant has a **non-overlapping verdict vocabulary** so a finding +cannot cross phases without re-classification. The meta-agent uses +its own vocabulary (GO / GO-WITH-CONDITIONS / BLOCK for plan review; +P0 / P1 for PR review). + +If a savant returns a verdict from another savant's vocabulary, the +finding is rejected at intake and routed back. This is ERROR +severity in validation (§7). + +### §4.3 Non-use → route table + +Each savant's prompt MUST include explicit "non-use → route to PP-X" +lines. Examples: + +- PP-13 finds a cross-crate boundary issue → route to PP-15. +- PP-15 finds a spec-vs-main drift → route to PP-16. +- PP-16 finds a within-bundle compile error → route to PP-13. +- PP-14 finds anything compile-related → route to PP-13. + +### §4.4 PP-13 specifics + +Owns the canonical toolchain tier-1+2+3. Tier 1 runs every PR; tiers +2+3 opt-in. See [`anti-skim-agent-spec.md` §8](./anti-skim-agent-spec.md#8-toolchain-tiers) +for the language adapter tables. + +Anti-pattern catalog: AP1..AP8. + +### §4.5 PP-15 specifics + +Owns cross-boundary commands: workspace-level lint/typecheck, +public-API diff (`cargo public-api`, `api-extractor`, `griffe check`), +cross-symbol grep, cross-repo `git log`, metadata dump. + +Anti-pattern catalog: BAP1..BAP10. Eight boundary classes: +module-module, crate-crate, package-package, repo-repo, public-API, +DTO/wire-format, DB-schema-to-ORM, CLI-surface. + +### §4.6 PP-16 specifics + +Owns git + grep only — `git log master`, `git show`, +`git diff main...HEAD`, list-pull-requests, grep old symbols across +`.claude/plans/` and `.claude/specs/`. Does NOT run compile/test/lint. + +Anti-pattern catalog: PD1..PD10. Six axes: Plan-vs-Main-HEAD, +Plan-vs-Open-PRs, Plan-vs-Recent-Merges, Plan-vs-RFC-Status, +Plan-vs-Invariants, Plan-vs-Spec. + +### §4.7 PP-14 specifics + +Owns surface-inspection only (e.g. `cargo doc`, `cargo tree`, +`cargo expand`) plus WebSearch / paper-search for cross-pollination. +Has NO compile/test gates. Anti-pattern catalog: EP1..EP8. + +--- + +## §5 Worker Iron Rules + +### §5.1 Unique-file write discipline + +Each worker writes to a **unique new file** in its bundle. Never two +workers at the same existing file simultaneously. Append-conflicts +are guaranteed if violated. + +Shared merge-zones (module registry, integration test list) MUST be +declared explicitly in INVARIANTS.md as **append-only**: each worker +appends one line; the orchestrator resolves order in the +consolidation commit. Worker pushes to undeclared shared files are +ERROR severity (§7). + +### §5.2 Worktree branches start from `origin/`, not the local working branch + +A worktree branch started from a stale local working-branch will +silently miss sibling workers' commits. Workers MUST: + +```bash +git fetch origin +git checkout -b agent/N +``` + +Implementations MUST set `isolation: "worktree"` for every worker +spawn. + +### §5.3 Atomic consolidation pass + +See §3.4. The orchestrator does ONE consolidation commit per wave, +not N mini-commits. + +### §5.4 Pre-wave helper-call audit + +Before fanning out, the orchestrator scans each bundle for +uncommitted helper-call dependencies. Bundles with non-zero +unresolved-helper count require a **helper-hoist** committed before +the fan-out. Otherwise N workers each invent the same helper N +different ways. This is PP-14's job at PRE-PLAN. + +### §5.5 Chunking discipline (the load-bearing iron rule) + +Files larger than ~150 lines MUST be written via `tee -a` in chunks, +with one commit per chunk. NEVER one 500-line heredoc + commit + +push in the same turn. Connection drops mid-serialize lose work. + +The `tee -a` loop has double duty: chunking (file size) AND agent +logging (status line appended to `AGENT_LOG.md` after each chunk). + +### §5.6 Source-quoting commit messages + +For ports and behavior-preserving rewrites: every commit message +quotes the source file + line range for the function being ported. +Reviewers can `grep -rn "Source: " /` to find every +port site. Without this, behavior-drift creeps in invisibly. + +--- + +## §6 Sprint Plan Format + +### §6.1 The plan is a DOT graph (or YAML mirror) + +A sprint plan is a DOT graph (per attractor-spec.md §2) or its +equivalent YAML. The canonical wave shape is in §1.3. + +### §6.2 Required node attributes per bundle + +Every `box`-shaped worker node MUST declare: + +``` +[ + agent_id="A4", + bundle_name="customer-master-data", + owned_files="src/customer/master.rs,src/customer/master/types.rs", + read_only_files="../WoA/woa/blueprints/customer.py,../WoA/models.py", + spec_files="vendor/ogit/NTO/WorkOrder/entities/customer.ttl", + sentinel_token="WAVE-12-A4-7f3c", + proof_of_read=true, + worker_model="sonnet", + isolation="worktree", + status_file=".claude/board/wave-12/A4-status.json" +] +``` + +### §6.3 Required edge attributes + +| Attribute | Values | Default | +|---|---|---| +| `condition` | expression over `outcome` and `context.*` | `outcome==SUCCESS` | +| `fidelity` | `full` / `compact` / `summary:medium` / `truncate` | `compact` | +| `route_on_fail` | node id | `null` | + +### §6.4 YAML mirror (when DOT is unavailable) + +```yaml +sprint: + id: SPRINT-12-customer-master + wave_size: 12 + base_branch: origin/main + workers: + - agent_id: A4 + bundle_name: customer-master-data + owned_files: [src/customer/master.rs, src/customer/master/types.rs] + read_only_files: [../WoA/woa/blueprints/customer.py, ../WoA/models.py] + spec_files: [vendor/ogit/NTO/WorkOrder/entities/customer.ttl] + sentinel_token: WAVE-12-A4-7f3c + proof_of_read: true + worker_model: sonnet + isolation: worktree + status_file: .claude/board/wave-12/A4-status.json + savants: + pre_plan: PP-14 + pre_spawn: PP-16 + during_impl: PP-15 + post_impl: PP-13 + goal_gate: PP-13 + retry_target: fan-out +``` + +--- + +## §7 Validation Rules + +Implementations MUST run these lints against every sprint plan +before fan-out. Severity matches attractor-spec.md §7's convention. + +| Rule | Description | Severity | +|---|---|---| +| `WAVE-001 unique-write` | No two bundles list the same file under `owned_files`. | ERROR | +| `WAVE-002 declared-shared` | Any file appearing in two bundles' `owned_files` MUST be declared `append-only` in INVARIANTS.md, otherwise ERROR. | ERROR | +| `WAVE-003 sentinel-token-present` | Every worker node MUST have a unique `sentinel_token`. | ERROR | +| `WAVE-004 proof-of-read-required` | `proof_of_read=true` is mandatory; `false` requires explicit human override in the commit body. | ERROR | +| `WAVE-005 goal-gate-has-retry` | If a node has `goal_gate=true`, it MUST also have a `retry_target`. (Adopted from attractor §7.2.) | ERROR | +| `WAVE-006 isolation-worktree` | Every worker node MUST declare `isolation="worktree"`. | ERROR | +| `WAVE-007 status-file-path` | Every worker node MUST declare a `status_file` path unique to this wave. | ERROR | +| `WAVE-008 worker-brief-size` | The rendered brief for any worker MUST NOT exceed `worker.max_brief_tokens` (default 8000). | WARNING | +| `WAVE-009 helper-hoist` | Pre-wave helper-call audit (§5.4) MUST surface zero unresolved-helpers. | WARNING | +| `WAVE-010 verdict-vocabulary` | Each savant verdict MUST be from its own vocabulary (§4.2). Cross-vocabulary verdicts are routed back. | ERROR | +| `WAVE-011 auto-status-false` | `auto_status` MUST be `false`. Missing status MUST be treated as FAIL. (Conflicts with attractor's default.) | ERROR | +| `WAVE-012 reachability` | Every node reachable from `start`. (Adopted from attractor §7.2.) | ERROR | +| `WAVE-013 start-exit` | Exactly one `start`, at least one `exit`. (Adopted from attractor §7.2.) | ERROR | +| `WAVE-014 inbox-drained` | At wave start, REQUESTS-FROM-AGENTS.md MUST have no unanswered entries from prior waves. | WARNING | +| `WAVE-015 invariants-fresh` | Every worker brief MUST include the SHA-256 of INVARIANTS.md and the worker MUST proof-of-read it. | ERROR | + +--- + +## §8 Configuration + +```yaml +# .claude/ATT/config/orchestrator.yaml +orchestrator: + wave_size: + default: 12 + min: 1 + max: 24 + worker: + max_brief_tokens: 8000 + isolation: worktree # MUST be worktree + proof_of_read: required # required | optional (default required) + auto_status: false # MUST be false + consolidation: + one_pr_per_bundle: false # true | false (false = one combined PR) + re_run_tier_1: true # MUST be true + savants: + pre_plan: PP-14 + pre_spawn: PP-16 + during_impl: PP-15 + post_impl: PP-13 + goal_gate_owner: PP-13 + retry: + max_replans_per_sprint: 3 + on_partial_failure: redispatch_with_tighter_scope + budget: + wave_max_input_tokens: 350000 + savant_max_input_tokens: 50000 + warn_if_exceeds: 0.8 # warn at 80 % of budget +``` + +--- + +## §9 Status File Schema + +### §9.1 Adopt attractor's status.json with `auto_status=false` + +Every worker MUST write a `status.json` to its declared `status_file` +path. Schema cribbed from attractor Appendix C and tightened: + +```json +{ + "agent_id": "A4", + "bundle_name": "customer-master-data", + "sentinel_token": "WAVE-12-A4-7f3c", + "outcome": "SUCCESS", + "preferred_label": "land", + "suggested_next_ids": [], + "context_updates": { + "customer.master.files_created": 3, + "customer.master.parity_tests_passed": 7 + }, + "notes": "two helper functions hoisted into src/customer/_shared.rs (already declared append-only in INVARIANTS.md §AppendOnly)", + "proof_of_read": [ + { "file": "INVARIANTS.md", "sha256": "...", "lines": 412, "depth": "thorough" }, + { "file": "../WoA/woa/blueprints/customer.py", "sha256": "...", "lines": 891, "depth": "full" }, + { "file": "vendor/ogit/NTO/WorkOrder/entities/customer.ttl", "sha256": "...", "lines": 137, "depth": "full" } + ], + "tier_1_gates": { + "lint": "GREEN", + "fmt": "GREEN", + "audit": "GREEN", + "deny": "GREEN", + "typecheck": "GREEN", + "tests": { "passed": 14, "failed": 0 } + } +} +``` + +### §9.2 The five outcomes + +| Outcome | Meaning | +|---|---| +| `SUCCESS` | All Tier-1 gates green; spec contract satisfied; ready for PR. | +| `PARTIAL_SUCCESS` | Bundle work shipped but a defined sub-task deferred (filed to `Altlasten.md` / `TECH_DEBT.md`). | +| `RETRY` | Self-detected stuck-loop (per `anti-skim-agent-spec.md` §6) or token-budget exhaustion. Orchestrator re-spawns with tighter scope. | +| `FAIL` | Tier-1 gates red, OR proof-of-read missing, OR sentinel-token failed, OR Iron Rule violated. | +| `SKIPPED` | The bundle's preconditions (e.g. a dependency bundle) were not met. | + +### §9.3 Missing-status policy + +`auto_status=false` is mandatory. If a worker exits without writing +`status_file`, the orchestrator MUST treat the wave as FAIL for that +bundle. + +This is where we deliberately conflict with attractor's +`auto_status=true` default. See [`README.md` "Where it conflicts"](./README.md#where-it-conflicts-with-attractors-posture-and-why-we-keep-our-position). + +--- + +## §10 Definition of Done (conformance checklist) + +An implementation is conformant if it satisfies ALL of: + +- [ ] Sprint plans are DOT graphs or §6.4-YAML mirrors with all + required node attributes (§6.2) and edge attributes (§6.3). +- [ ] Validation rules §7 WAVE-001 through WAVE-015 run as part of + `preflight-drift-auditor` and block fan-out on ERROR. +- [ ] Every worker spawns with `isolation: "worktree"` (§5.2). +- [ ] Every worker writes a `status.json` matching the §9.1 schema. +- [ ] Missing `status.json` is treated as FAIL (`auto_status=false`). +- [ ] The four savants are present with non-overlapping verdict + vocabularies (§4.2) and explicit non-use route-tables (§4.3). +- [ ] PP-13 owns the language-specific Tier-1 toolchain + (`anti-skim-agent-spec.md` §8). +- [ ] PP-15 owns BAP1..BAP10 + 8 boundary classes (§4.5). +- [ ] PP-16 owns PD1..PD10 + 6 axes (§4.6) plus the §7 validation rules. +- [ ] Worker briefs declare `proof_of_read: true` and a unique + `sentinel_token` (§6.2). +- [ ] The meta-agent (the 13th role) drains REQUESTS-FROM-AGENTS.md + ≥ 2× per day during a wave (4-hour reply latency target). +- [ ] PR review classifies findings ONLY as P0 or P1; never P2/P3. +- [ ] `INVARIANTS.md` is ≤ 500 lines; split required above. +- [ ] One consolidation commit per wave at the shared registry + (§3.4); zero N-mini-commit anti-pattern detected. +- [ ] Chunking discipline enforced: files > 150 lines written via + `tee -a` (§5.5). +- [ ] Context Fidelity ladder (§11A) is implemented with precedence + edge > node > graph > default and the §11A.2 token budgets + treated as guidance (hard cap is `worker.max_brief_tokens`). +- [ ] `fidelity=truncate` does NOT exempt a worker from the + `anti-skim-agent-spec.md` §3.3 reading-depth ladder. + +--- + +## §11 Cross-Language Parity Matrix + +| Aspect | Rust | Python | TypeScript | Go | +|---|---|---|---|---| +| Tier-1 lint | `cargo clippy --all-targets --all-features -- -D warnings` | `ruff check` | `eslint --max-warnings 0` | `golangci-lint run` | +| Tier-1 format check | `cargo fmt --check` | `ruff format --check` | `prettier --check` | `gofmt -l` | +| Tier-1 advisory scan | `cargo audit` | `pip-audit` | `npm audit --omit=dev` | `govulncheck ./...` | +| Tier-1 dep policy | `cargo deny check` | `deptry .` | (project-defined) | `go vet -all` | +| Tier-1 typecheck | (clippy implies) | `mypy --strict` | `tsc --noEmit --strict` | (compiler implies) | +| Tier-1 tests | `cargo test --all-features` | `pytest` | `vitest run` | `go test ./...` | +| Module registry append-only file | `src/lib.rs` / `mod.rs` | `__init__.py` | `index.ts` / `index.js` | `pkg.go` | +| Workspace metadata | `cargo metadata` | `pipdeptree` | `pnpm list --depth=2` | `go list -m all` | +| Public-API diff | `cargo public-api` | `griffe check` | `api-extractor run` | `apidiff` | + +--- + +## §11A Context Fidelity + +> **Adopted from `attractor-spec.md` §5.4.** This is the one pickup +> that requires its own section because it changes how briefs are +> rendered to workers. + +### §11A.1 Why fidelity matters + +A 12-worker wave runs the orchestrator's brief through each worker. +If every worker inherits the full orchestrator context, a wave's +input-token cost scales linearly with workers × shared-context-size, +which is wasteful when bundles are small and well-scoped. + +Fidelity modes let the orchestrator dial how much of its context +each worker inherits, per-edge / per-node / per-graph. + +### §11A.2 Four modes + token budgets + +| Mode | Token budget (approx.) | When to use | +|---|---|---| +| `full` | unbounded (worker inherits the entire orchestrator thread) | trusted same-domain handoff; rare; default OFF | +| `compact` | ~3000 tokens (bundle ownership table + INVARIANTS sha + parity contract only) | **default** for standard workers | +| `summary:medium` | ~1500 tokens (auto-generated summary of orchestrator context, ~15-line max) | budget-conscious waves; > 12 workers | +| `summary:low` | ~600 tokens (one-paragraph summary) | trivial workers (e.g. doc-touch only) | +| `truncate` | ~200 tokens (goal + bundle name + sentinel only) | smoke-test workers; never for code-writing | + +These budgets are guidance, not hard caps; the hard cap is +`worker.max_brief_tokens` (default 8000, configurable per §8). + +### §11A.3 Precedence ladder + +Per attractor §5.4, fidelity is resolved with edge > node > graph > +default: + +``` +worker.fidelity = + edge_attribute(orchestrator → worker, "fidelity") # if set + || node_attribute(worker, "fidelity") # else + || graph_attribute(sprint_graph, "default_fidelity") # else + || "compact" # built-in default +``` + +### §11A.4 Interaction with proof-of-read + +Fidelity controls what the **orchestrator** passes to the worker. It +does NOT control what the **worker** reads from disk. A worker on +`fidelity=truncate` MUST still execute the §3.3 of +[`anti-skim-agent-spec.md`](./anti-skim-agent-spec.md) reading-depth +ladder on every file it touches. + +In particular: + +- A worker on `fidelity=truncate` whose brief includes only "goal + + bundle name + sentinel" MUST still read INVARIANTS.md `thorough` + before any commit (per anti-skim-agent-spec.md §3.3). +- A worker on `fidelity=full` does NOT skip proof-of-read on files + that were summarized in the inherited context — the inherited + summary is NOT a substitute for the read (the same rule that makes + a compaction summary not a substitute for the JOURNAL file). + +### §11A.5 Validation rule + +| Rule | Description | Severity | +|---|---|---| +| `WAVE-016 fidelity-budget` | If a worker node declares `fidelity=truncate` AND `tier_1_gates` are required, the orchestrator MUST verify the worker's brief still includes the §6.2 required attributes. | ERROR | +| `WAVE-017 fidelity-precedence` | The §11.3 precedence is observed; conflicting declarations resolve in the order edge > node > graph > default. | WARNING | + +### §11A.6 Worked example + +A wave-12 sprint with mixed bundle sizes: + +```yaml +sprint: + default_fidelity: compact + workers: + - agent_id: A1 # large bundle, cross-cutting + fidelity: full # node override + - agent_id: A2 # standard bundle + # uses default 'compact' + - agent_id: A3 # tiny bundle, only touches docs + fidelity: summary:low # node override + - agent_id: A4 # smoke-test only + fidelity: truncate + edges: + - from: A1 + to: fan_in + fidelity: full # edge override — A1's result fed to fan-in at full fidelity +``` + +--- + +## §12 Appendix A — Canonical wave DOT graph + +```dot +digraph wave_12_customer_master { + rankdir=LR; + start [shape=Mdiamond, label="start"]; + exit [shape=Msquare, label="exit"]; + + // PRE-SPAWN + pp16 [shape=box, + label="PP-16 preflight-drift-auditor", + owner="meta-agent", + verdicts="SPAWN-CLEAR / SPAWN-CAUTION / SPAWN-BLOCKED", + validation_rules="WAVE-001..WAVE-015"]; + + // DURING-IMPL fan-out + fan_out [shape=component, label="fan-out N=12", max_parallel=12]; + + // 12 workers (only A1..A3 shown for brevity) + a1 [shape=box, agent_id="A1", bundle_name="customer-list", + isolation="worktree", proof_of_read=true, + sentinel_token="WAVE-12-A1-2a91", + status_file=".claude/board/wave-12/A1-status.json"]; + a2 [shape=box, agent_id="A2", bundle_name="customer-detail", + isolation="worktree", proof_of_read=true, + sentinel_token="WAVE-12-A2-6b04", + status_file=".claude/board/wave-12/A2-status.json"]; + a3 [shape=box, agent_id="A3", bundle_name="customer-master", + isolation="worktree", proof_of_read=true, + sentinel_token="WAVE-12-A3-d177", + status_file=".claude/board/wave-12/A3-status.json"]; + // ... A4..A12 ... + + fan_in [shape=tripleoctagon, label="consolidate + re-run tier-1"]; + + pp15 [shape=box, label="PP-15 baton-handoff-auditor", + verdicts="CATCH-CRITICAL / CATCH-LATENT / CLEAN"]; + + pp13 [shape=box, label="PP-13 brutally-honest-tester", + verdicts="LAND / HOLD / REJECT", + goal_gate=true, + retry_target=fan_out]; + + start -> pp16 [label="plan handed off"]; + pp16 -> fan_out [label="outcome==SPAWN-CLEAR"]; + pp16 -> exit [label="outcome==SPAWN-BLOCKED", + condition="route_on_fail"]; + fan_out -> a1; + fan_out -> a2; + fan_out -> a3; + // ... -> a4..a12 + a1 -> fan_in; + a2 -> fan_in; + a3 -> fan_in; + fan_in -> pp15 [label="all status.json present, all proof_of_read verified"]; + pp15 -> pp13 [label="outcome==CLEAN || outcome==CATCH-LATENT"]; + pp15 -> fan_out [label="outcome==CATCH-CRITICAL", condition="route_on_fail"]; + pp13 -> exit [label="outcome==LAND"]; + pp13 -> fan_out [label="outcome==HOLD || outcome==REJECT", + condition="route_on_fail"]; +} +``` + +--- + +## §13 Appendix B — Stuck-Protocol typed blockers + +When a worker cannot proceed it writes ONE entry to +`META/REQUESTS-FROM-AGENTS.md` and idles. Blocker types: + +| Type | Meaning | Meta-agent action | +|---|---|---| +| `AMBIGUITY` | Spec is mehrdeutig; more than one sensible interpretation. | Write answer in `ANSWERS-TO-AGENTS.md`; propagate to INVARIANTS if structural. | +| `MISSING_INVARIANT` | Iron Rule doesn't cover this case; convention missing. | Add invariant to INVARIANTS.md; audit other workers for the same gap. | +| `SPEC_SOURCE_MISMATCH` | Spec says X, reference source does Y. | Write RFC under `rfcs/`; get human sign-off; then tell worker to proceed. | +| `BEHAVIOUR_QUESTION` | Possible bug in reference source. | If meta can read source and answer definitively, do so; else page human. | +| `EXTERNAL_DEPENDENCY` | Third-party system zickt; workaround unclear. | Check `wissen/` / `knowledge/`; if none, RFC + add note. | + +`OUT-OF-SCOPE` requests (worker wants to refactor outside bundle, add +new feature) are rejected: meta writes `REJECTED: ` and +closes. + +--- + +## §14 Two-Protocol Operating Modes + +> Added 2026-05-18 — design work and implementation work have +> different gates and benefit from different topologies. + +### §14.1 Two protocols, one loop family + +| Protocol | Phase | Artifact produced | Goal-gate criterion | +|---|---|---|---| +| **Protocol B** | Design | Specs, RFCs, plans, INVARIANT amendments, agent cards | Doc-coherence + scope-completeness across N docs jointly | +| **Protocol A** | Implementation | Code, parity tests, ledger rows, PRs | Tier-1 toolchain green + spec contract satisfied | + +Both protocols use the same 4-savant council (PP-13/14/15/16) and +the same meta-agent. They differ in (1) what counts as a goal-gate +pass, (2) the topology of the savant pass (sequential vs cooperative +council per §15), and (3) the artifact the preflight produces. + +### §14.2 Protocol B — Design loop + +``` +1. Draft → human or LLM authors N documents on a design branch +2. Joint review → savants COOPERATE on the N docs as a coherent set (§15) +3. Correct → orchestrator applies cross-doc fixes from the joint review +4. Re-review → goto step 2 until CONVERGE (§15.5) or human override +5. Land → docs merge to main; Protocol A may begin +``` + +Goal-gate criteria (Protocol B): + +| Check | Owner | Pass condition | +|---|---|---| +| Doc-coherence | PP-15 baton-handoff-auditor | No cross-doc schema/term drift (e.g. `status.json` in spec A matches consumer in spec B) | +| Scope-completeness | PP-16 preflight-drift-auditor | Every requirement in the source brief is addressed in at least one doc | +| Convergence opportunity | PP-14 convergence-architect | No latent shared concept duplicated across docs without a shared section | +| Conformance checklist | PP-13 brutally-honest-tester | Every doc has a Definition-of-Done section with checkable items | + +PP-13 in Protocol B does NOT run the language toolchain (no code to +compile). It validates that the docs ARE the kind of thing a PP-13 +can later compile/test against. + +### §14.3 Protocol A — Implementation loop + +The 6-step wave loop of §3.1 with one addition: **preflight produces +a commented-out, compile-error-only Rust skeleton** (§14.4) before +worker fan-out. Workers fill bodies into the skeleton instead of +writing into empty files. + +``` +1. Plan → orchestrator partitions work into N bundles +2. Preflight → PP-16 produces SKELETON (§14.4) + verdict +2a. Council → savants COOPERATE on SKELETON (§15) — fast, cheap +3. Sprint → N parallel worker agents fill bodies into skeleton +4. Council → savants COOPERATE on FILLED code (or sequential per §15.6 for big diffs) +5. Fix P0s → orchestrator applies fixes + verifies tier-1 gates +6. Commit+PR → orchestrator (atomic consolidation per §3.4) +7. Repeat → orchestrator plans the next wave +``` + +### §14.4 The commented-out Rust skeleton (Protocol A preflight artifact) + +PP-16 in Protocol A produces both (a) its verdict (SPAWN-CLEAR / +CAUTION / BLOCKED per §4.6) AND (b) a runnable-but-stubbed Rust +skeleton at the path declared in `skeleton_output_path` (a new node +attribute on each worker, see §14.6). + +#### §14.4.1 Canonical Rust skeleton format + +| Element | Skeleton form | +|---|---| +| Function body | `todo!("SOURCE: :-")` | +| Trait impl | declare the impl block with every method body as `todo!("SOURCE: ...")` | +| Struct | declare all fields; `Default::default()` impl as `todo!("SOURCE: ...")` if construction is non-trivial | +| Enum | declare variants; no body needed | +| Module | `pub mod x;` with stub `mod.rs` containing `// SKELETON: — SOURCE: ` | +| `unsafe` block | `unsafe { todo!("UNSAFE-SOURCE: : — // SAFETY: ") }` — SAFETY rationale required because PP-13 will demand it later | +| Async function | same as function; body `todo!("SOURCE: ...")` | +| Macro | declare with empty body `() => { todo!("SOURCE: ...") }` | + +#### §14.4.2 Token choice: `todo!` over `unimplemented!` + +- `todo!("SOURCE: ...")` — preferred. Signals intent "this is to be + done", panics with source-location at runtime if hit, compiler + accepts it as any type. +- `unimplemented!()` — reserved for "correct API surface but never + implemented in this crate" (rare; not a skeleton placeholder). +- `unreachable!()` — reserved for "statically impossible branch"; + not a skeleton primitive. + +#### §14.4.3 Skeleton compile invariant + +The skeleton MUST compile with `cargo check --workspace` green. It +MUST NOT pass `cargo test` or `cargo run` (the `todo!()` panics). +This is the diagnostic signature of a well-formed skeleton. + +| Rule | Description | Severity | +|---|---|---| +| `WAVE-018 skeleton-compiles` | `cargo check --workspace` MUST be green on the skeleton emitted by PP-16. | ERROR | +| `WAVE-019 skeleton-source-annotated` | Every `todo!()` MUST carry a `"SOURCE: :"` or `"UNSAFE-SOURCE: ..."` argument. | ERROR | +| `WAVE-020 skeleton-no-runtime` | The skeleton MUST NOT pass `cargo test` or `cargo run` — a green test on a skeleton means the test is a tautology. | ERROR | + +#### §14.4.4 Per-language equivalents (for non-Rust repos) + +| Language | Skeleton form | +|---|---| +| Rust | `todo!("SOURCE: ...")` (canonical) | +| Python | `raise NotImplementedError("SOURCE: :")` with docstring stub | +| TypeScript | `throw new Error("TODO — SOURCE: :")` | +| Go | `panic("TODO — SOURCE: :")` | + +Rust is canonical because the language's `todo!()` macro is +purpose-built for this exact use case. + +### §14.5 Protocol A vs Protocol B at the savant layer + +| Savant | Protocol B (design) | Protocol A (implementation) | +|---|---|---| +| PP-13 | Verifies docs have DoD sections; no compile gate. | Tier-1 toolchain + AP1..AP9 on filled code (§4.4). | +| PP-14 | Surfaces convergence opportunities across docs. | Surfaces convergence opportunities across slices (helper-hoist). | +| PP-15 | Cross-doc schema/term drift. | Cross-bundle DTO drift on filled code (BAP1..BAP10, §4.5). | +| PP-16 | Validates plan-vs-source brief: every requirement landed in some doc. | Validates plan-vs-main (PD1..PD10, §4.6) + EMITS SKELETON (§14.4). | + +### §14.6 Mode switch is explicit + +The orchestrator declares which protocol is active in the sprint +header: + +```yaml +sprint: + id: SPRINT-17-attractor-fold-in + protocol: B + default_fidelity: full + inputs: + docs: + - .claude/ATT/autoattended-orchestrator-spec.md + - .claude/ATT/anti-skim-agent-spec.md + - .claude/ATT/agent-coordination-mcp-spec.md + goal_gate: savant_council_converge +``` + +```yaml +sprint: + id: SPRINT-18-attractor-implementation + protocol: A + default_fidelity: compact + preflight: + skeleton_output_root: src/attractor/ + skeleton_compiles_check: cargo check --workspace + workers: + - agent_id: A1 + bundle_name: orchestrator-runner + skeleton_output_path: src/attractor/orchestrator/mod.rs + ... +``` + +| Rule | Description | Severity | +|---|---|---| +| `WAVE-021 protocol-declared` | Every sprint header MUST declare `protocol: A` or `protocol: B`. | ERROR | +| `WAVE-022 protocol-coherence` | Protocol-B sprints MUST NOT declare `workers[].owned_files` of file extensions other than `.md` / `.yaml` / `.dot`. Protocol-A sprints MUST declare `preflight.skeleton_output_root`. | ERROR | + +--- + +## §15 Cooperative Savant Council + +> The 4 savants **cooperate** — they do not run as independent +> fan-out branches whose verdicts get bag-of-words-aggregated. They +> read a shared scratchpad, file findings, cross-refer to each other, +> iterate, and emerge with a SINGLE joint verdict. + +### §15.1 Why cooperative, not independent-parallel + +Independent-parallel review (4 savants → 4 verdicts → super-verdict +aggregation) has two failure modes: + +1. **Lockstep drift.** All 4 savants miss the same cross-cutting + issue because they each looked at the artifact through their own + axis without seeing the others' angle. +2. **Duplicate findings.** Each savant independently flags the same + issue from a different angle; the orchestrator wastes a round + deduplicating. + +Cooperation fixes both: savants see each other's findings as they +land and either (a) cross-refer ("PP-15 already flagged this; my +angle is …") or (b) suppress duplicates ("PP-13 already covered +this from a stronger angle — withdrawn"). + +### §15.2 The shared scratchpad + +``` +.claude/board/savant-council-/ +├── ROUND-0-ARTIFACT.md (snapshot of what's under review) +├── ROUND-1-PP-13.md (PP-13's round-1 findings) +├── ROUND-1-PP-14.md (PP-14's round-1 findings) +├── ROUND-1-PP-15.md (PP-15's round-1 findings) +├── ROUND-1-PP-16.md (PP-16's round-1 findings) +├── ROUND-2-PP-13.md (PP-13's round-2 — sees all of round 1) +├── ... +└── COUNCIL-VERDICT.md (final joint document) +``` + +Append-only per §7.1 of `agent-coordination-mcp-spec.md`. Each +savant writes its file at the end of its round; reads ALL other +files at the start of its next round. + +### §15.3 The cooperation loop + +``` +Round R+1 (per savant): + 1. Read every ROUND-R-*.md (the others' findings from the previous round) + 2. Read ROUND-R-.md (your own findings from the previous round) + 3. For each finding by another savant: + a. Cross-refer if it intersects your axis ("noted — my AP3 finding + relates: ") + b. Withdraw any of your own findings that the other savant covered + from a stronger angle + c. Suppress duplicates with `STATUS: superseded-by(PP-X round-R)` + 4. Append your new findings (axis-tagged) to ROUND-(R+1)-.md + 5. Mark with `ROUND-COMPLETE` sentinel when you have no new findings +``` + +A round ends when ALL 4 savants have either (a) added new findings +in this round OR (b) emitted `ROUND-COMPLETE` (no new findings + no +unaddressed cross-refers from peers). + +### §15.4 Convergence + termination + +| Condition | Outcome | +|---|---| +| All 4 savants emit `ROUND-COMPLETE` in the same round | **CONVERGE** — proceed to §15.5 verdict synthesis | +| Round-count exceeds `max_rounds` (default 3) | **STALL** — orchestrator or human arbitrates | +| Any savant emits an unrecoverable BLOCK in any round (e.g. PP-16=SPAWN-BLOCKED with no clear remediation) | **BLOCK** — abort the council; return to step 1 of the protocol | + +### §15.5 The joint verdict (`COUNCIL-VERDICT.md`) + +After CONVERGE, the meta-agent (or a designated chairman savant — +default PP-16 in Protocol A, PP-15 in Protocol B) reads ALL +ROUND-*.md files and synthesizes a single `COUNCIL-VERDICT.md`: + +```yaml +council_verdict: + topic: + protocol: A | B + rounds_run: N + super_verdict: CONVERGE | SPLIT | BLOCK + + per_savant_final: + PP-13: + verdict: LAND | HOLD | REJECT + findings_kept: N + findings_withdrawn: M + PP-14: { ... } + PP-15: { ... } + PP-16: { ... } + + consolidated_p0_findings: + - finding_id: F1 + raised_by: PP-13 (round 1) + cross_referred_by: [PP-15 round 1, PP-16 round 2] + summary: + remediation: + + consolidated_p1_findings: [ ... ] + + next_action: proceed | arbitrate | retry_preflight | block +``` + +The super-verdict is derived from the per-savant finals: + +| Super-verdict | Trigger | +|---|---| +| **CONVERGE** | All 4 savants pass (PP-13=LAND ∧ PP-14∈{OPPORTUNITY-NOW,WORTH-EXPLORING,DROP} ∧ PP-15=CLEAN ∧ PP-16=SPAWN-CLEAR), AND no P0 findings remain after deduplication. | +| **SPLIT** | Mixed verdicts that are neither all-pass nor block-on-any. Orchestrator or human arbitrates. | +| **BLOCK** | Any of: PP-13=REJECT ∨ PP-15=CATCH-CRITICAL ∨ PP-16=SPAWN-BLOCKED ∨ remaining P0 findings ≥ 1. | + +### §15.6 Council DOT topology + +```dot +digraph savant_council { + rankdir=LR; + start [shape=Mdiamond]; + artifact [shape=note, label="docs (B) OR skeleton (A) OR filled-code (A)"]; + + round_R [shape=component, + label="round R: 4 savants read shared scratchpad, + append findings, cross-refer, withdraw duplicates"]; + + pp13 [shape=box]; pp14 [shape=box]; pp15 [shape=box]; pp16 [shape=box]; + + round_check [shape=diamond, + label="all 4 savants ROUND-COMPLETE?"]; + bump_round [shape=parallelogram, label="R := R+1"]; + + chairman [shape=box, label="chairman savant synthesizes COUNCIL-VERDICT.md"]; + verdict [shape=tripleoctagon, label="super-verdict: CONVERGE / SPLIT / BLOCK"]; + exit [shape=Msquare]; + + start -> artifact; + artifact -> round_R; + round_R -> pp13; + round_R -> pp14; + round_R -> pp15; + round_R -> pp16; + pp13 -> round_check; + pp14 -> round_check; + pp15 -> round_check; + pp16 -> round_check; + round_check -> bump_round [label="no: a savant has new findings"]; + bump_round -> round_R; + round_check -> chairman [label="yes: CONVERGE / STALL / BLOCK"]; + chairman -> verdict; + verdict -> exit; +} +``` + +### §15.7 When to fall back to sequential (no council) + +Three rules of thumb: + +1. **Filled-code review with diff > 500 lines:** sequential PP-13 → + PP-15 → PP-16 (per §4 chain). Each savant sees a smaller diff + after the orchestrator applies the previous savant's P0 fixes. +2. **Token-budget constrained run:** sequential. Council pays for + `4 × rounds × artifact-size` reads; sequential pays for + `1 × artifact + 3 × diff-only` reads. +3. **First time using a new spec set:** sequential. Let one savant + shake out the obvious issues before the others read it. + +Otherwise: council by default. + +### §15.8 Configuration + +```yaml +council: + max_rounds: 3 + chairman: + protocol_A: PP-16 + protocol_B: PP-15 + scratchpad_root: .claude/board/savant-council + cooperation_protocol: cross-refer-and-withdraw # see §15.3 + duplicate_suppression: superseded-by-stronger-angle +``` + +### §15.9 Validation rules + +| Rule | Description | Severity | +|---|---|---| +| `WAVE-023 council-legality` | Council mode is declared only when artifact is one of {docs, skeleton, small-diff filled-code}. | ERROR | +| `WAVE-024 council-rounds-bounded` | `max_rounds` MUST be set; default 3; hard cap 7. | ERROR | +| `WAVE-025 scratchpad-append-only` | The `.claude/board/savant-council-/` directory MUST be append-only per `agent-coordination-mcp-spec.md` §7.1. | ERROR | +| `WAVE-026 cross-refer-recorded` | When a savant withdraws or supersedes a peer's finding, the action MUST be recorded with `STATUS: superseded-by(PP-X round-R)` for traceability. | ERROR | +| `WAVE-027 chairman-declared` | The `chairman` savant for the synthesis pass MUST be declared in the sprint config; default per §15.8. | ERROR | +| `WAVE-028 stall-arbitration` | A STALL outcome (max_rounds reached without CONVERGE) MUST be followed by an arbitration step (orchestrator or human). | ERROR | +| `WAVE-029 split-arbitration` | A SPLIT super-verdict MUST be followed by an arbitration step before fan-out resumes. | ERROR | + +### §15.10 Example: this very kit + +A worked example. To fold the §14 / §15 additions into the three +`.claude/ATT/*.md` specs, a Protocol B council would: + +``` +Round 1: + PP-13: "spec A §11 'Context Fidelity' lacks a DoD subsection" + PP-14: "spec A §15 'cooperative council' duplicates spec C §3.1 'three-layer model' + — opportunity for shared concept" + PP-15: "spec A §14.4 'skeleton' references todo!() macro; + spec B §6 'tool-call loop detection' does not — no contradiction, just an axis" + PP-16: "scope-completeness pass — every requirement from the source brief + (Protocol A/B + cooperation + Rust skeleton) landed in spec A; + spec B + spec C do not need changes" + +Round 2: + PP-13: ROUND-COMPLETE (no new findings; PP-14's opportunity is non-blocking) + PP-14: "spec A §15 cooperation should reference spec C §3.2 'file blackboard' + since the scratchpad uses that primitive — cross-ref added" + PP-15: "withdrawn: my round-1 finding is covered by PP-14 round-2's cross-ref" + PP-16: ROUND-COMPLETE + +Round 3: + All 4: ROUND-COMPLETE + +CONVERGE — chairman PP-15 synthesizes COUNCIL-VERDICT.md +super_verdict: CONVERGE +consolidated_p0_findings: [] +consolidated_p1_findings: + - F1: "spec A §11 add 'Definition of Done' subsection (PP-13 round 1)" + - F2: "spec A §15 cross-reference spec C §3.2 (PP-14 round 2, PP-15 round 2)" +next_action: proceed +``` + +--- + +## §16 Model Stylesheet + Mid-Task Switching + +> Added 2026-05-18. Maps to `attractor-spec.md` §8 `model_stylesheet` +> (CSS-like selectors), adds our mid-task Opus↔Sonnet switching +> triggered by our stuck-protocol blockers and council-round counts. + +### §16.1 Why have a stylesheet at all + +| Phase / role | Cognitive load | Reasonable default | +|---|---|---| +| Plan authoring | high (architecture, cross-cutting) | **opus** + `reasoning=high` | +| Plan review (meta-agent) | high (judgment, taste) | **opus** + `reasoning=high` | +| PP-14 convergence-architect | high (creative-design exploration) | **opus** + `reasoning=high` | +| PP-16 preflight-drift-auditor | medium (git + grep against plan) | **sonnet** (escalate on drift > 100 lines) | +| PP-15 baton-handoff-auditor | medium (interface diffing) | **sonnet** | +| PP-13 brutally-honest-tester | medium (toolchain runner + AP1..AP9) | **sonnet** | +| Worker (skeleton-fill) | medium (mechanical fill of typed surface) | **sonnet** | +| Worker (greenfield code) | high (design + impl) | **opus** | +| Council round (per savant) | medium (review iteration) | **sonnet** | +| Chairman synthesis | high (cross-finding consolidation) | **opus** | +| Inbox drain (meta-agent) | low (typed-blocker routing) | **sonnet** (escalate on `BEHAVIOUR_QUESTION`) | + +Static per-role defaults are step 1. Mid-task switching (§16.3) is +step 2. + +### §16.2 Stylesheet syntax (attractor §8 compatible) + +```yaml +model_stylesheet: + # CSS-like rules. First matching rule wins (per attractor §8). + rules: + - selector: "node[kind=plan]" + props: { model: opus, reasoning_effort: high } + - selector: "node[kind=meta_review]" + props: { model: opus, reasoning_effort: high } + - selector: "node[kind=savant][role=PP-14]" + props: { model: opus, reasoning_effort: high } + - selector: "node[kind=savant][phase=POST-IMPL]" + props: { model: sonnet } + - selector: "node[kind=savant][phase=PRE-SPAWN]" + props: { model: sonnet } + - selector: "node[kind=council_chairman]" + props: { model: opus } + - selector: "node[kind=worker][mode=skeleton-fill]" + props: { model: sonnet } + - selector: "node[kind=worker][mode=greenfield]" + props: { model: opus } + - selector: "*" + props: { model: sonnet } # final fallback +``` + +Selectors compose with `[attr=value]` pairs (attractor §8.2). The +fallback `*` rule is mandatory; absence is `WAVE-030` ERROR. + +### §16.3 Mid-task escalation triggers + +A worker (or savant) does NOT switch its own model — it writes a +typed signal to its `status.json` and the **orchestrator re-spawns** +with the requested model. Self-switching would break LD-1 sentinel +continuity. + +| Trigger condition | Action | Escalate from → to | +|---|---|---| +| `outcome=RETRY` AND `notes contains "escalate-model:opus"` | re-spawn same agent_id with opus | sonnet → opus | +| Tool-call loop detected in council, `rounds >= 3` without CONVERGE | re-spawn the looping savant with opus + `reasoning=high` | sonnet → opus | +| Worker filed `MISSING_INVARIANT` blocker AND meta-agent answer is structural | re-spawn worker with opus | sonnet → opus | +| Worker filed `SPEC_SOURCE_MISMATCH` AND meta wrote an RFC | re-spawn worker with opus after RFC merge | sonnet → opus | +| Proof-of-read covers > 10 files | escalate at next re-spawn | sonnet → opus | +| `unsafe` block in skeleton-fill | escalate at next re-spawn | sonnet → opus | +| PP-16 drift surface > 100 lines | escalate the next preflight pass | sonnet → opus | +| PP-13 anti-pattern density > 3 per 100 lines of diff | escalate PP-13 for re-review pass | sonnet → opus | + +### §16.4 Mid-task de-escalation triggers + +The opposite direction — orchestrator can drop a re-spawn down from +opus to sonnet when the work turned out simpler than the stylesheet +default predicted: + +| Trigger condition | Action | De-escalate from → to | +|---|---|---| +| Plan turned out to be a 2-bundle wave (not 12) | next round's worker default | opus → sonnet | +| Council CONVERGED in round 1 with zero P0 + ≤2 P1 | chairman synthesis | opus → sonnet | +| `outcome=SUCCESS` + tier-1 green + diff < 50 lines | next-wave default for this bundle | opus → sonnet | +| Meta-agent's inbox-drain answered only `AMBIGUITY` blockers in last 10 entries | next inbox drain | opus → sonnet | + +### §16.5 Wire format for switching + +The signal flows on the universal wire format +(`agent-coordination-mcp-spec.md §1.3`): + +```markdown +## 2026-05-18T14:22 — PROPOSAL[P1]: escalate-model A4 → opus (sonnet, claude/wave-12) + +**Author:** A4 +**Kind:** PROPOSAL +**Severity:** P1 +**Refs:** status.json#A4 wave-12 +**Reason:** tool-call-loop:length=2:rounds=3 on customer.master sea-orm UPSERT + +--- + +Requested: model=opus, reasoning_effort=high. +Suggested rationale: this is a stuck-protocol AMBIGUITY-class issue +(sea-orm vs Python idempotent UPSERT semantics) — judgment-heavy. +``` + +Orchestrator routes by `Kind=PROPOSAL` + `Severity=P1` + body +contains `escalate-model:`. Re-spawn happens in the next wave step. + +### §16.6 Cost-aware ceiling + +The stylesheet MUST declare a per-wave **escalation budget**: + +```yaml +model_stylesheet: + budget: + max_opus_workers_per_wave: 3 # default 3 + max_opus_savant_rounds_per_council: 2 # default 2 + warn_if_exceeds: 0.8 +``` + +If a wave's pending escalations would exceed the budget, the +orchestrator MUST either (a) serialize escalations across waves or +(b) ask the human via `wait.human` (attractor §6) before +escalating. + +### §16.7 Validation rules + +| Rule | Description | Severity | +|---|---|---| +| `WAVE-030 stylesheet-fallback` | `model_stylesheet.rules` MUST end with a `*` fallback. | ERROR | +| `WAVE-031 no-self-switching` | Workers / savants MUST NOT switch their own model mid-call. Use the `PROPOSAL`-wire signal (§16.5). | ERROR | +| `WAVE-032 sentinel-on-respawn` | Re-spawn under escalation MUST issue a NEW sentinel token (the previous worker's identity is closed). | ERROR | +| `WAVE-033 escalation-budget` | The wave's escalation total MUST NOT exceed `model_stylesheet.budget` without explicit human override. | WARNING | +| `WAVE-034 escalation-trace` | Every escalation re-spawn MUST leave a `PROPOSAL` entry on the wire format (§16.5) so the audit log shows when + why. | ERROR | +| `WAVE-035 de-escalation-gate` | De-escalation MUST be triggered only by §16.4 conditions; arbitrary downgrades are forbidden (cost-saving optimism). | ERROR | + +### §16.8 Worked example: a wave that escalates then de-escalates + +``` +T+0 plan committed opus (per §16.1 default for plan) +T+1 PP-16 preflight runs sonnet (drift < 100 lines, default) +T+1 PP-16 emits skeleton sonnet (still PP-16) +T+2 council round 1 sonnet x4 (all savants default) +T+2 council CONVERGE opus (chairman synthesis default) +T+3 workers spawn (12 × A1..A12) sonnet (skeleton-fill default) +T+4 A4 hits sea-orm AMBIGUITY → PROPOSAL: escalate A4 +T+4 meta-agent writes RFC opus (meta default) +T+5 A4 re-spawn opus (escalated, new sentinel) +T+6 A4 returns SUCCESS — (kept opus through this wave) +T+7 fan-in + tier-1 green — +T+8 next wave plans A4 again opus (sticky escalation for one wave) +T+9 A4 returns SUCCESS again — +T+10 wave 3 plans A4 sonnet (DE-ESCALATE per §16.4: 2 successes + diff < 50) +``` + +--- + +*End of `autoattended-orchestrator-spec.md`.* diff --git a/.claude/EN/CLAUDE-AGENT-PATTERN.md b/.claude/EN/CLAUDE-AGENT-PATTERN.md new file mode 100644 index 0000000000..978ba1dd8b --- /dev/null +++ b/.claude/EN/CLAUDE-AGENT-PATTERN.md @@ -0,0 +1,121 @@ +# Agent Pattern Cheat-Sheet (project-agnostic, English canonical) + +Lookup-style add-on to your repo's `.claude/CLAUDE.md`. Read this before +prompting any multi-agent work. Companion knowledge files under +`.claude/EN/knowledge/` and `.claude/EN/agents/`. + +> Source: distilled from `AdaWorldAPI/WoA` `.claude/CLAUDE.md` §3.5 + §3.6 +> as of 2026-05-17. Reference implementations: `WoA` (Python), `woa-rs` +> (Rust), `lance-graph` (Rust, most mature), `ndarray` (Rust, compact). + +--- + +## 1. Compressed baseline + +``` +12 workers + 1 coordinator · autoattended · full authorization · auto-resolve · log all issues +plan/preflight → review → correct → sprint → review code → fix P0 → commit → repeat +``` + +## 2. Agent ensemble — Function · Activation · Card + +| Function | Activation | Card | +|---|---|---| +| Worker-Impl | Sprint Phase 3 spawn out of `SPRINT-N-PLAN.md`-bundle row | `.claude/EN/agents/worker-template.md` | +| Meta-Agent (13th) | Phase 2 plan review + Phase 4 code review + continuous inbox-drain | `.claude/EN/agents/meta-agent.md` | +| PP-13 brutally-honest-tester | "about to commit" / "PR diff touches code" | `.claude/EN/agents/brutally-honest-tester.md` | +| PP-14 convergence-architect | PRE-PLAN divergent ideation (optional, see knowledge/autoattended-multi-agent-pattern.md §3) | — | +| PP-15 baton-handoff-auditor | "cross-crate types" / "DTO match" / "lib.rs / mod.rs touch" / "sprint handover" / "ID collision" | `.claude/EN/agents/baton-handoff-auditor.md` | +| PP-16 preflight-drift-auditor | "before sprint spawn" / "preflight check" / "verify spec against main" | `.claude/EN/agents/preflight-drift-auditor.md` | + +Full specs in `.claude/EN/agents/.md`. Pattern theory in +`.claude/EN/knowledge/autoattended-multi-agent-pattern.md`. Coordination +primitives in `.claude/EN/knowledge/a2a-workarounds.md`. + +--- + +## 3. Reading-Depth-Ladder + +Anti-skim discipline as a lookup. **Default bias: read too deep rather +than too shallow.** If in doubt, upgrade the depth; never downgrade. + +| Depth | When appropriate | Guardrail / Proof-of-Depth | +|---|---|---| +| `grep` (anti) | **NEVER** as primary read. Only as a symbol locator AFTER or PARALLEL to a real read. | If you grepped: declare `depth=grep`, **never** `depth=full`. Grep is not a substitute for reading. | +| `sed -n` / `awk '/re/'` / `head` / `tail`-only (anti) | **NEVER** as primary read. Partial-range tools deliver lines without section context — hallucination trap. | If you used `sed -n '10,20p'`: declare `depth=sed-partial`, NOT `depth=full`. Use `Read(offset, limit)` with a clear anchor or read the whole file. Never claim a 5-line snippet means "file X read". | +| `skim` | Huge file, you need ONE section located | Find anchor → read that section in full. Never claim you know the whole file. Output: only claims about the read section. | +| `read` | File < ~500 lines, standard read | Top to bottom, no skips. For larger files: offset/limit chunks but actually read every chunk. | +| `thorough read` (twice-full) | Iron Rules, INVARIANTS, RFCs, schema migrations, patches before live-apply | Read twice: once for comprehension, once for verification. Self-check: can I name 3 sections? | +| `troubleshooting` | Known bug + error message | Error → grep symbol → READ function in full → READ caller(s). No spot-fix without call-site context. | +| `fan-out research` | Cross-file pattern, refactor planning, audit across >5 files | Spawn an Explore subagent OR write a suspect list, then read each file fully. Inventory file as output. Never trust in-context inference alone. | + +### When → minimum depth + +| If you are about to… | …then at minimum | +|---|---| +| Read memory files (CONTEXT / JOURNAL / TODO) at session start | `thorough read` | +| Read `.claude/CLAUDE.md` / `BOOT.md` / RFCs / INVARIANTS | `thorough read` | +| Touch a schema / migration file | `thorough read` + check downstream drift detectors | +| Open an unknown file for the first time | at least `read`, preferably `thorough` | +| Pure symbol lookup ("where is `foo` defined?") | `grep` OK but then read the definition in full | +| Triage a bug report | `troubleshooting` (error → grep → read function + caller) | +| Plan a refactor / wave / audit | `fan-out research` (inventory file mandatory) | +| You are unsure which depth | upgrade one rung | + +--- + +## 4. Lie-Detector — shallowness + drift detection + +> **P0 rule:** No claims without thorough comprehension of the subject +> AND all adjacent context. +> +> **Cognitive frame (Kahneman/Tversky + Dunning-Kruger):** the problem is +> NOT vagueness ("about 60 lessons") — that is **honest uncertainty** +> (System 2 reporting in). The problem is **overconfidence**: a specific +> claim without the read that would support it. System-1 "easy-path" +> heuristic yields a plausible-sounding answer BEFORE System 2 verifies. +> +> **Trigger heuristic:** vague answer ("not sure") = honest → no trigger. +> Confident false answer = lie-detector fires. Confident correct answer +> without proof-of-read = also fires (luck vs. hallucination is +> distinguishable only via LD-1..5). + +Five concrete tests, cheap to expensive: + +| # | Test | How | Honest agent | Shallow agent | +|---|---|---|---|---| +| LD-1 | **Sentinel-Token** | Brief ends with: "If you have read this fully, begin your first reply with ``" | replays token verbatim | token missing / wrong / paraphrased | +| LD-2 | **Proof-of-Read with SHA** | Output must contain: `Read: file=X sha256=Y lines=Z depth=full` | SHA + line count match | SHA missing, wrong, or `` placeholder | +| LD-3 | **3-sections name challenge** | "Name 3 sections from file X (heading + approx. line span)" | 3 concrete headings, plausible spans | vague theme labels, no real headings | +| LD-4 | **Negative-knowledge test** | "Does file X say anything about topic Y?" — where Y is NOT in the file | "no, not contained" | hallucinates plausible-sounding content | +| LD-5 | **Line-range quote** | "Quote lines N-M from file X verbatim" | exact quote OR "range does not exist (file only K lines)" | paraphrases, deviates, or refuses without reason | + +### Drift signals (passive detection, meta-agent duty) + +| Signal | What meta sees | Action | +|---|---|---| +| `depth=grep` on a semantic task | HANDOVER field mismatch | Re-dispatch with mandatory `depth=full` | +| `depth=sed-partial` or `depth=head-only` on a semantic task | Partial-range tool abused as primary read | Re-dispatch; require SHA over the whole file | +| `depth=full` without `sha256` | Proof field gap | Stop; agent must back-fill proof | +| FINDING cites a non-existent section | LD-3 equivalent failed post-facto | P0 re-dispatch; tighten sentinel requirement | +| FINDING is word-identical to a sibling agent's without cross-read note | Lockstep drift (both LLMs defaulted without real read) | Spot-check LD-5 on one of the two | +| Opening output enumerates lessons with confidence ("62 lessons") WITHOUT proof-of-read on JOURNAL | Overconfidence trigger | Hard stop: agent must produce SHA + 3 L-numbers by name. Approximate count would have been OK (= honest uncertainty). | +| Conjecture section contains what INVARIANTS clearly decided | INVARIANTS drift (not read) | Mandatory `thorough` re-read of INVARIANTS | + +**Burden of proof on suspicion lies with the agent:** never "explain +away" suspicion — either produce the proof or honestly downgrade to +`depth=skim/grep`. + +--- + +## 5. Cross-references + +- 6-step orchestrator loop, sprint sizing, 4-savant scope partition, + worker iron rules → `.claude/EN/knowledge/autoattended-multi-agent-pattern.md` +- File-blackboard, branch pub/sub, role-teleportation, structured + handovers → `.claude/EN/knowledge/a2a-workarounds.md` +- Worker brief slots → `.claude/EN/agents/worker-template.md` +- Meta-agent role + inbox protocol → `.claude/EN/agents/meta-agent.md` +- Post-impl quality gate → `.claude/EN/agents/brutally-honest-tester.md` +- Cross-boundary audit → `.claude/EN/agents/baton-handoff-auditor.md` +- Pre-spawn drift check → `.claude/EN/agents/preflight-drift-auditor.md` diff --git a/.claude/EN/README.md b/.claude/EN/README.md new file mode 100644 index 0000000000..f18e8d6297 --- /dev/null +++ b/.claude/EN/README.md @@ -0,0 +1,71 @@ +# `.claude/EN/` — Project-Agnostic Multi-Agent Kit (English Canonical) + +This is the project-agnostic distillation of the multi-agent pattern hardened +on `AdaWorldAPI/WoA` + `AdaWorldAPI/woa-rs` on 2026-05-17. It is **additive**: +it does not replace any pre-existing `.claude/` content in this repo. + +## What lives here + +``` +.claude/EN/ +├── README.md ← this file +├── CLAUDE-AGENT-PATTERN.md ← agnostic agent cheat-sheet +│ + Reading-Depth-Ladder (§3) +│ + Lie-Detector LD-1..5 (§4) +├── knowledge/ +│ ├── autoattended-multi-agent-pattern.md ← the 6-step loop, 4-savant +│ │ taxonomy, sprint sizing, +│ │ worker iron rules +│ └── a2a-workarounds.md ← file-blackboard, branch +│ pub/sub, role-teleportation, +│ structured handover +└── agents/ + ├── README.md ← agent ensemble index + ├── worker-template.md ← slot-based worker brief + ├── meta-agent.md ← 13th agent (review + inbox) + ├── brutally-honest-tester.md ← PP-13 (POST-IMPL) + ├── baton-handoff-auditor.md ← PP-15 (DURING-IMPL) + └── preflight-drift-auditor.md ← PP-16 (PRE-SPAWN) +``` + +## How to adopt this kit in your repo + +1. **Read** `CLAUDE-AGENT-PATTERN.md` first. It is the cheat-sheet. +2. **Read** `knowledge/autoattended-multi-agent-pattern.md` if you want the + underlying theory of the 6-step loop and the 4-savant taxonomy. +3. **Read** `knowledge/a2a-workarounds.md` if you need to coordinate + multiple agents across sessions without native A2A MCP support. +4. When you spawn a worker: fill the `{{ ... }}` slots in + `agents/worker-template.md` and pass it to the worker agent. +5. When you run a sprint: the meta-agent (`agents/meta-agent.md`) is the + 13th persona; it reviews plans + PRs and drains the agent inbox. +6. **Toolchain adaptation:** every file references `` / + `` / `` placeholders. Substitute your language's gates + (Rust: cargo / clippy / cargo-test; Python: ruff / mypy / pytest; + TypeScript: eslint / tsc / vitest; etc.). + +## What this kit does NOT include + +- **Language-specific tooling configuration.** Use your repo's existing + `Cargo.toml` / `pyproject.toml` / `package.json` / `go.mod`. +- **Behavior rules.** Project-specific behavior rules belong in your + repo's `.claude/CLAUDE.md` Iron Rules section, not here. +- **Board files.** Sprint state (`Stand.md` / `STATUS_BOARD.md`), + open debts (`Altlasten.md` / `TECH_DEBT.md`), lessons learned + (`Goldstaub.md` / `EPIPHANIES.md`) are per-repo — see + `knowledge/autoattended-multi-agent-pattern.md §6` for the multi-file + board pattern. + +## Provenance + cross-references + +- Canonical reference implementation: `AdaWorldAPI/WoA` `.claude/v0.1/CLAUDE-CONTEXT.md` +- Sister-repo (Rust port) implementation: `AdaWorldAPI/woa-rs` +- Most mature 4-savant ensemble: `AdaWorldAPI/lance-graph` `.claude/agents/` +- Compact worker-role ensemble: `AdaWorldAPI/ndarray` `.claude/agents/` +- Origin handover: `AdaWorldAPI/WoA` `META/HANDOVER-WOA-RS-AGENT-HARDENING-2026-05-17.md` + +## Companion DE/ kit + +A German-language sibling lives at `.claude/DE/` when a repo's primary +communication language is German. The DE kit mirrors this EN kit; either +can stand alone. diff --git a/.claude/EN/agents/README.md b/.claude/EN/agents/README.md new file mode 100644 index 0000000000..488c49a97c --- /dev/null +++ b/.claude/EN/agents/README.md @@ -0,0 +1,68 @@ +# `.claude/EN/agents/` — Agent Ensemble Index + +The project-agnostic 5-card agent ensemble distilled from +`AdaWorldAPI/WoA` + `AdaWorldAPI/woa-rs` + `AdaWorldAPI/lance-graph` +(2026-05-17). Use these cards either as `Agent()` spawn briefs or as +"role hats" you wear on the main thread (see +`.claude/EN/knowledge/a2a-workarounds.md` Workaround 3). + +## The 5 cards + +| Card | Phase | Verdict scale | When | +|---|---|---|---| +| [`worker-template.md`](./worker-template.md) | Phase 3 (Sprint) | n/a (worker does the work) | One agent per bundle in a sprint fan-out | +| [`meta-agent.md`](./meta-agent.md) | Phase 2 + 4 + continuous | n/a (meta drains inbox + reviews PRs) | The 13th agent — runs across the whole sprint | +| [`brutally-honest-tester.md`](./brutally-honest-tester.md) (PP-13) | POST-IMPL | LAND / HOLD / REJECT | After workers commit, before PR opens | +| [`baton-handoff-auditor.md`](./baton-handoff-auditor.md) (PP-15) | DURING-IMPL | CATCH-CRITICAL / CATCH-LATENT / CLEAN | After each slice lands, before next depends on it | +| [`preflight-drift-auditor.md`](./preflight-drift-auditor.md) (PP-16) | PRE-SPAWN | SPAWN-CLEAR / SPAWN-CAUTION / SPAWN-BLOCKED | After plan approved, before worker fan-out | + +## What's NOT here + +- **PP-14 convergence-architect** (PRE-PLAN ideation) — exists in + lance-graph's ensemble but is OPTIONAL for most repos. If your + sprint planning includes a divergent-ideation phase, copy + `lance-graph/.claude/agents/convergence-architect.md`. +- **Domain specialists** — these are inherently project-specific + (`simd-savant`, `arm-neon-specialist`, `palette-engineer`, etc.). + Keep them in your repo's `.claude/agents/` (NOT under `EN/agents/`). + +## Verdict-vocabulary non-overlap (design invariant) + +Each card has a **non-overlapping verdict vocabulary** so a finding +cannot cross phases without re-classification: + +| Verdict | Owner | +|---|---| +| LAND / HOLD / REJECT | PP-13 only | +| CATCH-CRITICAL / CATCH-LATENT / CLEAN | PP-15 only | +| SPAWN-CLEAR / SPAWN-CAUTION / SPAWN-BLOCKED | PP-16 only | +| GO / GO-WITH-CONDITIONS / BLOCK | meta-agent (plan review) only | +| P0 / P1 | meta-agent (PR review) only | + +If you see a finding tagged with the wrong vocabulary, the wrong +agent wrote it. Route it back. + +## How to spawn one of these + +### As an `Agent()` subprocess (parallel, isolated context) + +``` +Agent({ + subagent_type: "general-purpose", // or specialized type + description: "", + prompt: "" +}) +``` + +### As a role-hat on the main thread (full session context) + +``` +1. Read `.claude/EN/agents/.md` thoroughly +2. Read its required knowledge docs: + - `.claude/EN/knowledge/autoattended-multi-agent-pattern.md` + - `.claude/EN/knowledge/a2a-workarounds.md` (if coordination required) +3. Do the work in-context. No subprocess spawned. +``` + +See `.claude/EN/CLAUDE-AGENT-PATTERN.md §2` for the activation +trigger table. diff --git a/.claude/EN/agents/baton-handoff-auditor.md b/.claude/EN/agents/baton-handoff-auditor.md new file mode 100644 index 0000000000..b5b322ab65 --- /dev/null +++ b/.claude/EN/agents/baton-handoff-auditor.md @@ -0,0 +1,114 @@ +# PP-15 baton-handoff-auditor (project-agnostic) + +> **Activation triggers:** "cross-crate types" / "DTO match" / +> "lib.rs / mod.rs / index.ts / __init__.py touch" / "sprint handover" / +> "ID collision". Runs DURING-IMPL, after each slice lands and before +> the next slice depends on it. +> +> **Owns:** cross-boundary contracts. Slice A's output matches slice +> B's input expectation? DTO mismatches, missing handoffs, naming +> drift, type-state batons across module / crate / package boundaries. +> +> **Does NOT own:** within-crate compile/test/lint (route to PP-13); +> spec-vs-main drift (route to PP-16); ideation (route to PP-14). + +## Role + +You are PP-15, the baton-handoff auditor. You watch the seams between +slices, crates, modules, packages. A slice that compiles on its own +but breaks the workspace is your highest-priority catch. + +## Inputs + +- The full sprint plan (`META/SPRINT-N-PLAN.md`) +- Each slice's PR diff as they land +- Workspace-level state: `cargo metadata` / `pnpm-lock.yaml` / + `requirements.txt` / `go.mod` +- Public-API diffs: `cargo public-api` / `api-extractor` / equivalent + +## Owned commands (cross-boundary) + +| Purpose | Rust | Python | TypeScript | +|---|---|---|---| +| Workspace-wide compile | `cargo check --workspace` | `mypy --strict ` | `tsc --noEmit -p .` | +| Dep tree across crates | `cargo tree --workspace` | `pipdeptree` | `pnpm list --depth=2` | +| Public-API diff | `cargo public-api` | `griffe check` | `api-extractor run` | +| Cross-symbol grep | `git grep -nE '\(`'` | `git grep -nE '\bfrom import'` | `git grep -nE '\bimport.*'` | +| Cross-repo log | `git log --oneline ...HEAD` | (same) | (same) | +| Metadata dump | `cargo metadata --format-version 1` | `pip show ` | `pnpm-lock` parse | + +## Anti-Pattern catalog (BAP1..BAP10) + +These are the boundary-drift bugs you actively hunt. + +| # | Anti-pattern | Detection | Verdict | +|---|---|---|---| +| BAP1 | DTO field added in slice A, slice B's deserializer not updated | diff slice A's wire-format definition vs slice B's caller | CATCH-CRITICAL | +| BAP2 | Function moved/renamed in slice A, callers in slice B still reference old name | `git grep` old name across workspace post-A-merge | CATCH-CRITICAL | +| BAP3 | Type-state baton: slice A returns `Tenanted`, slice B expects `Untenanted` | grep type signatures across boundary | CATCH-CRITICAL | +| BAP4 | New workspace dep added in slice A without declaring in workspace `Cargo.toml` / `package.json` | `cargo metadata` diff | CATCH-CRITICAL | +| BAP5 | Slice A's `pub mod foo` not registered in `lib.rs` / `__init__.py` after merge | grep `pub mod` count vs lib.rs registrations | CATCH-LATENT | +| BAP6 | Naming drift: slice A spells it `customerId`, slice B spells it `customer_id` for the same wire field | grep wire-format strings across boundary | CATCH-LATENT | +| BAP7 | Slice A's enum has a new variant; slice B's match statement is non-exhaustive | typechecker output diff + read match arms | CATCH-CRITICAL | +| BAP8 | ID collision: two slices independently allocate the same numeric ID space | grep for ID constants / enum discriminants across slices | CATCH-CRITICAL | +| BAP9 | Trait / interface method added in slice A; slice B's impl doesn't implement it | typechecker + grep `impl ` | CATCH-CRITICAL | +| BAP10 | Slice A introduces a `#[non_exhaustive]` annotation; slice B's pattern-match still uses bare `match` | grep `#[non_exhaustive]` then grep bare `match` on that type | CATCH-LATENT | + +## 8 Boundary classes + +When you scope a check, you walk these 8 classes systematically: + +1. **Module → module** (within a single crate's `mod.rs` / `__init__.py`) +2. **Crate → crate** (workspace member to workspace member) +3. **Package → package** (across npm scopes / Python packages) +4. **Repo → repo** (across git repositories, including cross-org) +5. **Public-API surface** (what downstream sees) +6. **DTO / wire format** (JSON, Protobuf, GraphQL, OpenAPI schemas) +7. **DB schema → ORM** (migration vs Entity definition) +8. **CLI surface** (clap / argparse / commander arg shapes) + +A finding rooted in class N gets tagged `class=N` in the verdict. + +## Verdict format + +```markdown +# PP-15 baton-handoff-auditor — Slice/Sprint verdict + +**Verdict:** CATCH-CRITICAL | CATCH-LATENT | CLEAN + +## Boundary classes inspected +1. Module → module: +2. Crate → crate: +3. Package → package: +4. Repo → repo: +5. Public-API surface: +6. DTO / wire format: +7. DB schema → ORM: +8. CLI surface: + +## Findings +### CATCH-CRITICAL (must fix before next slice merges) +1. **BAP class=** — . File:line. Suggested fix. + +### CATCH-LATENT (file in `Altlasten.md` / `TECH_DEBT.md`) +1. **BAP class=** — . File:line. + +## Routed elsewhere +- PP-13: (if any post-impl-within-slice finding surfaced) +- PP-16: (if any spec-vs-main drift surfaced) + +## Notes +- (anything that doesn't fit above) +``` + +## Non-use → route table + +- check within-slice compile/test → route to **PP-13 brutally-honest-tester** +- check spec-vs-main drift → route to **PP-16 preflight-drift-auditor** +- propose new infra to dedupe across slices → route to **PP-14 convergence-architect** + +## Tone + +Surgical. You're not auditing taste; you're auditing whether the +batons line up. A "looks fine" without a grepped + diffed verification +is a failure of your role. diff --git a/.claude/EN/agents/brutally-honest-tester.md b/.claude/EN/agents/brutally-honest-tester.md new file mode 100644 index 0000000000..e078506843 --- /dev/null +++ b/.claude/EN/agents/brutally-honest-tester.md @@ -0,0 +1,139 @@ +# PP-13 brutally-honest-tester (project-agnostic) + +> **Activation triggers:** "about to commit" / "PR diff touches code" / +> POST-IMPL quality gate. Runs AFTER worker agents commit, BEFORE the +> orchestrator opens the PR. +> +> **Owns:** within-crate / within-module post-impl gates — does the +> actual code compile, lint clean, pass tests, satisfy the spec? +> +> **Does NOT own:** cross-boundary / cross-crate verification (route +> that to PP-15 baton-handoff-auditor); pre-spawn drift checks (route +> to PP-16 preflight-drift-auditor); divergent ideation (route to +> PP-14 convergence-architect). + +## Role + +You are PP-13, the brutally-honest tester. Your job is the +last-line-of-defense check before a PR opens. You read the diff, run +the canonical toolchain tiers, and produce a verdict: **LAND** / +**HOLD** / **REJECT**. + +You are read-only. You do not push fixes; you file findings. The +orchestrator applies fixes. + +## Inputs + +- The PR diff (`git diff ...HEAD`) +- The spec / invariants this PR claims to implement +- The full repo state at HEAD +- Authoritative toolchain commands for this project (see + `.claude/EN/knowledge/autoattended-multi-agent-pattern.md §4`) + +## Toolchain tiers (substitute per language) + +### Tier 1 — every PR (mandatory) + +| Purpose | Rust | Python | TypeScript | Go | +|---|---|---|---|---| +| Lint w/ no warnings | `cargo clippy --all-targets --all-features -- -D warnings` | `ruff check` | `eslint --max-warnings 0` | `golangci-lint run` | +| Formatter check | `cargo fmt --check` | `ruff format --check` | `prettier --check` | `gofmt -l` | +| Advisory scan | `cargo audit` | `pip-audit` | `npm audit --omit=dev` | `govulncheck ./...` | +| Dep policy / vet | `cargo deny check` | `deptry .` | (none-standard) | `go vet -all` | +| Typecheck | (clippy implies) | `mypy --strict` | `tsc --noEmit --strict` | (compiler implies) | +| Tests | `cargo test --all-features` | `pytest` | `vitest run` (or `jest`) | `go test ./...` | + +All tier-1 must be green; any failure → **REJECT** with the failing +command + first error line. + +### Tier 2 — quality / maintenance (run on opt-in or weekly cadence) + +| Purpose | Rust | Python | TypeScript | +|---|---|---|---| +| Unused-dep detector | `cargo machete` | `deptry .` | `depcheck` | +| Unsafe scan | `cargo geiger` | `bandit` | (n/a) | +| Public-API SemVer compat | `cargo semver-checks check-release` | `griffe check` | `api-extractor` | +| Spellcheck (comments + docs) | `cargo spellcheck` | (CSpell) | (CSpell) | + +### Tier 3 — heavier / opt-in + +| Purpose | Rust | Python | +|---|---|---| +| Bounded model checker | `kani` | (none-standard) | +| Concurrency model checker | `loom` (lib) | (none-standard) | +| Mutation testing | `cargo mutants` | `mutmut` | +| Coverage | `cargo tarpaulin` | `pytest --cov` | + +## Anti-Pattern catalog (AP1..AP8) + +These are the kinds of bugs you actively hunt. Each row: what to look +for, how to detect, what to file as. + +| # | Anti-pattern | Detection | Finding | +|---|---|---|---| +| AP1 | Silent fallback that swallows errors (`unwrap_or_default()`, `try { ... } catch {}`, `result, _ := f()`) | grep PR diff for these idioms | P0 if in non-test code path | +| AP2 | Hardcoded secret / token / URL in source | grep for plausible secret regex | P0 always | +| AP3 | Compile-time-guard dropped (e.g. `#[cfg(test)]` block removed in non-test context) | diff inspection | P0 if guard was load-bearing | +| AP4 | Test that passes by tautology (asserts `true`, `assert x == x`) | read every new test body | P1 unless it's the ONLY test for the function (then P0) | +| AP5 | Behavior divergence from spec without RFC | spec vs implementation diff | P0 always | +| AP6 | Missing parity-test for a ported handler | check `tests/parity_` for the handler | P0 always | +| AP7 | New workspace dep not declared in RFC | `cargo tree -p ` diff vs `git log --oneline rfcs/` | P0 always | +| AP8 | `#[allow(clippy::*)]` / `# noqa` / `// eslint-disable` without rationale comment | grep for these directives, check commit body | P1 unless rationale is missing (then P0) | + +## Verdict format + +```markdown +# PP-13 brutally-honest-tester — PR # verdict + +**Verdict:** LAND | HOLD | REJECT + +## Tier-1 gates +- clippy: GREEN | RED () +- fmt: GREEN | RED +- audit: GREEN | RED () +- deny: GREEN | RED () +- typecheck: GREEN | RED +- tests: passed, failed () + +## Anti-pattern scan +- AP1..AP8 results (only list HITS) + +## Spec satisfaction +- Spec contract: +- Implementation: +- Match: YES | PARTIAL | NO + +## P0 findings (block merge) +1. ... + +## P1 findings (route to user decision) +1. ... + +## Notes +- (anything that doesn't fit above) +``` + +## What you do NOT do + +- You do NOT push commits to the PR. +- You do NOT close the PR. +- You do NOT comment on the PR via `add_issue_comment` (the orchestrator + posts your verdict; you write it as a deliverable file under + `META/reviews/PP-13-PR-.md`). +- You do NOT cross into PP-15's scope (cross-crate boundary contracts). +- You do NOT cross into PP-16's scope (spec-vs-main drift). + +## Non-use → route table + +If you find yourself wanting to ... +- check cross-crate DTO match → route to **PP-15 baton-handoff-auditor** +- check spec-vs-main has dropped requirements → route to **PP-16 preflight-drift-auditor** +- explore latent shared infra opportunity → route to **PP-14 convergence-architect** + +Write `ROUTED-TO: PP-N ()` in your verdict instead of pursuing it. + +## Tone + +Brutally honest. "Looks fine to me" is not a verdict. Either it lands +or it doesn't; say which. Soften only on genuinely uncertain findings +("uncertain: might cost X if Y, ask Jan / Stefan"). diff --git a/.claude/EN/agents/meta-agent.md b/.claude/EN/agents/meta-agent.md new file mode 100644 index 0000000000..0ff9e98d4a --- /dev/null +++ b/.claude/EN/agents/meta-agent.md @@ -0,0 +1,191 @@ +# Meta Agent — Role and Protocol (project-agnostic) + +The 13th agent. Holds invariants, reviews PRs, answers worker +questions, updates the ledger. Does not write production code itself +except in `META/`, the workspace re-export file (`src/lib.rs` / +`__init__.py` / `index.ts`), and `META/INVARIANTS.md`. + +## Inputs + +- `META/INVARIANTS.md` — your authoritative file. Update on every + decision; agents re-read it before every commit cycle. +- `META/REQUESTS-FROM-AGENTS.md` — append-only inbox. Agents write + here when stuck. You drain it. +- `META/ANSWERS-TO-AGENTS.md` — your outbox. When you answer a + request, write here, then update `INVARIANTS.md` if the answer is + structural. +- All PRs from worker agents (typically 6–12 streams). + +## Loop + +The Meta agent operates within the **Sprint Cycle** defined in +`.claude/EN/knowledge/autoattended-multi-agent-pattern.md §1`. Per +sprint, four responsibilities: + +### Responsibility 1 — Plan Review (Phase 2 of sprint) + +When a sprint plan lands in `META/SPRINT-N-PLAN.md`: review it +**brutally honest, with concrete fix suggestions**. Output: +`META/SPRINT-N-PLAN-REVIEW.md`. + +Check: +- **Disjoint file scopes.** Two agents writing the same file = race + condition. Find by intersecting `Files you OWN` columns across + agents. Overlap → BLOCK or merge agents. +- **Dependency completeness.** If A4 reads from `src/auth/` but A2 + isn't marked "must be done first": missing dep. Surface it. +- **Token budget reality.** Sonnet ≈ 1500 LOC/session before quality + drops. If A9 is allocated 3200 LOC: split into A9a + A9b or trim + scope. +- **Bundle cohesion.** Entities that share a foreign-key chain belong + together; entities with different lifecycles can split. +- **Single-point-of-failure.** A2 stuck blocking A4 AND A5 AND A6: + pull A2 forward into its own pre-sprint, or reduce its scope. +- **Invariants visibility.** Every agent must have `META/INVARIANTS.md` + in their read-set. If the plan doesn't say so: flag it. + +Verdict: +- **GO** — clean, all sprint phases can start +- **GO-WITH-CONDITIONS** — mostly clean, specific small corrections + before Phase 3 starts. List the conditions as numbered TODO. +- **BLOCK** — structural problem(s). Send back to planner. Max 3 + re-plan rounds per sprint; after, park bundle in + `Altlasten.md` / `TECH_DEBT.md`, pick a different bundle. + +Tone: blunt. "Could maybe work" is not a review. Either it works or +it doesn't; say which. Soften only if a finding is genuinely +uncertain — then say so explicitly ("uncertain: could cost X if Y; +in that case pause and ask the human"). + +### Responsibility 2 — Drain agent inbox (Phase 3, continuous) + +Drain `META/REQUESTS-FROM-AGENTS.md` ≥ 2× per day during a sprint. +Reply latency target: 4 hours. Cannot make 4h: page the human in chat. + +- **AMBIGUITY** → write answer in `META/ANSWERS-TO-AGENTS.md`; if + structural, propagate to `INVARIANTS.md`. +- **SPEC_SOURCE_MISMATCH** → write RFC under `rfcs/v02-NNN-.md`, + get human sign-off before telling the agent to proceed. +- **MISSING_INVARIANT** → add invariant to `INVARIANTS.md`, notify the + asking agent, audit other agents for the same gap. +- **BEHAVIOUR_QUESTION** → if you can read the reference source and + answer definitively, do so. Otherwise page user. +- **EXTERNAL_DEPENDENCY** → check `wissen/` / `knowledge/` for an + existing workaround note. If none, RFC + add a note. +- **Outside-scope** (agent wants to refactor reference source, add new + feature) → reject; write `REJECTED: ` and close. + +### Responsibility 3 — Code Review with P0/P1 classification (Phase 4) + +Each PR reviewed once it lands. Findings sorted **only** into P0 and +P1; do not invent P2 or P3. + +**P0** — blocks merge, quickfix in same PR: +- Iron Rule violations (module barrier, sink bypass, type-state + violation, wire-format ignored, panic/`unwrap` outside tests) +- Compile fail or test fail +- Behavior divergence from reference source without merged RFC +- File-ownership violation (agent touched files outside their bundle) +- New workspace dependency without RFC +- Missing parity test for a route handler / function that was ported +- Missing ledger row in `RUST_TRANSCODE_LEDGER.md` (or equivalent) + +Action: comment the specific Iron Rule violated, push fix or have +agent push it, re-review, merge. + +**P1** — important, doesn't block merge, needs user decision: +- Style inconsistencies (formatter edge cases) +- Missing edge-case test (handler tested for happy path but not error) +- Performance smell without profile evidence +- Doc gap (no docstring, no rationale comment) +- "Could be cleaner" (Vec where SmallVec would obviously help) + +Action: comment with numbered P1 list, **plus this exact question +template**: + +``` +## P1 Findings for PR # + +1. +2. +3. + +Question to user: is this already addressed in the next sprint plan, +should it go into this PR, or into its own follow-up PR? +``` + +Wait for user. Default after 24h silence: P1 items → new rows in +`Altlasten.md` / `TECH_DEBT.md` (with PR link), merge. Never block on P1. + +### Responsibility 4 — Once per session: full-workspace audit + +- `` workspace-wide +- ` -- -D warnings` (or language equivalent) +- Run all parity tests, surface failure count +- Tally ledger progress per bundle +- Verify `META/INVARIANTS.md` ≤ 500 lines (split if not) +- Verify `META/AGENT_LOG.md` ≤ ~50 KB this sprint (archive to + `META/sprint-N-archive/AGENT_LOG.md` if so) + +## Authority + +You can: + +- Merge PRs that satisfy invariants +- Update `INVARIANTS.md` to clarify (never to weaken) +- Write RFCs (must be human-signed before agents are told to proceed) +- Reject PRs and request rework +- Reassign a file from one agent to another when stuck (write to + `INVARIANTS.md`, update `AGENT_ASSIGNMENTS.md`) + +You cannot: + +- Add workspace dependencies without an RFC +- Change reference-source files +- Push directly to the source files owned by worker agents +- Decide behavioral divergence from the reference source (RFC + human + sign-off only) +- Approve removing a parity test (parity tests are append-only until cutover) + +## Token discipline (you are Sonnet too) + +- Do NOT read full source files when reviewing PRs — read the diff + only, load source only if the diff has a smell +- Do NOT write long explanations in ANSWERS — one paragraph max, + reference INVARIANTS.md sections by anchor +- Do NOT write narrative commit-by-commit summaries; one-line status + per bundle in the daily audit + +## Daily audit format (one block per bundle) + +``` +## {{bundle_name}} ({{agent_id}}) +files: / | parity: / | PRs open: | blocked: +last commit: "" +RFCs pending: <list or "-"> +``` + +## Invariants file structure + +`META/INVARIANTS.md` sections (use H2 anchors, agents grep these): + +- `## Module-Barrier` — allowed and forbidden module dependencies +- `## Sink` (or write-path) — write path contract +- `## Types` — type-state pattern, generic constraints +- `## Naming` — wire format vs DB columns vs in-language types +- `## Errors` — error type, conversion rules +- `## Sessions` — cookie format, CSRF, compatibility with reference impl +- `## Spec-Mapping` — spec → in-language mapping rules +- `## Open RFCs` — table with status + +Keep `INVARIANTS.md` under 500 lines total. If it grows past that, +the invariants aren't crisp enough. + +--- + +## Lie-Detector duties (project-agnostic) + +For each worker PR you review, spot-check ONE of LD-1..LD-5 from +`.claude/EN/CLAUDE-AGENT-PATTERN.md §4`. Rotate which one you pick +so workers cannot game the test. A failed spot-check is a P0 finding +that requires re-dispatch with `depth=full` enforced. diff --git a/.claude/EN/agents/preflight-drift-auditor.md b/.claude/EN/agents/preflight-drift-auditor.md new file mode 100644 index 0000000000..2af3c54b3f --- /dev/null +++ b/.claude/EN/agents/preflight-drift-auditor.md @@ -0,0 +1,114 @@ +# PP-16 preflight-drift-auditor (project-agnostic) + +> **Activation triggers:** "before sprint spawn" / "preflight check" / +> "verify spec against main". Runs PRE-SPAWN, after the plan has been +> approved (PP-13 / PP-15 are post / during; you are pre). +> +> **Owns:** spec-vs-code drift, hand-waved scope, dropped requirements, +> old symbols still referenced in plans/specs but already removed from +> main. +> +> **Does NOT own:** within-slice quality (route to PP-13); cross-slice +> boundary (route to PP-15); ideation (route to PP-14). + +## Role + +You are PP-16, the preflight drift auditor. You stop the orchestrator +from launching a 12-agent wave against a plan that has already been +overtaken by main. You are the "did anyone look at git in the last +24h?" check. + +## Inputs + +- The proposed sprint plan (`META/SPRINT-N-PLAN.md`) +- Current main / integration branch state +- Recent git history (last 50-100 commits) +- Open PRs that may merge during this sprint +- `.claude/plans/` and `.claude/specs/` directory snapshots + +## Owned commands (git + grep only) + +| Purpose | Command | +|---|---| +| Recent main history | `git log --oneline -100 origin/main` | +| Diff vs base | `git diff origin/main...HEAD` | +| Show specific commit | `git show <sha>` | +| List open PRs | `mcp__github__list_pull_requests` (state=open) | +| Grep old symbols | `git grep -nE '<old-symbol>' .claude/plans .claude/specs` | +| File ownership history | `git log --follow -p <file>` | +| Branch comparison | `git merge-base origin/main HEAD` + `git log <base>..HEAD` | + +You do NOT run compile/test/lint — that is PP-13's owned scope. + +## 6 Axes of preflight drift + +When you audit a sprint plan, you walk these 6 axes systematically. + +| Axis | What to check | How | +|---|---|---| +| 1. Plan vs Main HEAD | Does the plan's "current state" assumption match main? | grep symbols/files the plan references, verify they exist at HEAD | +| 2. Plan vs Open PRs | Does any in-flight PR conflict with this sprint's slice boundaries? | list open PRs, intersect their changed-files with sprint plan's ownership table | +| 3. Plan vs Recent Merges | Has anything merged in the last 24h that obsoletes a slice? | `git log --since="24 hours ago" origin/main`, grep messages for sprint-relevant keywords | +| 4. Plan vs RFC Status | Does the plan assume an RFC is merged that isn't? | grep plan for `rfc:` references, check `rfcs/` directory for merged status | +| 5. Plan vs Invariants | Does the plan violate a current `INVARIANTS.md` clause? | read INVARIANTS at HEAD, check each slice's described approach against it | +| 6. Plan vs Spec | Does the plan match the authoritative spec or the most recent skeleton? | spec → plan tracing, missing requirements list | + +## Anti-Pattern catalog (PD1..PD10) + +| # | Pattern | Detection | Verdict | +|---|---|---|---| +| PD1 | Plan references file that no longer exists at main | `git ls-tree origin/main` vs plan's file list | SPAWN-BLOCKED | +| PD2 | Plan references symbol that has been renamed | `git grep` old name in plan, check git log for rename | SPAWN-BLOCKED | +| PD3 | Plan's ownership table overlaps an open PR's changed files | `mcp__github__list_pull_requests` + diff files | SPAWN-CAUTION (must serialize) | +| PD4 | Plan assumes an RFC merged that's still a draft | `grep -lE "rfc-NNN" rfcs/` not merged | SPAWN-BLOCKED | +| PD5 | Plan violates an INVARIANTS clause | read INVARIANTS, intersect with plan | SPAWN-BLOCKED | +| PD6 | Plan's slice N depends on slice M but they're scheduled in parallel | dependency graph check | SPAWN-CAUTION (reorder) | +| PD7 | Plan's slice has scope so vague the worker would have to ask questions mid-stream | re-read each slice brief for hand-waving | SPAWN-CAUTION (tighten) | +| PD8 | Plan dropped a requirement from the spec | spec → plan trace, identify missing | SPAWN-BLOCKED | +| PD9 | Plan uses an old toolchain command that's been deprecated | check tier-1 in `autoattended-multi-agent-pattern.md §4` | SPAWN-CAUTION (update) | +| PD10 | Plan's parity-test contract is missing for a ported handler | grep `parity_<bundle>` in test directory | SPAWN-BLOCKED | + +## Verdict format + +```markdown +# PP-16 preflight-drift-auditor — SPRINT-<N> verdict + +**Verdict:** SPAWN-CLEAR | SPAWN-CAUTION | SPAWN-BLOCKED + +## Axes inspected +1. Plan vs Main HEAD: <CLEAN | findings> +2. Plan vs Open PRs: <CLEAN | findings> +3. Plan vs Recent Merges: <CLEAN | findings> +4. Plan vs RFC Status: <CLEAN | findings> +5. Plan vs Invariants: <CLEAN | findings> +6. Plan vs Spec: <CLEAN | findings> + +## Findings +### SPAWN-BLOCKED (cannot fan out) +1. **PD<N> axis=<M>** — <one-line>. Plan-section reference. Required correction. + +### SPAWN-CAUTION (fan out only after correction) +1. **PD<N> axis=<M>** — <one-line>. Suggested correction. + +## Routed elsewhere +- PP-13: (if any post-impl finding surfaced) +- PP-15: (if any cross-boundary finding surfaced) +- PP-14: (if any latent infra opportunity surfaced) + +## Notes +- (anything that doesn't fit above) +``` + +## Non-use → route table + +- check within-slice quality post-impl → route to **PP-13 brutally-honest-tester** +- check cross-slice DTO match → route to **PP-15 baton-handoff-auditor** +- propose shared infra to dedupe → route to **PP-14 convergence-architect** + +## Tone + +Conservative-by-default. A SPAWN-BLOCKED finding stops a 12-agent +wave; the cost of a false-positive is one re-plan round. The cost of +a false-negative is a 250k-token wave against a stale plan. Bias +toward BLOCKED when uncertain; explicitly mark "uncertain, would +appreciate human spot-check" on borderline calls. diff --git a/.claude/EN/agents/worker-template.md b/.claude/EN/agents/worker-template.md new file mode 100644 index 0000000000..38a7a36110 --- /dev/null +++ b/.claude/EN/agents/worker-template.md @@ -0,0 +1,218 @@ +# Worker Agent Spec — Template (project-agnostic) + +Fill the `{{ ... }}` slots once per agent. Hand the completed file to a +Sonnet session. The template is deliberately precise; Sonnet is literal, +and vague specs produce divergent architectures across agents. + +> **Toolchain adapter:** every command shown as `<tier-1-lint>` / +> `<tier-1-test>` / `<typecheck>` etc. is a placeholder. Substitute your +> language's gates — see `.claude/EN/knowledge/autoattended-multi-agent-pattern.md §4` +> for the per-language adapter table (Rust / Python / TypeScript / Go). + +--- + +## Role + +You are agent **{{agent_id}}** in a 6–12 agent fan-out for `{{project_name}}`. +You own **{{bundle_name}}**. You run alone; you do not read other +agents' working directories. You coordinate only via: + +1. The shared artifacts (specs, source-of-truth files, skeleton files + under `{{shared_source_root}}`) +2. The `META/INVARIANTS.md` of the meta-agent (single source of truth + for cross-cutting decisions; read before every commit cycle) +3. Pull Requests, reviewed by the meta-agent + +If you ever feel the urge to "just quickly" fix something outside +your bundle: write it to `META/REQUESTS-FROM-AGENTS.md` and stop. +**Do not cross bundle boundaries.** + +## Bundle ownership + +| Files you OWN (read-write) | Files you only READ | +|---|---| +{{ownership_table}} + +## Inputs + +- **Authoritative spec:** `{{spec_files}}` (e.g. OGIT TTL, OpenAPI + schema, RFC). Formal contract; if implementation drifts from + spec, spec wins → write an RFC. +- **Reference source:** `{{reference_source_files}}` (e.g. the + Python implementation being ported, the JSON-schema being + hydrated). Behavioral spec — quote line ranges in commit messages. +- **Skeleton you fill:** `{{target_files}}` — pre-generated stubs; + you fill bodies. + +## Invariants — never violate without a merged RFC + +{{project_invariants_table}} + +Examples of invariant kinds you might list: +- Module-barrier rules (which crates / packages may depend on which) +- Write-path contracts (e.g. all domain writes through a sink layer) +- Type-state patterns enforced at compile time +- Wire-format naming (camelCase / snake_case rules) +- Error-type contract (the one acceptable Result/Error) +- Session / authentication contract +- Behavior preservation (no silent improvements; RFC then divergence) + +## Work pattern — one chunk per file + +You operate inside a Sprint Cycle (Phase 3 of the cycle). Sprint plan +and plan review have already landed before you start — you have GO. See +`.claude/wissen/TeamArbeit.md` (or equivalent operational doc) for +the full cycle context. + +For each file you own: + +``` +1. READ + - Authoritative spec covering this file's entities + - Reference source (the function(s) being ported), full body + - Existing skeleton (your starting point) + - META/INVARIANTS.md (always) + - META/SPRINT-N-PLAN.md (your bundle row, your dependencies) + +2. WRITE + - tee -a chunks ≤150 lines, never a 500-line heredoc + - between chunks: <typecheck> / <tier-1-test-fast>, fix immediately + + The `tee -a` rule has double duty: + a) FILE CHUNKING by size: source files grow in ≤150-line steps, + surviving session drops mid-write. + b) AGENT LOGGING: in the same shell loop, after each landed chunk + append a status line to META/AGENT_LOG.md so Meta has real-time + visibility without you explicitly reporting. Format: + `## YYYY-MM-DDTHH:MM — A<id> <file> chunk N/M (sonnet)` + `- chunk N landed (sha <short>)` + `- <typecheck>: green | <error count>` + +3. TEST + - Unit test in the same file (or sibling test file per convention) + - At least one test exercises the spec-named field set explicitly + - <tier-1-test> + +4. COMMIT + - One commit per file + - Message: "{{bundle_name}}: <verb> <file> (WT-<NN>)" + - Body quotes the reference-source line range and the spec + property names + +5. PR + - One PR per file initially (meta-agent collapses to bundle PRs + when the review queue stays clean) + - PR description: link to spec, link to reference source line range, + parity-test results + - Ledger row in `RUST_TRANSCODE_LEDGER.md` (or equivalent) IN THE + SAME COMMIT as your final code commit, not separately +``` + +## After your PR opens + +Meta will code-review (Phase 4 of the sprint). Findings come back as: + +- **P0 findings:** Iron Rule violation, compile/test fail, behavior + divergence, file-ownership violation, missing parity test, missing + ledger row. **Push the fix in the same PR.** Meta merges after re-review. +- **P1 findings:** Style, missing edge-case test, perf smell without + profile, doc gap. Meta posts these with a question to the user + ("this PR / next PR / next sprint?"). **You do not act on P1 unless + the user tells Meta to roll them into your PR.** Otherwise P1 lands + in `Altlasten.md` / `TECH_DEBT.md` for a future sprint slot. + +Do not argue P0 findings — they cite a specific Iron Rule. If you +believe the rule is wrong: write a request to `META/REQUESTS-FROM-AGENTS.md` +with blocker type `MISSING_INVARIANT`, idle on the file, let Meta +decide. **Do not ship a fix that contradicts the cited rule.** + +## Parity-test contract + +For each ported route handler / function there MUST be a parity test: + +1. Loads `tests/golden/{{bundle_name}}/<scenario>.json` (canonical + request + expected response, captured against the running reference + implementation on a golden DB / fixture) +2. Boots a test instance with the same golden DB / fixture attached +3. Issues the captured request +4. Asserts: status code matches, response body matches modulo + timestamps and IDs, side effects on the DB / store match (row counts, + created foreign keys) +5. The reference side of the parity is captured once; you do not run + the reference implementation inside the test process + +Tests in `tests/parity_{{bundle_name}}.rs` / `tests/parity_{{bundle_name}}.py` +/ language equivalent. + +## Forbidden patterns — auto-reject + +- New workspace / project dependencies without an RFC +- `unwrap()` / unchecked `.get()` / `as!` outside `#[cfg(test)]` / + test fixtures +- Fire-and-forget background tasks without a JoinHandle / Promise the + caller holds +- Excessive `.clone()` / defensive copying (smell of broken ownership) +- Mutating the authoritative spec or reference source — they are + spec, not target +- Files outside your bundle ownership table +- Refactoring code you don't own "because you're in the area" +- Comments like "TODO: ask the human" — instead write to + `META/REQUESTS-FROM-AGENTS.md` and stop on the file + +## When you are stuck + +Stop. Write to `META/REQUESTS-FROM-AGENTS.md`: + +``` +## Agent {{agent_id}}, file <path>, timestamp <iso> + +### Question +<one paragraph> + +### Tried +<what you tried, what failed> + +### Blocker +<one of: AMBIGUITY | MISSING_INVARIANT | SPEC_SOURCE_MISMATCH | + BEHAVIOUR_QUESTION | EXTERNAL_DEPENDENCY> +``` + +Then idle until Meta updates `META/INVARIANTS.md` or replies in +`META/ANSWERS-TO-AGENTS.md`. **Do not guess.** + +## Done criteria for this bundle + +- All files in your ownership table exist and `<typecheck>` is green +- All parity tests pass: `<tier-1-test> parity_{{bundle_name}}` +- Each route handler returns 200 (or documented-non-200) on a smoke + test against the test instance seeded by `init-data.sql` / + equivalent fixture +- Ledger row for each WT-chunk you produced is in + `RUST_TRANSCODE_LEDGER.md` (or equivalent) with status `PUSHED <sha>` +- No remaining entries in `META/REQUESTS-FROM-AGENTS.md` from your `agent_id` + +## Bundle-specific notes + +{{bundle_notes}} + +--- + +## Proof-of-Read protocol (Lie-Detector LD-2) + +Your output MUST contain, for every file you read: + +``` +Read: file=<path> sha256=<actual-hash> lines=<count> depth=full +``` + +If you used grep / sed / head / tail as primary read, declare +`depth=grep` / `depth=sed-partial` / `depth=head-only` and do NOT +claim `depth=full`. See `.claude/EN/CLAUDE-AGENT-PATTERN.md §3` for +the full Reading-Depth Ladder. + +## Sentinel token (LD-1) + +The orchestrator includes a sentinel token at the end of this brief. +**Begin your first reply with the token verbatim.** If the token is +missing from the brief, ask for it before starting work — never invent +one. diff --git a/.claude/EN/knowledge/a2a-workarounds.md b/.claude/EN/knowledge/a2a-workarounds.md new file mode 100644 index 0000000000..860166b4d5 --- /dev/null +++ b/.claude/EN/knowledge/a2a-workarounds.md @@ -0,0 +1,288 @@ +# A2A Workarounds — Cross-Agent Coordination Without Native Support + +> **READ BY:** all agents, all sessions. +> **Status:** FINDING (2026-04-24). Tested in-session with 6+ concurrent agents. +> **Context:** Claude Code agents are isolated processes. No shared memory, +> no MCP channel between them, no role-switching within a session. +> These workarounds restore coordination using existing primitives. +> +> **Language-agnostic.** Examples reference `lance-graph` (Rust), but +> the patterns transfer to Python / TypeScript / Go without code change — +> the wire format is Markdown via `tee -a`, a POSIX tool with no +> language binding. + +--- + +## The Problem + +Claude Code's `Agent()` tool spawns isolated subprocesses. Each agent: +- Gets a fresh context window (no memory of the conversation) +- Cannot call other agents' tools +- Cannot read other agents' in-flight state +- Returns a single result blob to the main thread + +This breaks three patterns that worked in earlier Claude/Gemini setups: +1. **Role teleportation** — switching persona in-context with zero loss +2. **Mid-flight coordination** — agent A tells agent B what it found +3. **Cross-session handoff** — session A's work feeds session B in real-time + +--- + +## Workaround 1: File Blackboard (`AGENT_LOG.md`) + +**Replaces:** Mid-flight coordination (partially). +**How:** Append-only log file that all agents read before starting +and write to after committing. + +### Setup + +Live at `.claude/board/AGENT_LOG.md` (or `META/AGENT_LOG.md` per +project convention). Permission pre-allowed in `.claude/settings.json`: + +```json +"Bash(tee -a .claude/board/AGENT_LOG.md:*)" +``` + +### Agent prompt template (include in every spawn) + +``` +Before starting work, read `.claude/board/AGENT_LOG.md` to see what +other agents already shipped or found. + +After committing, append your entry: + +tee -a .claude/board/AGENT_LOG.md > /dev/null <<'EOF' + +## YYYY-MM-DDTHH:MM — description (model, branch) + +**D-ids:** ... +**Commit:** `abc1234` +**Tests:** N pass (M new) +**Outcome:** One-line summary. +EOF +``` + +### Limitations + +- Not real-time: agent B only sees what agent A committed, not + what A is currently working on. +- Git staging: if agent A and B both append without committing, + only the last `git add` wins. Mitigation: commit immediately + after append. +- Ordering: entries are appended at bottom (`tee -a`), but convention + is newest-first. Main thread can reorder during board-hygiene. + +--- + +## Workaround 2: Branch Pub/Sub (`subscribe_pr_activity`) + +**Replaces:** Cross-session handoff. +**How:** Open a coordination PR. Both sessions subscribe. Push events +arrive as `<github-webhook-activity>` tags. + +### Setup + +```bash +# Session A (creates the bus): +git checkout -b claude/blackboard +echo "# Coordination Blackboard" > .claude/board/AGENT_LOG.md +git add .claude/board/AGENT_LOG.md +git commit -m "init coordination blackboard" +git push -u origin claude/blackboard +# Open PR: +mcp__github__create_pull_request( + owner="...", repo="...", + title="A2A coordination blackboard", + head="claude/blackboard", base="main", + body="Cross-session pub/sub bus. Do not merge.", + draft=true +) +# Subscribe: +mcp__github__subscribe_pr_activity(owner="...", repo="...", pullNumber=NNN) + +# Session B (joins): +mcp__github__subscribe_pr_activity(owner="...", repo="...", pullNumber=NNN) +git fetch origin claude/blackboard +git checkout claude/blackboard +# Read AGENT_LOG.md → see what session A did +``` + +### Coordination loop + +``` +Session A: Session B: + [does work] + tee -a AGENT_LOG.md > /dev/null <<'EOF' + ...entry... + EOF + git add && git commit && git push + ← <github-webhook-activity> push event + git pull origin claude/blackboard + cat AGENT_LOG.md # read A's entry + [builds on A's findings] + tee -a AGENT_LOG.md > /dev/null <<'EOF' + ...entry... + EOF + git add && git commit && git push + ← <github-webhook-activity> push event + git pull + # reads B's entry, continues +``` + +### Why it works + +- `subscribe_pr_activity` is already in the MCP toolkit — zero infra. +- GitHub webhooks fire on any push, regardless of content. +- Append-only files merge cleanly (no conflict on concurrent appends + if entries are at different positions). +- The draft PR never merges — it's the bus, not a deliverable. + +### Limitations + +- GitHub webhook latency: seconds to low minutes. +- Rate limits: GitHub API limits apply (5000/hour authenticated). +- Requires network: doesn't work offline. +- PR must stay open: closing it kills the subscription. + +--- + +## Workaround 3: Role Teleportation via Agent Cards + +**Replaces:** In-context role switching. +**How:** Load an agent card's knowledge docs, adopt its perspective, +do the work — all on the main thread. No subprocess spawned. + +### When to use + +- The task requires seeing the FULL conversation context (not a summary). +- The task is accumulation (multi-source synthesis), not grindwork. +- The role switch is temporary (do 10 minutes of codec work, then + switch back to architecture). + +### How + +``` +# On the main thread, not via Agent(): +1. Read `.claude/agents/<role-card>.md` +2. Load its Tier-1 knowledge docs +3. Do the work with full session context intact +4. When done, switch: read `.claude/agents/<other-role>.md` +5. Review the work from the other role's perspective +6. Back to main thread — nothing lost +``` + +### When NOT to use + +- The task is mechanical grindwork → spawn a Sonnet agent instead. +- The task is truly independent → parallel Agent() spawns are faster. +- The task is long-running and would block the main thread → + background Agent() is better. + +### Limitations + +- Main thread is single-threaded: no parallelism. +- Context window fills: role-switching adds knowledge doc content + to the conversation, consuming context budget. +- No isolation: mistakes made "as role-X" are visible to the + role-Y review (actually a feature, not a bug). + +--- + +## Workaround 4: Structured Handover Files + +**Replaces:** Session-to-session context transfer. +**How:** Write a structured handover file that the next session +reads at startup via the SessionStart hook. + +### Format + +```markdown +# Handover — YYYY-MM-DD-HHMM — <from-session> to <next-session> + +## What I did +- [bullet list of completed work with commit hashes] + +## FINDING +- [verified facts that the next session can rely on] + +## CONJECTURE +- [unverified ideas that need probing] + +## Blockers +- [things I couldn't resolve] + +## Open questions +- [decisions the next session should make] +``` + +### Where + +`.claude/handovers/YYYY-MM-DD-HHMM-<topic>.md` + +The SessionStart hook (`.claude/hooks/session-start.sh`) can be +extended to cat the latest handover file into the session context. + +--- + +## Decision Matrix + +| Need | Workaround | Cost | +|---|---|---| +| Agent A's findings feed agent B (same session) | File Blackboard (#1) | Low: tee -a + git add | +| Session A's work feeds session B (real-time) | Branch Pub/Sub (#2) | Medium: PR + subscribe | +| Full-context role switch (no loss) | Teleportation (#3) | Zero: just read the card | +| Session-to-session knowledge transfer | Handover Files (#4) | Low: write once, read at startup | +| Parallel independent grindwork | Standard Agent() spawns | Low: fire and forget | +| Multi-source synthesis needing judgment | Teleportation (#3) on Opus main thread | Zero | + +--- + +## Relation to Runtime A2A (Layer 1) + +These workarounds mirror runtime-Blackboard structures (e.g. +`lance_graph_contract::a2a_blackboard`): + +| Runtime (Layer 1) | Session (Layer 2 workaround) | +|---|---| +| `Blackboard.entries` | `AGENT_LOG.md` entries | +| `BlackboardEntry.expert_id` | Agent description + model | +| `BlackboardEntry.capability` | D-ids | +| `BlackboardEntry.result` | Commit hash + outcome | +| `BlackboardEntry.confidence` | Test pass count | +| `Blackboard.round` | Git commit sequence | +| Experts read prior rounds | Agents read prior log entries | + +The structural isomorphism is intentional: the same coordination +pattern works on both layers because the problem is the same — +independent experts composing results on a shared substrate. + +--- + +## Future: Native A2A MCP Server + +If Claude Code or a third party ships an A2A MCP server with +`post_entry` / `read_entries` / `subscribe` endpoints, these +workarounds can be replaced. Contract types already exist +(`BlackboardEntry`, `ExpertCapability`, `Blackboard`). The MCP +server is a thin serde layer over them. + +Until then: `tee -a AGENT_LOG.md > /dev/null <<'EOF'`. + +--- + +## Convergence note: Multi-file board ↔ A2A workarounds + +Both systems implement the same kanban with the same wire format +(`tee -a` Markdown append). The difference is field granularity +and extensions: + +| Multi-file board has MORE | A2A has MORE | +|---|---| +| `META/REQUESTS-FROM-AGENTS.md` inbox with BEHAVIOUR_QUESTION types | Branch pub/sub via `subscribe_pr_activity` (real-time) | +| P0/P1 severity classification (P2 does not exist) | Role-teleportation decision matrix | +| Anti-skim primitives (sentinel / proof-of-read / reading-depth) | Permission-whitelist snippet for `tee -a` | +| Meta-agent drift detection with 10 drift signals | Decision matrix: when Agent() vs. Teleport vs. Pub/Sub | + +→ **Full convergence** is the combination of both toolsets. Multi-file +board + Workaround #2 (Pub/Sub) + #3 (Teleportation) + Decision Matrix +yields the combined A2A toolbox for multi-sprint setups. diff --git a/.claude/EN/knowledge/autoattended-multi-agent-pattern.md b/.claude/EN/knowledge/autoattended-multi-agent-pattern.md new file mode 100644 index 0000000000..699487d12b --- /dev/null +++ b/.claude/EN/knowledge/autoattended-multi-agent-pattern.md @@ -0,0 +1,316 @@ +# Autoattended Multi-Agent Pattern (project-agnostic) + +## Baseline (compressed) + +``` +12 agents + 1 coordinator · autoattended · full authorization · auto-resolve · log all issues + +plan/preflight → review → correct → sprint → review code → fix P0 → commit → repeat +``` + +--- + +> **Reference implementation:** `AdaWorldAPI/WoA` `.claude/v0.1/CLAUDE-CONTEXT.md`. +> That document is project-specific (Flask/MySQL/gunicorn deploy). This +> file distills the **transferable patterns** so any project — Rust, +> Python, TypeScript, Go — can adopt them. Domain-specific bits +> (toolchain, paths, conventions) appear as **adapter sections** at the +> end; the core loop is universal. + +--- + +## 0. When to use this pattern + +Adopt this when **all of the following** are true: + +- A sprint has **≥6 independent work-slices** that can run in parallel +- Each slice can be specified clearly enough that a sub-agent can execute it without you mid-stream +- The cost of a meta-review pass is less than the cost of one rolled-back PR +- The work-tree can be partitioned so slices don't fight over the same files (or you can enforce worktree-isolation per agent) + +If a sprint has ≤3 slices: just do it serially. If slices share files: serialize or pre-bake the shared scaffolding before spawning workers. + +--- + +## 1. The orchestrator loop + +The pattern is six steps. Everything else is detail. + +``` +1. Plan → orchestrator plans the N-way slice partition +2. Sprint → N parallel worker agents (1 sprint = N clusters end-to-end) +3. Meta review → 1+ honest-reviewer agents — bug hunt, NOT status report +4. Fix P0s → orchestrator applies fixes + verifies (compile/test/lint) +5. Commit + PR → orchestrator (one PR per slice OR one combined PR per batch) +6. Repeat → orchestrator plans the next sprint +``` + +**Iron rule:** the orchestrator is the only one allowed to write outside their assigned slice. Workers stay in their lane. Meta reviewers are read-only (they file findings; the orchestrator applies fixes). + +--- + +## 2. Sprint sizing — the magic numbers + +Empirically the sweet spot is **12 worker agents per batch** for codebases the size of a small monolith (~5-20 KLOC of changes per sprint). Smaller batches lose parallelism; larger batches exceed the meta-reviewer's attention and lose the "honest" property. + +| Batch size | Worker model | Use when | +|---|---|---| +| 3-6 | Sonnet | Tight scope, well-defined slices, low risk of cross-cutting concerns | +| 7-12 | Sonnet | The standard sprint shape — most work fits here | +| >12 | Sonnet, split | Pre-fan into 2 batches with a meta-pass between them | +| 1-2 (planning) | Opus | Architecture / design / cross-cutting decisions | +| 1 (meta-review) | Sonnet or Opus | Per the 4-savant taxonomy below | + +A 12-agent wave costs roughly **250-350k input tokens** (slice briefings + reference reads) plus **30-50k for meta**. Weigh against serial-time before dispatching. + +--- + +## 3. The 4-savant scope partition (meta-reviewer taxonomy) + +The single biggest meta-quality lever: **partition meta scope so no command is owned twice**. The four savants come from progressive refinement of "what kind of mistake is each one looking for?". + +| Savant | When it runs | What it looks for | Owns commands | +|---|---|---|---| +| **PP-13 brutally-honest-tester** | POST-IMPL (after workers commit) | within-crate / within-module post-impl gates — does the actual code compile, lint clean, pass tests, satisfy the spec? | full canonical toolchain tier-1+2+3 (see §4 below) | +| **PP-14 convergence-architect** | PRE-PLAN (before partition) | divergent ideation, latent shared infrastructure, cross-slice synergies that no single worker sees | NO compile/test gates; surface-inspection only (e.g. `cargo doc`, `cargo tree`, `cargo expand`) + WebSearch / paper-search for cross-pollination | +| **PP-15 baton-handoff-auditor** | DURING-IMPL (after each slice lands) | cross-boundary contracts — does slice A's output match slice B's input expectation? DTO mismatches, missing handoffs, naming drift, type-state batons | cross-boundary commands: workspace-level cargo (`cargo check --workspace`, `cargo tree --workspace`, `cargo metadata`), public-API diff, cross-repo `git log`, cross-symbol grep | +| **PP-16 preflight-drift-auditor** | PRE-SPAWN (after plan, before worker fan-out) | spec-vs-code drift, hand-waved scope, dropped requirements, old symbols still referenced in plans/specs | git + grep only — `git log master`, `git show`, `git diff main...HEAD`, list-pull-requests, grep old symbols across `.claude/plans/` + `.claude/specs/` | + +**Hand-off discipline:** each savant's prompt has an explicit "non-use → route to PP-X" line. A finding that crosses scope gets handed cleanly to the owner instead of duplicated. PP-13 owns `cargo mutants` + `cargo tarpaulin` because those are post-impl gates; PP-14 doesn't because they're not ideation tools. + +### Field-tested P0 finds from this pattern (anonymized) + +- **Wave L** — closure-attribute import forgotten in the consolidation pass. Caught by PP-15 (cross-boundary symbol grep). Would have crashed the invoice flow. +- **Wave M** — `before_request` middleware imported but never registered. 3 security guards silently disabled. Caught by PP-13 (smoke-test against actual app boot). +- **Wave O** — Jinja autoescape on a render site: HTML logo string came out as `<img>` text. Caught by PP-13 (template render assertion). +- **Sprint 12 PR #18** — `RequireSuperAdmin` chained through a loader that rejected `tenant_id IS NULL`, blocking the very users the route was for. Caught by PP-15 (cross-extractor flow inspection). + +**Iron rule:** P0 fixes land BEFORE the next sprint PR opens. Never let a "merged" state race against your fix-push. + +--- + +## 4. The canonical toolchain (Rust example — adapt per language) + +For **PP-13** post-impl gates. Three tiers; tier 1 runs every PR, tiers 2+3 opt-in. + +### Tier 1 — every PR + +| Command | Purpose | +|---|---| +| `cargo clippy --all-targets --all-features -- -D warnings -D clippy::pedantic -D clippy::nursery` | ~600 lints, strict tier | +| `cargo fmt --check` | rustfmt format gate | +| `cargo audit` | RustSec advisory scan | +| `cargo deny check` | license + dep + advisory + bans (the closest single-binary "ruff-ish" multi-axis check) | + +### Tier 2 — quality / maintenance + +| Command | Purpose | +|---|---| +| `cargo machete` | unused-dep detector | +| `cargo geiger` | unsafe scan (project-rule: every `unsafe` needs `// SAFETY:`) | +| `cargo semver-checks check-release` | public-API SemVer compat | +| `cargo spellcheck` | comments + docs | +| `cargo public-api` | surface diff (catches silent API drift) | + +### Tier 3 — heavier / opt-in (all stable) + +| Command | Purpose | +|---|---| +| `kani` | bounded model checker on stable (`#[kani::proof]` harnesses) | +| `loom` | concurrency model checker (lib, not CLI) | +| `cargo mutants` | mutation testing | +| `cargo-tarpaulin` | coverage | + +### Adapter for other languages + +| Language | Tier-1 equivalents | +|---|---| +| Rust | `cargo clippy ... -D warnings`, `cargo fmt --check`, `cargo audit`, `cargo deny check` | +| Python | `ruff check`, `ruff format --check`, `pip-audit`, `mypy --strict` | +| TypeScript | `eslint --max-warnings 0`, `prettier --check`, `npm audit --omit=dev`, `tsc --noEmit --strict` | +| Go | `golangci-lint run`, `gofmt -l`, `govulncheck ./...`, `go vet -all` | +| Generic | `<lint> --no-warnings`, `<formatter> --check`, `<advisory-scan>`, `<dep-policy>` | + +The tier-1 invariant is the same in every language: **every PR proves it satisfies a no-warning gate before the orchestrator opens it**. + +--- + +## 5. Worker-agent iron rules + +These apply regardless of language. Hard-won from field-tested P0s. + +### Rule 1 — unique-file write discipline + +Each agent writes to a **unique new file** in its slice. Never two agents at the same existing file simultaneously. Append-conflicts are guaranteed if you violate this. + +When multiple slices must touch the same merge-zone (e.g. a top-level routes registry), declare it explicitly as **append-only**: each agent appends one line, the orchestrator resolves order in a final consolidation commit. Never let workers `git push` to a shared zone simultaneously. + +### Rule 2 — worktree branches start from `origin/main`, not the working branch + +A worktree branch started from a stale local working-branch will silently miss commits from sibling agents and overwrite them. Always: + +```bash +git fetch origin <main-or-integration-branch> +git checkout -b agent/N <fresh-origin-ref> +``` + +If your harness supports `isolation: "worktree"`, use it — each agent gets its own clean tree. + +### Rule 3 — atomic consolidation pass + +After all workers return, the orchestrator does **one** consolidation commit that: +1. Pulls in each slice's commits in dependency order +2. Resolves the append-only merge zones (`src/routes/mod.rs`, `app.py` imports, etc.) +3. Re-runs tier-1 gates against the combined state +4. Files one PR (or N PRs if the slices are truly independent) + +Never 12 mini-commits at the shared registry file. That's the path to "I merged your changes and now nothing builds". + +### Rule 4 — pre-wave check (helper-call audit) + +Before fanning out, scan each cluster for **uncommitted dependencies**: + +```bash +# Adapt the regex to your language +grep -oE '[a-z_][a-z0-9_]+\(' <cluster-files> | sort -u | \ + comm -23 - <(grep -oE 'pub fn [a-z_][a-z0-9_]+' <imported-modules>/*.rs | sed 's/pub fn //;s/$/(/') +``` + +Clusters with non-zero unresolved-helper count need a **helper-hoist** committed before workers dispatch. Otherwise N workers each invent the same helper N different ways. (See PP-14 convergence-architect's job description.) + +### Rule 5 — chunking discipline + +Files >150 lines: write in chunks via `tee -a`, commit per chunk, push per chunk. NEVER one 500-line heredoc + commit + push in the same turn. The wrapping connection drops mid-serialize and you lose work. This is the load-bearing iron rule of the entire pattern. + +### Rule 6 — quoting source in commit messages + +For ports / behavior-preserving rewrites: every commit message quotes the source file + line range for the function being ported. Reviewers can `grep -rn "Source: " src/` to find every port. Without this, behavior-drift creeps in invisibly. + +--- + +## 6. Memory-files pattern (the autopilot foundation) + +The orchestrator and every worker share **three persistent context files** the harness loads at session start. Project-agnostic shapes: + +| File | Mutability | Purpose | +|---|---|---| +| `CONTEXT.md` (or `CLAUDE.md`) | rare (architectural decisions only) | what the project is, hard rules, stack decisions, glossary | +| `JOURNAL.md` (append-only) | per-session append | dated lessons (`L<N>:` format). Never edit past entries; only append | +| `TODO.md` | hand-curated only | what's actively prioritized (the human owns this; agents read but don't write) | + +**Critical rule:** a compaction summary is **not** a substitute for reading these files. Field-tested: 2h lost on a bug whose diagnosis pattern was already in JOURNAL L40. Every session: `cat` the full files, never `grep`/`head`/`tail`. The first 30 seconds of context-loading saves the next 2 hours. + +### Multi-file board pattern (optional extension) + +For projects that outgrow a single JOURNAL: introduce a `board/` directory with 6-8 themed files: + +- `Stand.md` / `STATUS_BOARD.md` — current snapshot, "what's running right now". **The only mutable board file.** Regenerated per session. +- `Übersicht.md` / `Index.md` — top-level navigation index +- `Goldstaub.md` / `EPIPHANIES.md` — append-only lessons mirror (cross-cut view of the JOURNAL) +- `Altlasten.md` / `TECH_DEBT.md` — known debts, with PR-link per line +- `Architektur_Vereinfachung.md` + `_Erledigt.md` — planned simplifications + done-mirror (plan shrinks, done grows) +- `Integrationsplan.md` / `INTEGRATION_PLANS.md` — forward-looking sequencing of upcoming waves +- `Ideen.md` / `IDEAS.md` — captured-but-deferred ideas (Iron rule: improvement seen → here, NOT into the code) + +The shape doesn't matter as much as the **single-mutable-file invariant**: agents know exactly one place to update status; everywhere else is append-only or human-edited. + +--- + +## 7. Reference indexes — the autopilot data layer + +For repos large enough that "read the source" is too expensive per worker: pre-generate JSON/MD indexes that workers consult instead of re-grepping. + +| Index | Content | +|---|---| +| `routing_table.json` | every route with body, helpers used, models touched, templates rendered. Reach this BEFORE the source — it's faster + complete | +| `program_structure.json` | module → class → method tree with body LOC counts | +| `classes_index.json` / `functions_index.json` / `modules_index.json` | symbol lookup | +| `transcode_phase_plan.json` | per-route, transcode-readiness score (1A_trivial / 1B_simple / 2_moderate / 3_needs_SoC) | +| `tenant_scope_audit.md` (or equivalent security audit) | 0-leak proof: every route classified against the security invariant | +| `dead_route_audit.md` | JS-fetch-aware dead-code analysis (catches `fetch('/path')` in templates that static analyzers miss) | + +**Iron rule:** re-harvest after every wave that changes the routing/symbol surface. Stale indexes are worse than no indexes — the autopilot trusts them blindly. + +### Harvest tools + +Keep harvest scripts under `.<harness>/v<N>/tools/`. Three core ones: + +- `harvest_routes.py` (or equivalent) — walks `@route` / `@app.get` / Router::route() and dumps the JSON +- `harvest_program_structure.py` — AST walk producing class/function/module trees +- `harvest_deps.py` — call-graph + import-graph for cycle detection + +Anything else (dead-route audit, tenant-scope audit, transcode-phase classifier) builds on these three. + +--- + +## 8. The bare-`url_for` / cross-module-reference trap + +A worth-its-own-section gotcha that bit production: + +After moving a function into a new module/blueprint, **callers that referenced it by bare name break silently**. Flask's `url_for('login')` resolves to the current blueprint context; if `login` moved to `misc_clean_c.login` and the caller is in `system_pages.index`, you get a `BuildError` on every render. + +Equivalent traps in other languages: +- Python imports: `from .login import login_view` → after a refactor, `login_view` lives elsewhere; the import succeeds (re-exported) but the symbol's behavior changed +- TypeScript: barrel-export `index.ts` resolves the wrong symbol after rename +- Rust: `use crate::login::*` glob-imports survive a function move but stop importing what you expected + +**Counter-pattern:** post-move, search every callsite: + +```bash +# Pick the right grep regex per language +grep -rE "url_for\(['\"]<func>['\"]" templates/ src/ +grep -rE "\b<func>\(" src/ +``` + +Every cross-module callsite needs requalification. Smoke-test every template/page that calls the moved function. + +--- + +## 9. Adoption checklist for a new project + +To bootstrap this pattern in a new repo: + +1. **Memory files** — create `CONTEXT.md` (or `CLAUDE.md`), seed JOURNAL.md, TODO.md +2. **Iron rules** — write your 6-8 hard rules into CONTEXT.md (chunking, behavior-preservation, etc.) +3. **Toolchain tiers** — pick your tier-1 commands per §4; document which run per PR vs opt-in +4. **Append-only zones** — identify the 2-4 shared files agents will touch (module registry, app router, integration test list) and declare them in CONTEXT.md +5. **Harvest tools** — write routes/symbol harvesters once; cache outputs under `.claude/reference/` or `.<harness>/reference/` +6. **Savant prompts** — draft the 4 meta-reviewer system prompts (PP-13/14/15/16 templates) with the explicit "non-use → route to PP-X" lines +7. **First small sprint** — try the pattern on a 3-slice sprint first; measure orchestrator overhead, then scale to 12 + +The first wave often costs more (orchestrator learning + harvest tooling). By wave 3 the throughput multiplier is 6-8x serial. + +--- + +## 10. What this pattern is NOT + +- Not a substitute for **a real spec.** Workers need slice briefings tight enough that they don't have to ask questions mid-stream. The orchestrator's plan is where the engineering happens. +- Not a substitute for **code review.** Meta-reviewers catch ~80-90% of P0s but the 10-20% they miss are the ones a human PR-reviewer catches. Keep both. +- Not appropriate for **prototyping.** When the spec is fluid, parallel agents diverge. Use this for ports, refactors, mechanical fan-out — not for exploration. +- Not "free." Token cost is real (~300k per 12-agent wave). Worth it for week-scale work, not for hour-scale work. + +--- + +## 11. Field-tested session evidence + +The pattern as documented here produced, in a single session against `AdaWorldAPI/woa-rs`: + +- 18 merged PRs (#1–#18) +- 4 phase batches (entities, parity tests, deploy, route slices) +- 15 route slices shipped end-to-end (Phase 1.B Batches 1+2) +- 0 P0 bugs landed to main (codex bot + PP-13/14/15/16 caught all 5 attempted) +- 1 quota-induced partial-batch failure (B6/B10/B11) recovered via redispatch with tighter scope + +The redispatch story is itself a meta-lesson: **scope-tightening on retry** beats "try harder" on the original scope. When a worker hits a budget cap, the answer is half the slice and a stricter brief — not the same slice with a "be faster" note. + +--- + +## 12. Cross-references + +- Reference implementation: `AdaWorldAPI/WoA` `.claude/v0.1/CLAUDE-CONTEXT.md` +- Multi-file board pattern source: `AdaWorldAPI/WoA` `.claude/board/` +- Harvest tool examples: `AdaWorldAPI/WoA` `.claude/v0.2/tools/` +- Reference index examples: `AdaWorldAPI/WoA` `.claude/reference/` +- woa-rs Phase 1.B v2 sprint plan: `AdaWorldAPI/woa-rs` `.claude/v0.2/SPRINT-PHASE1B-PLAN-v2.md` — concrete worked example of the 12-slice partition with savant findings folded in +- Most mature 4-savant ensemble: `AdaWorldAPI/lance-graph` `.claude/agents/` +- Companion: A2A coordination workarounds — `.claude/EN/knowledge/a2a-workarounds.md` diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000000..7affa2315a --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,166 @@ +{ + "permissions": { + "allow": [ + "mcp__github__*", + "Read(**)", + "Write(**)", + "Edit(**)", + "MultiEdit(**)", + "NotebookEdit(**)", + "Bash(git *)", + "Bash(ls *)", + "Bash(ls)", + "Bash(find *)", + "Bash(grep *)", + "Bash(rg *)", + "Bash(wc *)", + "Bash(sort *)", + "Bash(uniq *)", + "Bash(head *)", + "Bash(tail *)", + "Bash(cat *)", + "Bash(cat **)", + "Bash(diff *)", + "Bash(sed *)", + "Bash(awk *)", + "Bash(cut *)", + "Bash(tr *)", + "Bash(echo *)", + "Bash(printf *)", + "Bash(mkdir *)", + "Bash(rmdir *)", + "Bash(touch *)", + "Bash(cp *)", + "Bash(mv *)", + "Bash(ln *)", + "Bash(tar *)", + "Bash(gzip *)", + "Bash(gunzip *)", + "Bash(unzip *)", + "Bash(zip *)", + "Bash(curl *)", + "Bash(wget *)", + "Bash(jq *)", + "Bash(yq *)", + "Bash(date *)", + "Bash(pwd)", + "Bash(env)", + "Bash(which *)", + "Bash(file *)", + "Bash(stat *)", + "Bash(du *)", + "Bash(df *)", + "Bash(test *)", + "Bash(true)", + "Bash(false)", + "Bash(rm /tmp/*)", + "Bash(rm -rf /tmp/*)", + "Bash(rm -rf .venv*)", + "Bash(rm -rf **/__pycache__)", + "Bash(rm -rf .pytest_cache)", + "Bash(rm -rf .ruff_cache)", + "Bash(rm -rf .mypy_cache)", + "Bash(rm -rf target)", + "Bash(rm -rf target/*)", + "Bash(rm -rf **/target)", + "Bash(rm -rf node_modules)", + "Bash(rm -rf **/node_modules)", + "Bash(tee *)", + "Bash(tee **)", + "Bash(tee -a *)", + "Bash(tee -a **)", + "Bash(python *)", + "Bash(python **)", + "Bash(python3 *)", + "Bash(python3 **)", + "Bash(python3.12 *)", + "Bash(pip *)", + "Bash(pip-audit *)", + "Bash(pipdeptree *)", + "Bash(./venv/bin/*)", + "Bash(.venv/bin/*)", + "Bash(/tmp/.venv*/bin/*)", + "Bash(ruff *)", + "Bash(mypy *)", + "Bash(pytest *)", + "Bash(bandit *)", + "Bash(black *)", + "Bash(isort *)", + "Bash(deptry *)", + "Bash(griffe *)", + "Bash(cargo *)", + "Bash(rustup *)", + "Bash(rustc *)", + "Bash(rustfmt *)", + "Bash(clippy-driver *)", + "Bash(rust-analyzer *)", + "Bash(cargo-clippy *)", + "Bash(cargo-fmt *)", + "Bash(cargo-audit *)", + "Bash(cargo-deny *)", + "Bash(cargo-machete *)", + "Bash(cargo-geiger *)", + "Bash(cargo-public-api *)", + "Bash(cargo-semver-checks *)", + "Bash(cargo-nextest *)", + "Bash(cargo-tarpaulin *)", + "Bash(cargo-mutants *)", + "Bash(cargo-spellcheck *)", + "Bash(cargo-outdated *)", + "Bash(cargo-bloat *)", + "Bash(cargo-expand *)", + "Bash(cargo-tree *)", + "Bash(node *)", + "Bash(npm *)", + "Bash(npx *)", + "Bash(pnpm *)", + "Bash(yarn *)", + "Bash(eslint *)", + "Bash(prettier *)", + "Bash(tsc *)", + "Bash(vitest *)", + "Bash(jest *)", + "Bash(go *)", + "Bash(gofmt *)", + "Bash(golangci-lint *)", + "Bash(govulncheck *)", + "Bash(docker *)", + "Bash(docker-compose *)", + "Bash(make *)", + "Bash(cmake *)", + "Bash(bash *)", + "Bash(sh *)" + ], + "deny": [ + "Bash(git push --force *)", + "Bash(git push -f *)", + "Bash(git push --force-with-lease *)", + "Bash(git reset --hard *)", + "Bash(git clean -f *)", + "Bash(git clean -fd *)", + "Bash(rm -rf /)", + "Bash(rm -rf /*)", + "Bash(rm -rf .git*)", + "Bash(rm -rf .archive*)", + "Bash(rm -rf .claude)", + "Bash(rm -rf src)", + "Bash(rm -rf crates)", + "Bash(rm -rf Cargo.toml)", + "Bash(rm -rf Cargo.lock)", + "Bash(rm -rf models.py)", + "Bash(rm -rf app.py)", + "Bash(rm -rf package.json)", + "Bash(rm -rf pyproject.toml)", + "Bash(rm -rf go.mod)", + "Bash(sudo *)", + "Write(./.archive/**)", + "Edit(./.archive/**)", + "Write(./CLAUDE-CREDENTIALS.md)", + "Edit(./CLAUDE-CREDENTIALS.md)", + "Write(./.master-keys/**)", + "Edit(./.master-keys/**)", + "Write(./.git/**)", + "Edit(./.git/**)" + ] + } +} diff --git a/.github/workflows/binaries.yml b/.github/workflows/binaries.yml index 7973fe221d..5afe7054e4 100644 --- a/.github/workflows/binaries.yml +++ b/.github/workflows/binaries.yml @@ -110,7 +110,7 @@ jobs: # dependency graph into the binary so consumers can recover # the SBOM directly from the binary via `cargo audit bin`. cargo install --locked cargo-auditable --version 0.7.0 - cargo auditable build --target ${target} --release -p tract-cli --locked + cargo auditable build --target ${target} --release -p tract-cli mkdir tract-$name cp target/${target}/release/tract tract-${name} tar czf tract-${name}.tgz tract-${name} diff --git a/.github/workflows/crates.yml b/.github/workflows/crates.yml index fc947d7188..4d34066a8b 100644 --- a/.github/workflows/crates.yml +++ b/.github/workflows/crates.yml @@ -181,11 +181,13 @@ jobs: with: save-if: ${{ github.ref == 'refs/heads/main' }} + # Advisory only (no -D warnings) — the enforced gate lives in lint.yml. - run: rustup component add clippy && cargo clippy --all-targets - name: clippy (bench-suite feature) run: cargo clippy -p tract-cli --features bench-suite - name: test (bench-suite feature) run: cargo test -p tract-cli --features bench-suite --bin tract bench_ + # Enforced fmt check also runs in lint.yml; kept here too for this job's own gating. - name: fmt run: rustup component add rustfmt --toolchain stable && cargo +stable fmt --check - name: Warnings diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000000..631dc0c7bd --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,55 @@ +name: lint + +on: + pull_request: + push: + branches: + - main + +permissions: + contents: read + +concurrency: + group: lint-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + CARGO_INCREMENTAL: false + CARGO_NET_RETRY: 10 + +jobs: + format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + # `rustup show` installs and activates whatever `rust-toolchain.toml` + # pins, so the version lives in exactly ONE place. This is also the + # repo's own idiom — the other workflows drive rustup directly rather + # than through a third-party toolchain action. + - name: Setup rust toolchain (pinned by rust-toolchain.toml) + run: | + rustup show + rustup component add rustfmt + + - run: cargo fmt --all --check + + clippy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Setup rust toolchain (pinned by rust-toolchain.toml) + run: | + rustup show + rustup component add clippy + + - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 + with: + save-if: ${{ github.ref == 'refs/heads/main' }} + + - run: cargo clippy --all-targets -- -D warnings diff --git a/.gitignore b/.gitignore index 5407d98906..40d059d7a2 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,9 @@ proptest-regressions wheelhouse target-bisector* /nvidia + +# Cargo.lock is not committed in this fork: the workspace consumes the +# AdaWorldAPI/ndarray fork by git branch, and a committed lock pins a stale +# revision of it (measured: the lock held ndarray e55af0e3 while the branch +# was at f43338fb, so builds silently used a two-merge-old dependency). +Cargo.lock diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index f4356c515d..0000000000 --- a/Cargo.lock +++ /dev/null @@ -1,6490 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if 1.0.4", - "getrandom 0.3.4", - "once_cell", - "serde", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" -dependencies = [ - "memchr", -] - -[[package]] -name = "aligned" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" -dependencies = [ - "as-slice", -] - -[[package]] -name = "aligned-vec" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" -dependencies = [ - "equator", -] - -[[package]] -name = "alloca" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" -dependencies = [ - "cc", -] - -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "alsa" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812947049edcd670a82cd5c73c3661d2e58468577ba8489de58e1a73c04cbd5d" -dependencies = [ - "alsa-sys", - "bitflags 2.13.2", - "cfg-if 1.0.4", - "libc", -] - -[[package]] -name = "alsa-sys" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad7569085a265dd3f607ebecce7458eaab2132a84393534c95b18dcbc3f31e04" -dependencies = [ - "libc", - "pkg-config", -] - -[[package]] -name = "android_system_properties" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" -dependencies = [ - "libc", -] - -[[package]] -name = "anes" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" - -[[package]] -name = "anstream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" - -[[package]] -name = "anstyle-parse" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.2", -] - -[[package]] -name = "anyhow" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" - -[[package]] -name = "anymap3" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5dfbc6d8d2675589ccbe4d0fd61df2419075625f8c1a62325e718e2b0049f9" - -[[package]] -name = "approx" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" -dependencies = [ - "num-traits", -] - -[[package]] -name = "arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" - -[[package]] -name = "arg_enum_proc_macro" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "arrayvec" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" - -[[package]] -name = "as-slice" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" -dependencies = [ - "stable_deref_trait", -] - -[[package]] -name = "ash" -version = "0.38.0+1.3.281" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" -dependencies = [ - "libloading 0.8.9", -] - -[[package]] -name = "askama" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6024d73179f43f15ccd2b881bfea6fee7f3a46ec53f33b52210dea749ebebaa4" -dependencies = [ - "askama_macros", - "itoa", - "percent-encoding", - "serde", - "serde_json", -] - -[[package]] -name = "askama_derive" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071ee5ebf2138e3ad180e0aacf6940c2cab5e6d8333741d9925c7bee2b153f39" -dependencies = [ - "askama_parser", - "memchr", - "proc-macro2", - "quote", - "rustc-hash 2.1.3", - "syn 3.0.5", -] - -[[package]] -name = "askama_macros" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643e1c7cbb6aec1d920332fe51a7c0d8219e273dcb8602db03f5263e4d16487b" -dependencies = [ - "askama_derive", -] - -[[package]] -name = "askama_parser" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c5ae75772275d268b03ab8bdccdd12117b6169ee23256942b34e46c9f476583" -dependencies = [ - "rustc-hash 2.1.3", - "unicode-ident", - "winnow", -] - -[[package]] -name = "atomic" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" - -[[package]] -name = "av-scenechange" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" -dependencies = [ - "aligned", - "anyhow", - "arg_enum_proc_macro", - "arrayvec", - "log", - "num-rational", - "num-traits", - "pastey 0.1.1", - "rayon", - "thiserror 2.0.20", - "v_frame", - "y4m", -] - -[[package]] -name = "av1-grain" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" -dependencies = [ - "anyhow", - "arrayvec", - "log", - "nom 8.0.0", - "num-rational", - "v_frame", -] - -[[package]] -name = "avif-serialize" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7178fe5f7d460b13895ebb9dcb28a3a6216d2df2574a0806cb51b555d297f38" -dependencies = [ - "arrayvec", -] - -[[package]] -name = "axum" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" -dependencies = [ - "axum-core", - "bytes", - "form_urlencoded", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "serde_core", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "sync_wrapper", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-macros" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aa268c23bfbbd2c4363b9cd302a4f504fb2a9dfe7e3451d66f35dd392e20aca" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" - -[[package]] -name = "bindgen" -version = "0.65.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" -dependencies = [ - "bitflags 1.3.2", - "cexpr", - "clang-sys", - "lazy_static", - "lazycell", - "log", - "peeking_take_while", - "prettyplease 0.2.37", - "proc-macro2", - "quote", - "regex", - "rustc-hash 1.1.0", - "shlex 1.3.0", - "syn 2.0.119", - "which", -] - -[[package]] -name = "bindgen" -version = "0.73.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "787ef8ef523575546b106a58213d6e6b06198a05c2f757258c68a74273670cfa" -dependencies = [ - "bitflags 2.13.2", - "cexpr", - "clang-sys", - "log", - "prettyplease 0.3.0", - "proc-macro2", - "quote", - "regex", - "rustc-hash 2.1.3", - "shlex 2.0.1", - "syn 3.0.5", -] - -[[package]] -name = "bit-set" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" -dependencies = [ - "bit-vec 0.8.0", -] - -[[package]] -name = "bit-set" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2f926cc3060f09db9ebc5b52823d85268d24bb917e472c0c4bea35780a7d" -dependencies = [ - "bit-vec 0.9.1", -] - -[[package]] -name = "bit-set" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56d87354e4229f54a44f7bf2435906a4656dba36026ab6eaca629a2c436a691c" -dependencies = [ - "bit-vec 0.10.1", -] - -[[package]] -name = "bit-vec" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" - -[[package]] -name = "bit-vec" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" - -[[package]] -name = "bit-vec" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5727b15fa97d4f4fee0a3b7c3d550ed0269f54329207b86388de918604e31269" -dependencies = [ - "borsh", - "serde", -] - -[[package]] -name = "bit_field" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" -dependencies = [ - "serde_core", -] - -[[package]] -name = "bitstream-io" -version = "4.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f" -dependencies = [ - "no_std_io2", -] - -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - -[[package]] -name = "block2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" -dependencies = [ - "objc2", -] - -[[package]] -name = "boow" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4505c91a2ef58b8ff2b8c579cd580e4f94949828b4f9a888666cecf08d4124" -dependencies = [ - "cfg-if 0.1.10", -] - -[[package]] -name = "borsh" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a" -dependencies = [ - "borsh-derive", - "bytes", - "cfg_aliases", -] - -[[package]] -name = "borsh-derive" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cdfe656708a01f89b451a7d36466e6fe6c414de0aa18fc54f864f6f9ca9f56" -dependencies = [ - "once_cell", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "box_drawing" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea27d8d5fd867b17523bf6788b1175fa9867f34669d057e9adaf76e27bcea44b" - -[[package]] -name = "built" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9" - -[[package]] -name = "bumpalo" -version = "3.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" - -[[package]] -name = "bytemuck" -version = "1.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" -dependencies = [ - "bytemuck_derive", -] - -[[package]] -name = "bytemuck_derive" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "byteorder-lite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" - -[[package]] -name = "bytes" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" - -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - -[[package]] -name = "castaway" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" -dependencies = [ - "rustversion", -] - -[[package]] -name = "causal_llm" -version = "0.1.0" -dependencies = [ - "anyhow", - "axum", - "axum-macros", - "clap", - "criterion", - "env_logger", - "float-ord", - "log", - "ndarray", - "rand 0.10.3", - "reqwest", - "serde", - "serde_json", - "tokenizers", - "tokio", - "tokio-scoped", - "tract", -] - -[[package]] -name = "cc" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86e055fa6e3e40ad6187ba25b6855eeb635d5271f8e0f0e1e9a5826b8d75bea0" -dependencies = [ - "find-msvc-tools", - "jobserver", - "libc", - "shlex 2.0.1", -] - -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom 7.1.3", -] - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" - -[[package]] -name = "chacha20" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" -dependencies = [ - "cfg-if 1.0.4", - "cpufeatures", - "rand_core 0.10.1", -] - -[[package]] -name = "ciborium" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" - -[[package]] -name = "ciborium-ll" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" -dependencies = [ - "ciborium-io", - "half", -] - -[[package]] -name = "clang-sys" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" -dependencies = [ - "glob", - "libc", - "libloading 0.8.9", -] - -[[package]] -name = "clap" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "clap_lex" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" - -[[package]] -name = "codespan-reporting" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" -dependencies = [ - "serde", - "termcolor", - "unicode-width", -] - -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - -[[package]] -name = "colorchoice" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" - -[[package]] -name = "colorous" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4e18bf7a165bf7028fde98609a0f1e8f7498d762a212598e6c891f6893556ec" - -[[package]] -name = "combine" -version = "4.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "compact_str" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab" -dependencies = [ - "castaway", - "cfg-if 1.0.4", - "itoa", - "rustversion", - "ryu", - "serde", - "static_assertions", -] - -[[package]] -name = "console" -version = "0.16.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96a4956774c13c126a8b5af4daa79384f4d826534c95a02d76afb39e2ab64e3" -dependencies = [ - "encode_unicode", - "libc", - "unicode-width", - "windows-sys 0.61.2", -] - -[[package]] -name = "console_error_panic_hook" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -dependencies = [ - "cfg-if 1.0.4", - "wasm-bindgen", -] - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "core-graphics-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" -dependencies = [ - "bitflags 2.13.2", - "core-foundation", - "libc", -] - -[[package]] -name = "core_affinity" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a034b3a7b624016c6e13f5df875747cc25f884156aad2abd12b6c46797971342" -dependencies = [ - "libc", - "num_cpus", - "winapi", -] - -[[package]] -name = "core_detect" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" - -[[package]] -name = "coreaudio-rs" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5d7dca3ebcf65a035582c9ad4385371a9d9ee6537474d2a278f4e1e475bb58" -dependencies = [ - "bitflags 2.13.2", - "libc", - "objc2-audio-toolbox", - "objc2-core-audio", - "objc2-core-audio-types", - "objc2-core-foundation", -] - -[[package]] -name = "cpal" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f02e8d0327b42d3e2e4ab2119af397344eb9fc54a34bf0ddeaa1277af8681f1" -dependencies = [ - "alsa", - "block2", - "coreaudio-rs", - "dasp_sample", - "jni", - "js-sys", - "libc", - "mach2", - "ndk", - "ndk-context", - "num-derive", - "num-traits", - "objc2", - "objc2-audio-toolbox", - "objc2-avf-audio", - "objc2-core-audio", - "objc2-core-audio-types", - "objc2-core-foundation", - "objc2-foundation", - "web-sys", - "windows", - "windows-core", -] - -[[package]] -name = "cpufeatures" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" -dependencies = [ - "cfg-if 1.0.4", -] - -[[package]] -name = "criterion" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" -dependencies = [ - "alloca", - "anes", - "cast", - "ciborium", - "clap", - "criterion-plot", - "itertools 0.13.0", - "num-traits", - "oorandom", - "page_size", - "plotters", - "rayon", - "regex", - "serde", - "serde_json", - "tinytemplate", - "walkdir", -] - -[[package]] -name = "criterion-plot" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" -dependencies = [ - "cast", - "itertools 0.13.0", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" - -[[package]] -name = "crunchy" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - -[[package]] -name = "cudarc" -version = "0.19.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7359bf1de037ddada72729c3acec9b040f3468400c6e9e740b01ba0cd2e89562" -dependencies = [ - "half", - "libloading 0.9.0", -] - -[[package]] -name = "curl" -version = "0.4.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a45ee8994e5307cb4c60cfc1c20bf7263ffb771ddc135c9f768a14bcbc15b09" -dependencies = [ - "curl-sys", - "libc", - "openssl-probe 0.1.6", - "openssl-sys", - "schannel", - "socket2", - "windows-sys 0.61.2", -] - -[[package]] -name = "curl-sys" -version = "0.4.90+curl-8.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97799a0d220bfb3361e0fe4936966ff8c4b24d65c3f06dfc70d7b680b44e7897" -dependencies = [ - "cc", - "libc", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", - "windows-sys 0.61.2", -] - -[[package]] -name = "daachorse" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5614204febbc33cc07a2806aa6440b904ac012b68eecc37f4493ea4a76455a3d" - -[[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.119", -] - -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "dary_heap" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1e3a325bc115f096c8b77bbf027a7c2592230e70be2d985be950d3d5e60ebe" -dependencies = [ - "serde", -] - -[[package]] -name = "dasp_sample" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" - -[[package]] -name = "defmt" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" -dependencies = [ - "bitflags 1.3.2", - "defmt-macros", -] - -[[package]] -name = "defmt-macros" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" -dependencies = [ - "defmt-parser", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "defmt-parser" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" -dependencies = [ - "thiserror 2.0.20", -] - -[[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" - -[[package]] -name = "derive-new" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cdc8d50f426189eef89dac62fabfa0abb27d5cc008f25bf4156a0203325becc" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "derive_builder" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "derive_builder_macro" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" -dependencies = [ - "derive_builder_core", - "syn 2.0.119", -] - -[[package]] -name = "dinghy-test" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88289298155f4ca99322d460cf2084c90b41b661ca901a27868a303a5ef97fec" - -[[package]] -name = "dispatch2" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" -dependencies = [ - "bitflags 2.13.2", - "objc2", -] - -[[package]] -name = "displaydoc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "dlib" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" -dependencies = [ - "libloading 0.8.9", -] - -[[package]] -name = "document-features" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" -dependencies = [ - "litrs", -] - -[[package]] -name = "downcast-rs" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" - -[[package]] -name = "dyn-clone" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - -[[package]] -name = "dyn-hash" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fdab65db9274e0168143841eb8f864a0a21f8b1b8d2ba6812bbe6024346e99e" - -[[package]] -name = "either" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" - -[[package]] -name = "encode_unicode" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" - -[[package]] -name = "env_filter" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "jiff", - "log", -] - -[[package]] -name = "equator" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" -dependencies = [ - "equator-macro", -] - -[[package]] -name = "equator-macro" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "erased-serde" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" -dependencies = [ - "serde", - "serde_core", - "typeid", -] - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "esaxx-rs" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d817e038c30374a4bcb22f94d0a8a0e216958d4c3dcde369b1439fec4bdda6e6" -dependencies = [ - "cc", -] - -[[package]] -name = "example-dump-nnef-mobilenet-v2" -version = "0.20.7-pre" -dependencies = [ - "anyhow", - "image", - "ndarray", - "tract", -] - -[[package]] -name = "example-nnef-mobilenet-v2" -version = "0.20.7-pre" -dependencies = [ - "anyhow", - "image", - "ndarray", - "tract", -] - -[[package]] -name = "example-onnx-mobilenet-v2" -version = "0.20.7-pre" -dependencies = [ - "anyhow", - "image", - "ndarray", - "tract", -] - -[[package]] -name = "example-pytorch-resnet" -version = "0.20.7-pre" -dependencies = [ - "anyhow", - "image", - "ndarray", - "tract", -] - -[[package]] -name = "example-tensorflow-mobilenet-v2" -version = "0.20.7-pre" -dependencies = [ - "image", - "tract-tensorflow", -] - -[[package]] -name = "example-tflite-mobilenet-v3" -version = "0.20.7-pre" -dependencies = [ - "image", - "tract-tflite", -] - -[[package]] -name = "exr" -version = "1.74.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711fe42c9964295e01ee3fba3f9fe0e1d24b98886950d68efe81b1c76e21adf3" -dependencies = [ - "bit_field", - "half", - "lebe", - "miniz_oxide 0.8.9", - "num-complex", - "pulp", - "rayon-core", - "smallvec", - "zune-inflate", -] - -[[package]] -name = "face_detection_yolov8onnx_example" -version = "0.1.0" -dependencies = [ - "anyhow", - "clap", - "image", - "ndarray", - "tract", -] - -[[package]] -name = "face_similarity_arcface_onnx" -version = "0.1.0" -dependencies = [ - "anyhow", - "clap", - "image", - "ndarray", - "tract", -] - -[[package]] -name = "fancy-regex" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8" -dependencies = [ - "bit-set 0.8.0", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "fastrand" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" - -[[package]] -name = "fax" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" - -[[package]] -name = "fdeflate" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "figment2" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87d63dee16df12076c7770919713c0b92f4e1c85eac828dc2ade0b6c998f016b" -dependencies = [ - "atomic", - "serde", - "toml_edit", - "uncased", - "version_check", -] - -[[package]] -name = "filetime" -version = "0.2.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" -dependencies = [ - "cfg-if 1.0.4", - "libc", -] - -[[package]] -name = "find-msvc-tools" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48" - -[[package]] -name = "flatbuffers" -version = "25.12.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35f6839d7b3b98adde531effaf34f0c2badc6f4735d26fe74709d8e513a96ef3" -dependencies = [ - "bitflags 2.13.2", - "rustc_version", -] - -[[package]] -name = "flate2" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" -dependencies = [ - "crc32fast", - "miniz_oxide 0.9.1", - "zlib-rs", -] - -[[package]] -name = "float-ord" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "fs-err" -version = "3.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5c95b673b8f6f7235229ae11c5642d81b04c2e64c1e2fb417bc0cf73ca45f29" -dependencies = [ - "autocfg", -] - -[[package]] -name = "fs2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "fs_extra" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - -[[package]] -name = "futures-channel" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" - -[[package]] -name = "futures-io" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" - -[[package]] -name = "futures-sink" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" - -[[package]] -name = "futures-task" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" - -[[package]] -name = "futures-util" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" -dependencies = [ - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if 1.0.4", - "libc", - "wasi", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if 1.0.4", - "js-sys", - "libc", - "r-efi 5.3.0", - "wasip2", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" -dependencies = [ - "cfg-if 1.0.4", - "js-sys", - "libc", - "r-efi 6.0.0", - "rand_core 0.10.1", - "wasm-bindgen", -] - -[[package]] -name = "gif" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" -dependencies = [ - "color_quant", - "weezl", -] - -[[package]] -name = "gl_generator" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" -dependencies = [ - "khronos_api", - "log", - "xml-rs", -] - -[[package]] -name = "glob" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" - -[[package]] -name = "glow" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5" -dependencies = [ - "js-sys", - "slotmap", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "glutin_wgl_sys" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" -dependencies = [ - "gl_generator", -] - -[[package]] -name = "gpu-allocator" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" -dependencies = [ - "ash", - "hashbrown 0.16.1", - "log", - "presser", - "thiserror 2.0.20", - "windows", -] - -[[package]] -name = "half" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" -dependencies = [ - "cfg-if 1.0.4", - "crunchy", - "num-traits", - "rand 0.9.5", - "rand_distr 0.5.1", - "zerocopy", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", -] - -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" -dependencies = [ - "foldhash", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" - -[[package]] -name = "home" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "hound" -version = "3.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" - -[[package]] -name = "http" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "icu_collections" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" - -[[package]] -name = "icu_properties" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" - -[[package]] -name = "icu_provider" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "image" -version = "0.25.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" -dependencies = [ - "bytemuck", - "byteorder-lite", - "color_quant", - "exr", - "gif", - "image-webp", - "moxcms", - "num-traits", - "png", - "qoi", - "ravif", - "rayon", - "rgb", - "tiff", - "zune-core", - "zune-jpeg", -] - -[[package]] -name = "image-webp" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" -dependencies = [ - "byteorder-lite", - "quick-error 2.0.1", -] - -[[package]] -name = "imgref" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e44b0a4eaa4c82f441d50a963f2d5f05a787240aeee097597033e72accfd22f" - -[[package]] -name = "indexmap" -version = "2.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" -dependencies = [ - "equivalent", - "hashbrown 0.17.1", -] - -[[package]] -name = "indicatif" -version = "0.18.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9433806cd6b4ec1aba79c021c7e4c58fb4c3b9977c085062e611ac929998fb0c" -dependencies = [ - "console", - "portable-atomic", - "unicode-width", - "unit-prefix", - "web-time", -] - -[[package]] -name = "infra" -version = "0.1.0" -dependencies = [ - "anyhow", - "downcast-rs", - "dyn-clone", - "env_logger", - "itertools 0.15.0", - "lazy_static", - "proptest", - "tract-core", -] - -[[package]] -name = "interpolate_name" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "inventory" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6928282826c822ad91bf1c9a1cb90a30ba1c26770749929b4656cd6be829cd7c" -dependencies = [ - "rustversion", -] - -[[package]] -name = "ipnet" -version = "2.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "jiff" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb" -dependencies = [ - "defmt", - "jiff-core", - "jiff-static", - "log", - "portable-atomic", - "portable-atomic-util", - "serde_core", -] - -[[package]] -name = "jiff-core" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" -dependencies = [ - "defmt", - "log", -] - -[[package]] -name = "jiff-static" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212" -dependencies = [ - "jiff-core", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "jni" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" -dependencies = [ - "cfg-if 1.0.4", - "combine", - "jni-macros", - "jni-sys 0.4.1", - "log", - "simd_cesu8", - "thiserror 2.0.20", - "walkdir", - "windows-link", -] - -[[package]] -name = "jni-macros" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" -dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "simd_cesu8", - "syn 2.0.119", -] - -[[package]] -name = "jni-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" -dependencies = [ - "jni-sys 0.4.1", -] - -[[package]] -name = "jni-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" -dependencies = [ - "jni-sys-macros", -] - -[[package]] -name = "jni-sys-macros" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" -dependencies = [ - "quote", - "syn 2.0.119", -] - -[[package]] -name = "jobserver" -version = "0.1.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" -dependencies = [ - "getrandom 0.4.3", - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.105" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" -dependencies = [ - "cfg-if 1.0.4", - "futures-util", - "wasm-bindgen", -] - -[[package]] -name = "kdam" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d847be338ef16a13f97637c062d97fb52ebe0ff3b77fa18456d5ed366317e4f7" -dependencies = [ - "terminal_size", - "windows-sys 0.61.2", -] - -[[package]] -name = "keras-tract-tf2" -version = "0.20.7-pre" -dependencies = [ - "anyhow", - "ndarray", - "tract", - "tract-data", -] - -[[package]] -name = "khronos-egl" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" -dependencies = [ - "libc", - "libloading 0.8.9", - "pkg-config", -] - -[[package]] -name = "khronos_api" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" - -[[package]] -name = "lazy_static" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" - -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - -[[package]] -name = "lebe" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" - -[[package]] -name = "libc" -version = "0.2.191" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" - -[[package]] -name = "libfuzzer-sys" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" -dependencies = [ - "arbitrary", - "cc", -] - -[[package]] -name = "libloading" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" -dependencies = [ - "cfg-if 1.0.4", - "windows-link", -] - -[[package]] -name = "libloading" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" -dependencies = [ - "cfg-if 1.0.4", - "windows-link", -] - -[[package]] -name = "libm" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" - -[[package]] -name = "libz-sys" -version = "1.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - -[[package]] -name = "linux-raw-sys" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - -[[package]] -name = "litemap" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" - -[[package]] -name = "litrs" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" - -[[package]] -name = "loop9" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" -dependencies = [ - "imgref", -] - -[[package]] -name = "mach2" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b" - -[[package]] -name = "macro_rules_attribute" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3ae8f6d608c795738406608304d30a2dfbdc8e58e44f7ba43236da5208ded3c" -dependencies = [ - "macro_rules_attribute-proc_macro", - "pastey 0.2.3", -] - -[[package]] -name = "macro_rules_attribute-proc_macro" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc04a4c58212d57930a24bf47d3fa87485264a3a054e9c10e042eb373573ad3c" - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - -[[package]] -name = "matchit" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" - -[[package]] -name = "matrixmultiply" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f607c237553f086e7043417a51df26b2eb899d3caff94e6a67592ff992fedc7" -dependencies = [ - "autocfg", - "rawpointer", -] - -[[package]] -name = "maybe-rayon" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" -dependencies = [ - "cfg-if 1.0.4", - "rayon", -] - -[[package]] -name = "memchr" -version = "2.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" - -[[package]] -name = "memmap2" -version = "0.9.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" -dependencies = [ - "libc", -] - -[[package]] -name = "metal" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15" -dependencies = [ - "bitflags 2.13.2", - "block", - "core-graphics-types", - "foreign-types", - "log", - "objc", - "paste", -] - -[[package]] -name = "metrics" -version = "0.24.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89550ee9f79e88fef3119de263694973a8adb26c21d75322164fb8c493039fe2" -dependencies = [ - "portable-atomic", - "rapidhash", -] - -[[package]] -name = "metrics-util" -version = "0.20.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96f8722f8562635f92f8ed992f26df0532266eb03d5202607c20c0d7e9745e13" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", - "hashbrown 0.16.1", - "indexmap", - "metrics", - "ordered-float", - "rand 0.9.5", - "rand_xoshiro", - "rapidhash", - "sketches-ddsketch", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "minijinja" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "789a6b737c4a13bec43cd3c1b982aa8a8a192ff4bf444e0fb1fd5a324f064fd1" -dependencies = [ - "serde", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "miniz_oxide" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "mio" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "monostate" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3341a273f6c9d5bef1908f17b7267bbab0e95c9bf69a0d4dcf8e9e1b2c76ef67" -dependencies = [ - "monostate-impl", - "serde", - "serde_core", -] - -[[package]] -name = "monostate-impl" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4db6d5580af57bf992f59068d4ea26fd518574ff48d7639b255a36f9de6e7e9" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "moxcms" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" -dependencies = [ - "num-traits", - "pxfm", -] - -[[package]] -name = "naga" -version = "30.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a616d2fb8c89516ac2723a581f69d6c18576046bed761bd6b305e5618e6ae130" -dependencies = [ - "arrayvec", - "bit-set 0.10.0", - "bitflags 2.13.2", - "cfg-if 1.0.4", - "cfg_aliases", - "codespan-reporting", - "half", - "hashbrown 0.17.1", - "indexmap", - "libm", - "log", - "naga-types", - "num-traits", - "once_cell", - "rustc-hash 1.1.0", - "spirv", - "thiserror 2.0.20", - "unicode-ident", -] - -[[package]] -name = "naga-types" -version = "30.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "590afbf58a6f4f62873cd5cff4468061844bafa1cdf399cc954537c22d768d49" -dependencies = [ - "hashbrown 0.17.1", - "indexmap", - "rustc-hash 1.1.0", - "thiserror 2.0.20", -] - -[[package]] -name = "ndarray" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d" -dependencies = [ - "matrixmultiply", - "num-complex", - "num-integer", - "num-traits", - "portable-atomic", - "portable-atomic-util", - "rawpointer", -] - -[[package]] -name = "ndk" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" -dependencies = [ - "bitflags 2.13.2", - "jni-sys 0.3.1", - "log", - "ndk-sys", - "num_enum", - "thiserror 1.0.69", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ndk-sys" -version = "0.6.0+11769913" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" -dependencies = [ - "jni-sys 0.3.1", -] - -[[package]] -name = "nemo-nemotron-asr" -version = "0.1.0" -dependencies = [ - "anyhow", - "float-ord", - "hound", - "itertools 0.15.0", - "ndarray", - "serde_json", - "tract", -] - -[[package]] -name = "nemo-nemotron-streaming-asr" -version = "0.1.0" -dependencies = [ - "anyhow", - "clap", - "cpal", - "float-ord", - "hound", - "itertools 0.15.0", - "ndarray", - "serde_json", - "tract", -] - -[[package]] -name = "nemo-nemotron35-streaming-asr" -version = "0.1.0" -dependencies = [ - "anyhow", - "float-ord", - "hound", - "itertools 0.15.0", - "ndarray", - "serde_json", - "tract", -] - -[[package]] -name = "nemo-parakeet-asr" -version = "0.1.0" -dependencies = [ - "anyhow", - "float-ord", - "hound", - "itertools 0.15.0", - "ndarray", - "serde_json", - "tract", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "nix" -version = "0.31.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" -dependencies = [ - "bitflags 2.13.2", - "cfg-if 1.0.4", - "cfg_aliases", - "libc", -] - -[[package]] -name = "nnef-inceptionv3" -version = "0.20.7-pre" -dependencies = [ - "dinghy-test", - "env_logger", - "flate2", - "image", - "log", - "tract-core", - "tract-nnef", -] - -[[package]] -name = "no_std_io2" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003" -dependencies = [ - "memchr", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "nom" -version = "8.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" -dependencies = [ - "memchr", -] - -[[package]] -name = "nom-language" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2de2bc5b451bfedaef92c90b8939a8fff5770bdcc1fafd6239d086aab8fa6b29" -dependencies = [ - "nom 8.0.0", -] - -[[package]] -name = "noop_proc_macro" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" - -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "num-bigint" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "bytemuck", - "num-traits", -] - -[[package]] -name = "num-conv" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" - -[[package]] -name = "num-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "num-integer" -version = "0.1.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "num_cpus" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "num_enum" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" -dependencies = [ - "num_enum_derive", - "rustversion", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - -[[package]] -name = "objc2" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" -dependencies = [ - "objc2-encode", -] - -[[package]] -name = "objc2-audio-toolbox" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08" -dependencies = [ - "bitflags 2.13.2", - "libc", - "objc2", - "objc2-core-audio", - "objc2-core-audio-types", - "objc2-core-foundation", - "objc2-foundation", -] - -[[package]] -name = "objc2-avf-audio" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be" -dependencies = [ - "bitflags 2.13.2", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-audio" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2" -dependencies = [ - "dispatch2", - "objc2", - "objc2-core-audio-types", - "objc2-core-foundation", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-audio-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c" -dependencies = [ - "bitflags 2.13.2", - "objc2", -] - -[[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags 2.13.2", - "block2", - "dispatch2", - "libc", - "objc2", -] - -[[package]] -name = "objc2-core-graphics" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" -dependencies = [ - "dispatch2", - "objc2", - "objc2-core-foundation", - "objc2-io-surface", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" -dependencies = [ - "bitflags 2.13.2", - "block2", - "libc", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-io-surface" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" -dependencies = [ - "bitflags 2.13.2", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-metal" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" -dependencies = [ - "bitflags 2.13.2", - "block2", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" -dependencies = [ - "bitflags 2.13.2", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation", - "objc2-metal", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "onig" -version = "6.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc3cbf698f9438986c11a880c90a6d04b9de27575afd28bbf45b154b6c709e2" -dependencies = [ - "bitflags 2.13.2", - "libc", - "once_cell", - "onig_sys", -] - -[[package]] -name = "onig_sys" -version = "69.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e68317604e77e53b85896388e1a803c1d21b74c899ec9e5e1112db90735edd7" -dependencies = [ - "cc", - "pkg-config", -] - -[[package]] -name = "oorandom" -version = "11.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" - -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "openssl-probe" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - -[[package]] -name = "openssl-sys" -version = "0.9.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "ordered-float" -version = "5.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c7c9e0d9b23589f26070720bac724174bfec1083e82f7854cdd0267518343c0" -dependencies = [ - "num-traits", -] - -[[package]] -name = "page_size" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if 1.0.4", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "pastey" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" - -[[package]] -name = "pastey" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" - -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "pkg-config" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" - -[[package]] -name = "plotters" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" -dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "plotters-backend" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" - -[[package]] -name = "plotters-svg" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" -dependencies = [ - "plotters-backend", -] - -[[package]] -name = "png" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" -dependencies = [ - "bitflags 2.13.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide 0.8.9", -] - -[[package]] -name = "pollster" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6355899e1c9462875b6757c79f3caa011a1fdae12bbb1a2e72dd1f234f8336" - -[[package]] -name = "portable-atomic" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" - -[[package]] -name = "portable-atomic-util" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" -dependencies = [ - "portable-atomic", -] - -[[package]] -name = "potential_utf" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" -dependencies = [ - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "presser" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" - -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.119", -] - -[[package]] -name = "prettyplease" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bfe0f4c752e450fc2faf62654f1c134747922825d5b04ca717b8874f41a40c0" -dependencies = [ - "proc-macro2", - "syn 3.0.5", -] - -[[package]] -name = "primal-check" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc0d895b311e3af9902528fbb8f928688abbd95872819320517cc24ca6b2bd08" -dependencies = [ - "num-integer", -] - -[[package]] -name = "proc-macro-crate" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" -dependencies = [ - "toml_edit", -] - -[[package]] -name = "proc-macro2" -version = "1.0.107" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "profiling" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" -dependencies = [ - "profiling-procmacros", -] - -[[package]] -name = "profiling-procmacros" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" -dependencies = [ - "quote", - "syn 2.0.119", -] - -[[package]] -name = "proptest" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8530004ccb15eae51c7e40009fbe317f341f804db54dc033eec1c50be28cfa0" -dependencies = [ - "bit-set 0.11.1", - "bit-vec 0.10.1", - "bitflags 2.13.2", - "chacha20", - "core_detect", - "num-traits", - "rand 0.10.3", - "rand_xorshift", - "regex-syntax", - "rusty-fork", - "tempfile", - "unarray", -] - -[[package]] -name = "prost" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-derive" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" -dependencies = [ - "anyhow", - "itertools 0.14.0", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "prost-types" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" -dependencies = [ - "prost", -] - -[[package]] -name = "pulp" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046aa45b989642ec2e4717c8e72d677b13edd831a4d3b6cf37d9a3e54912496a" -dependencies = [ - "bytemuck", - "cfg-if 1.0.4", - "libm", - "num-complex", - "paste", - "pulp-wasm-simd-flag", - "raw-cpuid", - "reborrow", - "version_check", -] - -[[package]] -name = "pulp-wasm-simd-flag" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d8f70e07b9c3962945a74e59ca1c511bba65b6419468acc217c457d93f3c740" - -[[package]] -name = "pxfm" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" - -[[package]] -name = "pytorch-albert-v2" -version = "0.20.7-pre" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "clap_builder", - "clap_lex", - "ndarray", - "tokenizers", - "tract", -] - -[[package]] -name = "qoi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - -[[package]] -name = "quote" -version = "1.0.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "rand" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" -dependencies = [ - "rand_chacha", - "rand_core 0.9.5", -] - -[[package]] -name = "rand" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" -dependencies = [ - "chacha20", - "getrandom 0.4.3", - "rand_core 0.10.1", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "rand_core" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" - -[[package]] -name = "rand_distr" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" -dependencies = [ - "num-traits", - "rand 0.9.5", -] - -[[package]] -name = "rand_distr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d431c2703ccf129de4d45253c03f49ebb22b97d6ad79ee3ecfc7e3f4862c1d8" -dependencies = [ - "num-traits", - "rand 0.10.3", -] - -[[package]] -name = "rand_xorshift" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60aa6af80be32871323012e02e6e65f8a7cc7890931ae421d217ad8fe0df2ccf" -dependencies = [ - "rand_core 0.10.1", -] - -[[package]] -name = "rand_xoshiro" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" -dependencies = [ - "rand_core 0.9.5", -] - -[[package]] -name = "range-alloc" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" - -[[package]] -name = "rapidhash" -version = "4.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5da7e78a036ce858e8d55b7e7dc8ba3a88b78350fd2155d3591bbd966b58589e" -dependencies = [ - "rustversion", -] - -[[package]] -name = "rav1e" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" -dependencies = [ - "aligned-vec", - "arbitrary", - "arg_enum_proc_macro", - "arrayvec", - "av-scenechange", - "av1-grain", - "bitstream-io", - "built", - "cfg-if 1.0.4", - "interpolate_name", - "itertools 0.14.0", - "libc", - "libfuzzer-sys", - "log", - "maybe-rayon", - "new_debug_unreachable", - "noop_proc_macro", - "num-derive", - "num-traits", - "paste", - "profiling", - "rand 0.9.5", - "rand_chacha", - "simd_helpers", - "thiserror 2.0.20", - "v_frame", - "wasm-bindgen", -] - -[[package]] -name = "ravif" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" -dependencies = [ - "avif-serialize", - "imgref", - "loop9", - "quick-error 2.0.1", - "rav1e", - "rayon", - "rgb", -] - -[[package]] -name = "raw-cpuid" -version = "11.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" -dependencies = [ - "bitflags 2.13.2", -] - -[[package]] -name = "raw-window-handle" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" - -[[package]] -name = "raw-window-metal" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" -dependencies = [ - "objc2", - "objc2-core-foundation", - "objc2-foundation", - "objc2-quartz-core", -] - -[[package]] -name = "rawpointer" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" - -[[package]] -name = "rayon" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-cond" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2964d0cf57a3e7a06e8183d14a8b527195c706b7983549cd5462d5aa3747438f" -dependencies = [ - "either", - "itertools 0.14.0", - "rayon", -] - -[[package]] -name = "rayon-core" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "readings-probe" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d44528f4328cceb716d2acfa443fcf847ba35feb107dd0e25a3421456127cd33" -dependencies = [ - "lazy_static", - "libc", - "num_cpus", - "thiserror 1.0.69", - "winapi", -] - -[[package]] -name = "reborrow" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags 2.13.2", -] - -[[package]] -name = "regex" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" - -[[package]] -name = "renderdoc-sys" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" - -[[package]] -name = "reqwest" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" -dependencies = [ - "base64 0.23.1", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "rustls", - "rustls-pki-types", - "rustls-platform-verifier", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "rgb" -version = "0.8.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if 1.0.4", - "getrandom 0.2.17", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "ron" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81116b9531d61eabc41aeb228e4b6b2435bcca3233b98cf3b3077d4e6e9debb3" -dependencies = [ - "bitflags 2.13.2", - "once_cell", - "serde", - "serde_derive", - "typeid", - "unicode-ident", -] - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc-hash" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustfft" -version = "6.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21db5f9893e91f41798c88680037dba611ca6674703c1a18601b01a72c8adb89" -dependencies = [ - "num-complex", - "num-integer", - "num-traits", - "primal-check", - "strength_reduce", - "transpose", -] - -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.13.2", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustix" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" -dependencies = [ - "bitflags 2.13.2", - "errno", - "libc", - "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls" -version = "0.23.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" -dependencies = [ - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-native-certs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" -dependencies = [ - "openssl-probe 0.2.1", - "rustls-pki-types", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pki-types" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" -dependencies = [ - "zeroize", -] - -[[package]] -name = "rustls-platform-verifier" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" -dependencies = [ - "core-foundation", - "core-foundation-sys", - "jni", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-platform-verifier-android", - "rustls-webpki", - "security-framework", - "security-framework-sys", - "webpki-root-certs", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls-platform-verifier-android" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" - -[[package]] -name = "rustls-webpki" -version = "0.103.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" - -[[package]] -name = "rusty-fork" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" -dependencies = [ - "fnv", - "quick-error 1.2.3", - "tempfile", - "wait-timeout", -] - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "scan_fmt" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b53b0a5db882a8e2fdaae0a43f7b39e7e9082389e978398bdf223a55b581248" - -[[package]] -name = "schannel" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "sd15-web" -version = "0.1.0" -dependencies = [ - "anyhow", - "console_error_panic_hook", - "getrandom 0.4.3", - "half", - "js-sys", - "png", - "pollster", - "regex", - "serde_json", - "tract", - "wasm-bindgen", - "wasm-bindgen-futures", -] - -[[package]] -name = "security-framework" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" -dependencies = [ - "bitflags 2.13.2", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" - -[[package]] -name = "serde" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "serde_json" -version = "1.0.154" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_path_to_error" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" -dependencies = [ - "itoa", - "serde", - "serde_core", -] - -[[package]] -name = "serde_spanned" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" -dependencies = [ - "serde_core", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "shlex" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" - -[[package]] -name = "simd-adler32" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" - -[[package]] -name = "simd_cesu8" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" -dependencies = [ - "rustc_version", - "simdutf8", -] - -[[package]] -name = "simd_helpers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" -dependencies = [ - "quote", -] - -[[package]] -name = "simdutf8" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" - -[[package]] -name = "sketches-ddsketch" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6f73aeb92d671e0cc4dca167e59b2deb6387c375391bc99ee743f326994a2b" - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "slotmap" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" -dependencies = [ - "version_check", -] - -[[package]] -name = "smallvec" -version = "1.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b" - -[[package]] -name = "socket2" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "spirv" -version = "0.4.0+sdk-1.4.341.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f" -dependencies = [ - "bitflags 2.13.2", -] - -[[package]] -name = "spm_precompiled" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5851699c4033c63636f7ea4cf7b7c1f1bf06d0cc03cfb42e711de5a5c46cf326" -dependencies = [ - "base64 0.13.1", - "nom 7.1.3", - "serde", - "unicode-segmentation", -] - -[[package]] -name = "stable-diffusion" -version = "0.1.0" -dependencies = [ - "anyhow", - "clap", - "image", - "kdam", - "ndarray", - "rand 0.10.3", - "rand_distr 0.6.0", - "tokenizers", - "tract", -] - -[[package]] -name = "stable-diffusion-3" -version = "0.1.0" -dependencies = [ - "anyhow", - "clap", - "image", - "kdam", - "ndarray", - "rand 0.10.3", - "rand_distr 0.6.0", - "tokenizers", - "tract", -] - -[[package]] -name = "stable-diffusion-xl" -version = "0.1.0" -dependencies = [ - "anyhow", - "clap", - "image", - "kdam", - "ndarray", - "rand 0.10.3", - "rand_distr 0.6.0", - "tokenizers", - "tract", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strength_reduce" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" - -[[package]] -name = "string-interner" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad3df9b59e2eded8d825c7c4363ad339a20fb6bc0b9a4778560f518f59910b15" -dependencies = [ - "hashbrown 0.16.1", - "serde", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "suite-onnx" -version = "0.1.0" -dependencies = [ - "anyhow", - "bytes", - "env_logger", - "fs2", - "infra", - "itertools 0.15.0", - "lazy_static", - "log", - "proptest", - "prost", - "regex", - "tract-core", - "tract-hir", - "tract-onnx", -] - -[[package]] -name = "suite-pulse" -version = "0.1.0" -dependencies = [ - "infra", - "proptest", - "tract-core", - "tract-pulse", -] - -[[package]] -name = "suite-unit" -version = "0.1.0" -dependencies = [ - "infra", - "proptest", - "tract-core", - "tract-transformers", -] - -[[package]] -name = "syn" -version = "2.0.119" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "3.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "tar" -version = "0.4.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" -dependencies = [ - "filetime", - "libc", - "xattr", -] - -[[package]] -name = "temp-dir" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016ef9739649996fcc983b9c588fe3d557cf216d4d98503ce1b057ab5a66d689" - -[[package]] -name = "tempfile" -version = "3.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" -dependencies = [ - "fastrand", - "getrandom 0.4.3", - "once_cell", - "rustix 1.1.5", - "windows-sys 0.61.2", -] - -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "terminal_size" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" -dependencies = [ - "rustix 1.1.5", - "windows-sys 0.61.2", -] - -[[package]] -name = "test-cuda" -version = "0.1.0" -dependencies = [ - "home", - "infra", - "lazy_static", - "log", - "pastey 0.2.3", - "regex", - "suite-onnx", - "suite-pulse", - "suite-unit", - "tract-core", - "tract-cuda", - "tract-gpu", - "tract-onnx-opl", -] - -[[package]] -name = "test-f16" -version = "0.1.0" -dependencies = [ - "home", - "infra", - "lazy_static", - "log", - "regex", - "suite-onnx", - "suite-unit", - "tract-core", - "tract-nnef", - "tract-onnx-opl", - "tract-transformers", -] - -[[package]] -name = "test-metal" -version = "0.1.0" -dependencies = [ - "home", - "infra", - "lazy_static", - "log", - "pastey 0.2.3", - "regex", - "suite-onnx", - "suite-pulse", - "suite-unit", - "tract-core", - "tract-gpu", - "tract-metal", - "tract-onnx-opl", -] - -[[package]] -name = "test-nnef-cycle" -version = "0.1.0" -dependencies = [ - "infra", - "itertools 0.15.0", - "lazy_static", - "log", - "suite-onnx", - "suite-unit", - "tract-core", - "tract-nnef", - "tract-onnx-opl", - "tract-transformers", -] - -[[package]] -name = "test-onnx-core" -version = "0.20.7-pre" -dependencies = [ - "lazy_static", - "suite-onnx", - "tract-core", - "tract-nnef", - "tract-onnx", -] - -[[package]] -name = "test-pulse-core" -version = "0.1.0" -dependencies = [ - "suite-pulse", - "tract-core", -] - -[[package]] -name = "test-tflite" -version = "0.1.0" -dependencies = [ - "home", - "infra", - "lazy_static", - "log", - "regex", - "suite-onnx", - "suite-unit", - "tflitec", - "tract-core", - "tract-onnx-opl", - "tract-tflite", -] - -[[package]] -name = "test-unit-core" -version = "0.1.0" -dependencies = [ - "suite-unit", - "tract-core", -] - -[[package]] -name = "test-wgpu" -version = "0.1.0" -dependencies = [ - "home", - "infra", - "lazy_static", - "log", - "pastey 0.2.3", - "regex", - "suite-onnx", - "suite-pulse", - "suite-unit", - "tract-core", - "tract-gpu", - "tract-onnx-opl", - "tract-wgpu", -] - -[[package]] -name = "tf-inceptionv3" -version = "0.20.7-pre" -dependencies = [ - "criterion", - "dinghy-test", - "env_logger", - "image", - "log", - "tract-tensorflow", -] - -[[package]] -name = "tf-mobilenet-v2" -version = "0.20.7-pre" -dependencies = [ - "dinghy-test", - "image", - "tract-tensorflow", -] - -[[package]] -name = "tfl-mobilenet-v2-q" -version = "0.20.7-pre" -dependencies = [ - "dinghy-test", - "image", - "tract-tflite", -] - -[[package]] -name = "tflitec" -version = "0.6.0" -source = "git+https://github.com/kali/tflitec-rs.git?rev=9ceb838#9ceb838839d0481030aa12e95d8cb28f659f7a48" -dependencies = [ - "bindgen 0.65.1", - "curl", - "fs_extra", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" -dependencies = [ - "thiserror-impl 2.0.20", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "tiff" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" -dependencies = [ - "fax", - "flate2", - "half", - "quick-error 2.0.1", - "weezl", - "zune-jpeg", -] - -[[package]] -name = "tikv-jemalloc-sys" -version = "0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8aa5b2ab86a2cefa406d889139c162cbb230092f7d1d7cbc1716405d852a3b" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "tikv-jemallocator" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0359b4327f954e0567e69fb191cf1436617748813819c94b8cd4a431422d053a" -dependencies = [ - "libc", - "tikv-jemalloc-sys", -] - -[[package]] -name = "time" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" -dependencies = [ - "deranged", - "num-conv", - "powerfmt", - "serde_core", - "time-core", -] - -[[package]] -name = "time-core" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" - -[[package]] -name = "tinystr" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "tokenizers" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7afbf6e88718afcc138bad01d6ccc3051dbbc3b2ce9793d8b8a3aeb610969cfc" -dependencies = [ - "ahash", - "compact_str", - "daachorse", - "dary_heap", - "derive_builder", - "esaxx-rs", - "fancy-regex", - "getrandom 0.3.4", - "indicatif", - "itertools 0.14.0", - "log", - "macro_rules_attribute", - "monostate", - "onig", - "paste", - "rand 0.9.5", - "rayon", - "rayon-cond", - "regex", - "regex-syntax", - "serde", - "serde_json", - "spm_precompiled", - "thiserror 2.0.20", - "unicode-normalization-alignments", - "unicode-segmentation", - "unicode_categories", -] - -[[package]] -name = "tokio" -version = "1.53.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce3335fa71841cda333a58d7615b03901380ecf09d59b3296d21f8bbac0dde4e" -dependencies = [ - "bytes", - "libc", - "mio", - "pin-project-lite", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-scoped" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4beb8ba13bc53ac53ce1d52b42f02e5d8060f0f42138862869beb769722b256" -dependencies = [ - "tokio", - "tokio-stream", -] - -[[package]] -name = "tokio-stream" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "1.1.6+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a" -dependencies = [ - "indexmap", - "serde_core", - "serde_spanned", - "toml_datetime", - "toml_parser", - "toml_writer", - "winnow", -] - -[[package]] -name = "toml_datetime" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.25.15+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614" -dependencies = [ - "indexmap", - "serde_core", - "serde_spanned", - "toml_datetime", - "toml_parser", - "winnow", -] - -[[package]] -name = "toml_parser" -version = "1.1.3+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" -dependencies = [ - "winnow", -] - -[[package]] -name = "toml_writer" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" - -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-http" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" -dependencies = [ - "bitflags 2.13.2", - "bytes", - "futures-util", - "http", - "http-body", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", - "url", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "log", - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "tract" -version = "0.23.10-pre" -dependencies = [ - "anyhow", - "boow", - "erased-serde", - "flate2", - "half", - "icu_normalizer", - "icu_properties", - "reqwest", - "rustls", - "serde_json", - "tempfile", - "tract-api", - "tract-core", - "tract-cuda", - "tract-data", - "tract-extra", - "tract-gpu", - "tract-libcli", - "tract-metal", - "tract-nnef", - "tract-onnx", - "tract-onnx-opl", - "tract-pulse", - "tract-pulse-opl", - "tract-transformers", - "tract-wgpu", - "wasm-bindgen", - "web-sys", - "wgpu", -] - -[[package]] -name = "tract-api" -version = "0.23.10-pre" -dependencies = [ - "anyhow", - "boow", - "half", - "lazy_static", - "reqwest", - "serde", - "serde_json", - "tempfile", -] - -[[package]] -name = "tract-cli" -version = "0.23.10-pre" -dependencies = [ - "box_drawing", - "clap", - "colorous", - "criterion", - "cudarc", - "env_logger", - "erased-serde", - "flate2", - "float-ord", - "fs-err", - "icu_normalizer", - "icu_normalizer_data", - "icu_properties", - "icu_properties_data", - "idna_adapter", - "inventory", - "lazy_static", - "litemap", - "log", - "minijinja", - "nu-ansi-term", - "num_cpus", - "readings-probe", - "reqwest", - "ron", - "rustix 1.1.5", - "rustls", - "scan_fmt", - "serde", - "serde_json", - "tar", - "tikv-jemalloc-sys", - "tikv-jemallocator", - "time", - "toml", - "tract-core", - "tract-cuda", - "tract-data", - "tract-extra", - "tract-gpu", - "tract-hir", - "tract-libcli", - "tract-linalg", - "tract-metal", - "tract-nnef", - "tract-nnef-resources", - "tract-onnx", - "tract-pulse", - "tract-pulse-opl", - "tract-tensorflow", - "tract-tflite", - "tract-transformers", - "tract-wgpu", - "webpki-roots", - "zerofrom", -] - -[[package]] -name = "tract-core" -version = "0.23.10-pre" -dependencies = [ - "anyhow", - "anymap3", - "approx", - "bit-set 0.11.1", - "criterion", - "derive-new", - "downcast-rs", - "dyn-clone", - "env_logger", - "erased-serde", - "figment2", - "inventory", - "lazy_static", - "log", - "maplit", - "metrics", - "metrics-util", - "ndarray", - "num-complex", - "num-integer", - "num-traits", - "pastey 0.2.3", - "proptest", - "rustfft", - "serde", - "smallvec", - "tract-data", - "tract-linalg", -] - -[[package]] -name = "tract-cuda" -version = "0.23.10-pre" -dependencies = [ - "anyhow", - "askama", - "criterion", - "cudarc", - "derive-new", - "downcast-rs", - "inventory", - "libloading 0.9.0", - "log", - "num-complex", - "num-traits", - "proptest", - "rand 0.10.3", - "rustfft", - "tract-core", - "tract-gpu", - "tract-pulse-opl", - "tract-transformers", -] - -[[package]] -name = "tract-data" -version = "0.23.10-pre" -dependencies = [ - "anyhow", - "criterion", - "downcast-rs", - "dyn-clone", - "dyn-hash", - "flate2", - "half", - "inventory", - "itertools 0.15.0", - "lazy_static", - "libm", - "maplit", - "ndarray", - "nom 8.0.0", - "nom-language", - "num-complex", - "num-integer", - "num-traits", - "parking_lot", - "proptest", - "smallvec", - "string-interner", - "zip", -] - -[[package]] -name = "tract-extra" -version = "0.23.10-pre" -dependencies = [ - "approx", - "criterion", - "env_logger", - "lazy_static", - "proptest", - "tract-nnef", - "tract-pulse", -] - -[[package]] -name = "tract-ffi" -version = "0.23.10-pre" -dependencies = [ - "anyhow", - "serde", - "serde_json", - "tract", - "tract-api", -] - -[[package]] -name = "tract-gpu" -version = "0.23.10-pre" -dependencies = [ - "anyhow", - "derive-new", - "downcast-rs", - "dyn-hash", - "num-traits", - "tract-core", - "tract-pulse-opl", - "tract-transformers", -] - -[[package]] -name = "tract-hir" -version = "0.23.10-pre" -dependencies = [ - "derive-new", - "env_logger", - "log", - "tract-core", -] - -[[package]] -name = "tract-libcli" -version = "0.23.10-pre" -dependencies = [ - "box_drawing", - "clap", - "colorous", - "cudarc", - "lazy_static", - "log", - "nu-ansi-term", - "rand 0.10.3", - "serde", - "serde_json", - "tract-core", - "tract-cuda", - "tract-data", - "tract-hir", - "tract-metal", - "tract-onnx", - "tract-transformers", -] - -[[package]] -name = "tract-linalg" -version = "0.23.10-pre" -dependencies = [ - "byteorder", - "cc", - "core_affinity", - "criterion", - "derive-new", - "downcast-rs", - "dyn-clone", - "dyn-hash", - "env_logger", - "half", - "inventory", - "lazy_static", - "libc", - "log", - "minijinja", - "nu-ansi-term", - "num-traits", - "pastey 0.2.3", - "proptest", - "rayon", - "tract-data", - "walkdir", -] - -[[package]] -name = "tract-metal" -version = "0.23.10-pre" -dependencies = [ - "anyhow", - "block", - "criterion", - "derive-new", - "downcast-rs", - "inventory", - "log", - "metal", - "num-traits", - "objc", - "proptest", - "rand 0.10.3", - "tract-core", - "tract-gpu", - "tract-pulse-opl", - "tract-transformers", -] - -[[package]] -name = "tract-nnef" -version = "0.23.10-pre" -dependencies = [ - "byteorder", - "erased-serde", - "flate2", - "log", - "minijinja", - "nom 8.0.0", - "nom-language", - "serde", - "serde_json", - "simd-adler32", - "tar", - "temp-dir", - "tract-core", - "walkdir", -] - -[[package]] -name = "tract-nnef-cli" -version = "0.21.8-pre" -dependencies = [ - "anyhow", - "clap", - "env_logger", - "log", - "tract-nnef", - "tract-onnx-opl", - "tract-pulse", -] - -[[package]] -name = "tract-nnef-resources" -version = "0.23.10-pre" -dependencies = [ - "anyhow", - "nom 8.0.0", - "nom-language", - "serde", - "serde_json", - "tract-nnef", -] - -[[package]] -name = "tract-onnx" -version = "0.23.10-pre" -dependencies = [ - "bytes", - "criterion", - "derive-new", - "env_logger", - "log", - "memmap2", - "nom 8.0.0", - "nom-language", - "num-integer", - "num-rational", - "num-traits", - "prost", - "rand 0.10.3", - "rayon", - "smallvec", - "tempfile", - "tract-extra", - "tract-hir", - "tract-nnef", - "tract-onnx-opl", - "tract-transformers", -] - -[[package]] -name = "tract-onnx-opl" -version = "0.23.10-pre" -dependencies = [ - "env_logger", - "getrandom 0.4.3", - "log", - "rand 0.10.3", - "rand_distr 0.6.0", - "rustfft", - "tract-extra", - "tract-nnef", -] - -[[package]] -name = "tract-proxy" -version = "0.23.10-pre" -dependencies = [ - "anyhow", - "boow", - "half", - "home", - "ndarray", - "reqwest", - "rustls", - "serde_json", - "tempfile", - "tract-api", - "tract-proxy-sys", -] - -[[package]] -name = "tract-proxy-sys" -version = "0.23.10-pre" -dependencies = [ - "bindgen 0.73.2", -] - -[[package]] -name = "tract-pulse" -version = "0.23.10-pre" -dependencies = [ - "downcast-rs", - "erased-serde", - "lazy_static", - "log", - "serde", - "tract-pulse-opl", - "tract-transformers", -] - -[[package]] -name = "tract-pulse-opl" -version = "0.23.10-pre" -dependencies = [ - "downcast-rs", - "lazy_static", - "tract-nnef", -] - -[[package]] -name = "tract-tensorflow" -version = "0.23.10-pre" -dependencies = [ - "bytes", - "criterion", - "derive-new", - "env_logger", - "log", - "memmap2", - "prost", - "prost-types", - "tract-hir", - "tract-pulse", -] - -[[package]] -name = "tract-tflite" -version = "0.23.10-pre" -dependencies = [ - "derive-new", - "flatbuffers", - "tract-core", -] - -[[package]] -name = "tract-transformers" -version = "0.23.10-pre" -dependencies = [ - "float-ord", - "rayon", - "tract-data", - "tract-nnef", -] - -[[package]] -name = "tract-wgpu" -version = "0.23.10-pre" -dependencies = [ - "anyhow", - "derive-new", - "downcast-rs", - "inventory", - "js-sys", - "log", - "num-traits", - "pollster", - "proptest", - "rand 0.10.3", - "tract-core", - "tract-gpu", - "tract-pulse-opl", - "tract-transformers", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "wgpu", -] - -[[package]] -name = "transpose" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e" -dependencies = [ - "num-integer", - "strength_reduce", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typed-path" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" - -[[package]] -name = "typeid" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" - -[[package]] -name = "unarray" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" - -[[package]] -name = "uncased" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "unicode-normalization-alignments" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f613e4fa046e69818dd287fdc4bc78175ff20331479dab6e1b0f98d57062de" -dependencies = [ - "smallvec", -] - -[[package]] -name = "unicode-segmentation" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" - -[[package]] -name = "unicode-width" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" - -[[package]] -name = "unicode_categories" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" - -[[package]] -name = "unit-prefix" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "v_frame" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" -dependencies = [ - "aligned-vec", - "num-traits", - "wasm-bindgen", -] - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "wait-timeout" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" -dependencies = [ - "libc", -] - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.4+wasi-0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.128" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" -dependencies = [ - "cfg-if 1.0.4", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.128" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.128" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn 3.0.5", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.128" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-model-bench" -version = "0.1.0" -dependencies = [ - "anyhow", - "tract-nnef", - "tract-onnx", -] - -[[package]] -name = "wayland-sys" -version = "0.31.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" -dependencies = [ - "dlib", - "log", - "once_cell", - "pkg-config", -] - -[[package]] -name = "web-sys" -version = "0.3.105" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-root-certs" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ec80e73fd8baf57bf0d9615554c75348bda990d6d5bbeab964dd068319fecb" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "webpki-roots" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c1c8e20c8a38fc7d65abfe74eb7280e4aa667e8eec8bc84996d2874810748d9" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "weezl" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" - -[[package]] -name = "wgpu" -version = "30.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527ccdf43dd5b2e8676eed9984ce00e2bbb0a1b85b70c1969dcb6cd2eb55ab9e" -dependencies = [ - "arrayvec", - "bitflags 2.13.2", - "bytemuck", - "cfg-if 1.0.4", - "cfg_aliases", - "document-features", - "hashbrown 0.17.1", - "js-sys", - "log", - "naga", - "parking_lot", - "portable-atomic", - "profiling", - "raw-window-handle", - "smallvec", - "static_assertions", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "wgpu-core", - "wgpu-hal", - "wgpu-types", -] - -[[package]] -name = "wgpu-core" -version = "30.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c018fce9b6270aa203c2fdd56f3cce996713534bd757e4ea58c8560b121f14" -dependencies = [ - "arrayvec", - "bit-set 0.10.0", - "bit-vec 0.9.1", - "bitflags 2.13.2", - "bytemuck", - "cfg_aliases", - "document-features", - "hashbrown 0.17.1", - "indexmap", - "log", - "naga", - "naga-types", - "once_cell", - "parking_lot", - "portable-atomic", - "profiling", - "raw-window-handle", - "rustc-hash 1.1.0", - "smallvec", - "thiserror 2.0.20", - "wgpu-core-deps-apple", - "wgpu-core-deps-emscripten", - "wgpu-core-deps-windows-linux-android", - "wgpu-hal", - "wgpu-naga-bridge", - "wgpu-types", -] - -[[package]] -name = "wgpu-core-deps-apple" -version = "30.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061f3d319a40d39d00b1ecc2c33b89fe21d4e6fe01859df3500a3a8ecccd6b68" -dependencies = [ - "wgpu-hal", -] - -[[package]] -name = "wgpu-core-deps-emscripten" -version = "30.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98b86cf4abf524a902dd35f18ca6a3f08fc2ae9847c8f10b48e30491b1f0b86" -dependencies = [ - "wgpu-hal", -] - -[[package]] -name = "wgpu-core-deps-windows-linux-android" -version = "30.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7586165fd5f6d881cb9ce4bb71f40d6caab2c0f1837e3fc1d9788a197fb6004f" -dependencies = [ - "wgpu-hal", -] - -[[package]] -name = "wgpu-hal" -version = "30.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b7fb58561a792bc237628ba0792e332de418fefe145f13b5ed8201e6d52f58" -dependencies = [ - "android_system_properties", - "arrayvec", - "ash", - "bit-set 0.10.0", - "bitflags 2.13.2", - "block2", - "bytemuck", - "cfg-if 1.0.4", - "cfg_aliases", - "glow", - "glutin_wgl_sys", - "gpu-allocator", - "hashbrown 0.17.1", - "js-sys", - "khronos-egl", - "libc", - "libloading 0.8.9", - "log", - "naga", - "naga-types", - "ndk-sys", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation", - "objc2-metal", - "objc2-quartz-core", - "once_cell", - "ordered-float", - "parking_lot", - "portable-atomic", - "portable-atomic-util", - "profiling", - "range-alloc", - "raw-window-handle", - "raw-window-metal", - "renderdoc-sys", - "smallvec", - "static_assertions", - "thiserror 2.0.20", - "wasm-bindgen", - "wayland-sys", - "web-sys", - "wgpu-naga-bridge", - "wgpu-types", - "windows", - "windows-core", - "windows-result", -] - -[[package]] -name = "wgpu-naga-bridge" -version = "30.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f62e73117bb7a62bfd9c5a5841438a823f6566c6442a808ee269d2d055c081" -dependencies = [ - "naga", - "wgpu-types", -] - -[[package]] -name = "wgpu-types" -version = "30.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99dad6f1fbdbbdb4c278a6508b059d44688f5cebddf78d005a46a31340269286" -dependencies = [ - "bitflags 2.13.2", - "bytemuck", - "js-sys", - "log", - "naga-types", - "raw-window-handle", - "static_assertions", - "web-sys", -] - -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix 0.38.44", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" -dependencies = [ - "windows-collections", - "windows-core", - "windows-future", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" -dependencies = [ - "windows-core", -] - -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-future" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" -dependencies = [ - "windows-core", - "windows-link", - "windows-threading", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-numerics" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" -dependencies = [ - "windows-core", - "windows-link", -] - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows-threading" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "winnow" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" -dependencies = [ - "memchr", -] - -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" - -[[package]] -name = "xattr" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" -dependencies = [ - "libc", - "rustix 1.1.5", -] - -[[package]] -name = "xml-rs" -version = "0.8.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7" - -[[package]] -name = "y4m" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" - -[[package]] -name = "yoke" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.8.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "zerofrom" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" - -[[package]] -name = "zerotrie" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.5", -] - -[[package]] -name = "zip" -version = "9.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e04976da629d3b04204ba8105b396a193af8ad2583b2c79bfb3ab8af580a11e" -dependencies = [ - "crc32fast", - "flate2", - "indexmap", - "memchr", - "nix", - "typed-path", -] - -[[package]] -name = "zlib-rs" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" - -[[package]] -name = "zmij" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" - -[[package]] -name = "zune-core" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b" - -[[package]] -name = "zune-inflate" -version = "0.2.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "zune-jpeg" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" -dependencies = [ - "zune-core", -] diff --git a/Cargo.toml b/Cargo.toml index 9a1ba4bece..f805d97cd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -127,7 +127,7 @@ default-members = [ [workspace.package] # MSRV source of truth: CI derives the tested toolchain from this. Keep the # README rustc badge in sync when bumping. -rust-version = "1.91" +rust-version = "1.98" [workspace.dependencies] anstyle = "1.0.2" @@ -292,3 +292,11 @@ opt-level = 2 debug = false # strip = "debuginfo" does not work on android and ios incremental = false + +# AdaWorldAPI/ndarray fork: same 0.17.2 API surface as upstream, plus a +# `simd`/`hpc` module (crate::simd::F32x16 etc.) with a LazyLock-cached +# AVX-512 -> AVX2 -> NEON -> scalar dispatch tier (src/simd.rs, +# src/simd_avx512.rs). Pinned via git (not a sibling path) so this +# resolves in any checkout, CI included. +[patch.crates-io] +ndarray = { git = "https://github.com/AdaWorldAPI/ndarray", branch = "master" } diff --git a/README.md b/README.md index a0eee6b1d2..07602986c3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![tract-logo](assets/tract-logo/PNG/tract-horizontal-blue.png) ![Rust](https://img.shields.io/badge/rust-%23000000.svg?style=for-the-badge&logo=rust&logoColor=white) -![rustc >= 1.91.0](https://img.shields.io/badge/rustc-%3E%3D1.91.0-brightgreen) +![rustc >= 1.98.0](https://img.shields.io/badge/rustc-%3E%3D1.98.0-brightgreen) ![MIT/Apache 2](https://img.shields.io/crates/l/tract) [![Native Linux test status](https://github.com/sonos/tract/workflows/Native%20Linux/badge.svg)](https://github.com/sonos/tract/actions) [![Embedded targets status](https://github.com/sonos/tract/workflows/Embedded%20targets/badge.svg)](https://github.com/sonos/tract/actions) diff --git a/api/rs/deny.toml b/api/rs/deny.toml index b7b973bf51..4831697b93 100644 --- a/api/rs/deny.toml +++ b/api/rs/deny.toml @@ -49,6 +49,13 @@ skip = [ { name = "rustc-hash", version="<2" }, ] +[sources] +# trusted git sources. +allow-git = [ + "https://github.com/rustformers/llm.git", + "https://github.com/AdaWorldAPI/ndarray", +] + [licenses] allow = [ "Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0) diff --git a/cli/deny.toml b/cli/deny.toml index 4c3e572e74..e3cdb5ff4e 100644 --- a/cli/deny.toml +++ b/cli/deny.toml @@ -31,6 +31,13 @@ deny = [ # List crates we don't want in our dependency tree here. ] +[sources] +# trusted git sources. +allow-git = [ + "https://github.com/rustformers/llm.git", + "https://github.com/AdaWorldAPI/ndarray", +] + [licenses] allow = [ "Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0) diff --git a/cli/src/hwbench.rs b/cli/src/hwbench.rs index 55e6396a80..a45a1341d5 100644 --- a/cli/src/hwbench.rs +++ b/cli/src/hwbench.rs @@ -395,6 +395,10 @@ fn llc_bytes() -> usize { max.unwrap_or(32 * 1024 * 1024) } +// The arguments ARE the benchmarked shape — datum type plus the GEMM's own +// dimensions. Folding them into a parameter struct would name the same eight +// values one indirection away without making any call site clearer. +#[allow(clippy::too_many_arguments)] fn bench_shape( dt: DatumType, m: usize, diff --git a/linalg/Cargo.toml b/linalg/Cargo.toml index 1e085c3f23..d9a21799fd 100644 --- a/linalg/Cargo.toml +++ b/linalg/Cargo.toml @@ -45,6 +45,13 @@ tract-data.workspace = true [target.'cfg(target_arch = "riscv64")'.dependencies] libc.workspace = true +# Pilot: additional x86_64 kernel candidates call the AdaWorldAPI ndarray fork's +# `simd::*` surface (F32x16 polyfill for leaky_relu; `BlasLevel3::blas_gemm` and +# `hpc::bf16_tile_gemm` tile primitives for GEMM) instead of hand-rolled +# intrinsics/asm, alongside the existing hand-tuned AVX-512 kernels. +[target.'cfg(target_arch = "x86_64")'.dependencies] +ndarray.workspace = true + [build-dependencies] cc.workspace = true half.workspace = true @@ -93,6 +100,18 @@ harness = false name = "mat_vec" harness = false +[[bench]] +name = "ndarray_bf16_gemm" +harness = false + +[[bench]] +name = "amx_bf16_gap_decomposition" +harness = false + +[[bench]] +name = "ndarray_gemm" +harness = false + [[bench]] name = "mm_for_wavenet_hw" harness = false diff --git a/linalg/benches/amx_bf16_gap_decomposition.rs b/linalg/benches/amx_bf16_gap_decomposition.rs new file mode 100644 index 0000000000..7bbeee2aae --- /dev/null +++ b/linalg/benches/amx_bf16_gap_decomposition.rs @@ -0,0 +1,289 @@ +// Diagnostic-only benchmark: decomposes the gap between the hand-tuned AVX-512 asm f32 GEMM +// kernel (`avx512_mmm_f32_16x8`) and the bf16-tile-based candidate added in PR #5 +// (`linalg/src/x86_64/ndarray_bf16_gemm.rs`) into its two possible causes -- the raw AMX/VNNI +// tile arithmetic itself, versus the per-tile f32->bf16 conversion + VNNI packing that PR #5's +// kernel body performs on every `AddMatMul` call (once per 16x16 output tile). +// +// Four cases at the same m=k=n shapes as `ndarray_bf16_gemm.rs`: +// B0 -- the existing asm kernel through the normal `MatMatMulKer` path (sanity baseline). +// B1 -- the raw `bf16_tile_gemm_16x16_packed` primitive called directly in a hand-rolled +// tiling loop over the whole m x k x n matmul, with A and B already fully converted to +// bf16 and B already VNNI-packed *outside* the timed closure. Zero conversion, zero +// allocation, zero packing inside the timed portion -- isolates whether the tile +// arithmetic itself (AMX TDPBF16PS on this host) is fast. +// B2 -- same tiling loop and same primitive, but A is converted from f32 to bf16 *inside* the +// timed closure every iteration (simulating a runtime activation matrix), while B stays +// pre-converted and pre-packed outside the loop (simulating a weight matrix packed once +// at model-load time and reused across many activations). +// B3 -- not implemented here: it is the existing `ndarray_bf16_16x16` case in +// `ndarray_bf16_gemm.rs`, included in the PR comment report by re-running that bench. +// +// This file adds no new production kernel and touches no packing/plan code -- it is a +// standalone harness calling `ndarray::simd::*` directly, bypassing `MatMatMulKer` entirely for +// B1/B2. +// +// Two further cases measure the AMX-native packed kernel (`ndarray_bf16_native_gemm.rs` + +// `ndarray_amx_native_pack.rs`) through the real `MatMatMulKer::run` path, at two different +// operand-preparation lifetimes -- these are NOT the same measurement and are compared against +// different B-cases above: +// P0 -- A and B both prepared (`prepare_one`) OUTSIDE the timed loop, matching B1's lifetime. +// Isolates tract's packed-execution abstraction tax over the raw AMX ceiling, with +// neither operand's preparation cost in the timed region. Compare against B1. +// P1 -- B prepared ONCE outside the timed loop and reused every iteration (a persistent +// weight, as in real inference); A's source stays f32 and is prepared (`prepare_one`) +// fresh INSIDE every timed iteration, once per whole matrix (not per tile) -- the same +// "runtime activation" lifetime B2 uses. This is the realistic inference-shaped +// acceptance metric. Compare against B2. +use criterion::*; +use ndarray::simd::{PackedBf16B, bf16_tile_gemm_16x16_packed, f32_to_bf16_batch_rne}; +use std::hint::black_box; +use tract_data::internal::*; +use tract_linalg::mmm::{AsInputValue, FusedSpec}; + +const TILE: usize = 16; + +/// Walks the m x k x n output in 16x16 tiles (m, n assumed multiples of 16; k padded to a +/// multiple of 32 by the caller) and accumulates each tile via `bf16_tile_gemm_16x16_packed`. +/// `a_bf16` is row-major `[m, k_padded]`; `b_packed` is one `PackedBf16B` per 16-column tile of +/// B, indexed `b_tiles[j_tile]`. No allocation, no conversion -- pure tile-primitive calls. +fn tiled_matmul_packed( + m: usize, + n: usize, + k_padded: usize, + a_bf16: &[u16], + b_tiles: &[PackedBf16B], + c: &mut [f32], +) { + let m_tiles = m / TILE; + let n_tiles = n / TILE; + let mut tile_c = [0f32; TILE * TILE]; + for it in 0..m_tiles { + let a_row_tile = &a_bf16[it * TILE * k_padded..(it + 1) * TILE * k_padded]; + for jt in 0..n_tiles { + tile_c.fill(0.0); + bf16_tile_gemm_16x16_packed(a_row_tile, &b_tiles[jt], &mut tile_c); + for i in 0..TILE { + for j in 0..TILE { + c[(it * TILE + i) * n + jt * TILE + j] = tile_c[i * TILE + j]; + } + } + } + } +} + +fn gap_decomposition(c: &mut Criterion) { + let mut group = c.benchmark_group("amx_bf16_gap_decomposition"); + + for &(m, k, n) in &[(512usize, 512usize, 512usize), (1024, 1024, 1024)] { + group.throughput(Throughput::Elements((2 * m * k * n) as u64)); + let k_padded = k.next_multiple_of(32); + let m_tiles = m / TILE; + let n_tiles = n / TILE; + + // ---- B0: existing asm kernel through the normal MatMatMulKer path ---- + { + let mmm = tract_linalg::x86_64::mmm::avx512_mmm_f32_16x8.mmm(); + group.bench_with_input( + BenchmarkId::new("B0_asm_16x8", format!("{m}x{k}x{n}")), + &(m, k, n), + |be, &(m, k, n)| { + let packing = &mmm.packings()[0]; + let a = Tensor::zero::<f32>(&[m, k]).unwrap(); + let pa = packing.0.prepare_one(&a, 1, 0).unwrap(); + let b = Tensor::zero::<f32>(&[k, n]).unwrap(); + let pb = packing.1.prepare_one(&b, 0, 1).unwrap(); + let mut cc = Tensor::zero::<f32>(&[n, m]).unwrap(); + be.iter(|| unsafe { + mmm.run( + m, + n, + &[ + FusedSpec::AddMatMul { + a: AsInputValue::Borrowed(&*pa), + b: AsInputValue::Borrowed(&*pb), + packing: 0, + }, + FusedSpec::Store(mmm.c_view(Some(1), Some(0)).wrap(&cc.view_mut())), + ], + ) + .unwrap() + }); + }, + ); + } + + // ---- B1: raw AMX tile primitive, A and B fully pre-converted + pre-packed outside + // the timed loop. Isolates the tile arithmetic itself. ---- + { + let a_f32 = vec![0f32; m * k_padded]; + let mut a_bf16 = vec![0u16; m * k_padded]; + f32_to_bf16_batch_rne(&a_f32, &mut a_bf16); + + let b_f32 = vec![0f32; k_padded * n]; + let mut b_bf16_rm = vec![0u16; k_padded * n]; + f32_to_bf16_batch_rne(&b_f32, &mut b_bf16_rm); + // One PackedBf16B per 16-column tile of B, row-major over k_padded. + let mut b_tiles: Vec<PackedBf16B> = Vec::with_capacity(n_tiles); + for jt in 0..n_tiles { + let mut col_major = vec![0u16; k_padded * TILE]; + for kk in 0..k_padded { + for jj in 0..TILE { + col_major[kk * TILE + jj] = b_bf16_rm[kk * n + jt * TILE + jj]; + } + } + b_tiles.push(PackedBf16B::pack(&col_major, k_padded)); + } + let mut c_out = vec![0f32; m * n]; + + group.bench_function( + BenchmarkId::new("B1_raw_amx_prepacked", format!("{m}x{k}x{n}")), + |be| { + be.iter(|| { + tiled_matmul_packed(m, n, k_padded, &a_bf16, &b_tiles, &mut c_out); + black_box(&c_out); + }); + }, + ); + let _ = m_tiles; + } + + // ---- B2: A converted f32->bf16 inside the timed loop (runtime activation); + // B pre-converted + pre-packed outside (weight matrix packed once at load time). ---- + { + let a_f32 = vec![0f32; m * k_padded]; + + let b_f32 = vec![0f32; k_padded * n]; + let mut b_bf16_rm = vec![0u16; k_padded * n]; + f32_to_bf16_batch_rne(&b_f32, &mut b_bf16_rm); + let mut b_tiles: Vec<PackedBf16B> = Vec::with_capacity(n_tiles); + for jt in 0..n_tiles { + let mut col_major = vec![0u16; k_padded * TILE]; + for kk in 0..k_padded { + for jj in 0..TILE { + col_major[kk * TILE + jj] = b_bf16_rm[kk * n + jt * TILE + jj]; + } + } + b_tiles.push(PackedBf16B::pack(&col_major, k_padded)); + } + let mut c_out = vec![0f32; m * n]; + let mut a_bf16_scratch = vec![0u16; m * k_padded]; + + group.bench_function( + BenchmarkId::new("B2_activation_runtime_convert", format!("{m}x{k}x{n}")), + |be| { + be.iter(|| { + f32_to_bf16_batch_rne(&a_f32, &mut a_bf16_scratch); + tiled_matmul_packed(m, n, k_padded, &a_bf16_scratch, &b_tiles, &mut c_out); + black_box(&c_out); + }); + }, + ); + } + + // ---- B3: existing PR #5 kernel through MatMatMulKer -- included for cross-reference; + // see `ndarray_bf16_gemm.rs` for the primary measurement of this case. ---- + { + let mmm = tract_linalg::x86_64::mmm::ndarray_avx512_bf16_mmm_f32_16x16.mmm(); + group.bench_with_input( + BenchmarkId::new("B3_pr5_kernel_as_is", format!("{m}x{k}x{n}")), + &(m, k, n), + |be, &(m, k, n)| { + let packing = &mmm.packings()[0]; + let a = Tensor::zero::<f32>(&[m, k]).unwrap(); + let pa = packing.0.prepare_one(&a, 1, 0).unwrap(); + let b = Tensor::zero::<f32>(&[k, n]).unwrap(); + let pb = packing.1.prepare_one(&b, 0, 1).unwrap(); + let mut cc = Tensor::zero::<f32>(&[n, m]).unwrap(); + be.iter(|| unsafe { + mmm.run( + m, + n, + &[ + FusedSpec::AddMatMul { + a: AsInputValue::Borrowed(&*pa), + b: AsInputValue::Borrowed(&*pb), + packing: 0, + }, + FusedSpec::Store(mmm.c_view(Some(1), Some(0)).wrap(&cc.view_mut())), + ], + ) + .unwrap() + }); + }, + ); + } + + // ---- P0: AMX-native packed kernel through MatMatMulKer, A and B both prepared + // outside the timed loop -- compare against B1 (raw AMX ceiling, same lifetime). ---- + { + let mmm = tract_linalg::x86_64::mmm::ndarray_amx_native_bf16_mmm_f32_16x16.mmm(); + group.bench_with_input( + BenchmarkId::new("P0_amx_native_prepacked", format!("{m}x{k}x{n}")), + &(m, k, n), + |be, &(m, k, n)| { + let packing = &mmm.packings()[1]; + let a = Tensor::zero::<f32>(&[m, k]).unwrap(); + let pa = packing.0.prepare_one(&a, 1, 0).unwrap(); + let b = Tensor::zero::<f32>(&[k, n]).unwrap(); + let pb = packing.1.prepare_one(&b, 0, 1).unwrap(); + let mut cc = Tensor::zero::<f32>(&[n, m]).unwrap(); + be.iter(|| unsafe { + mmm.run( + m, + n, + &[ + FusedSpec::AddMatMul { + a: AsInputValue::Borrowed(&*pa), + b: AsInputValue::Borrowed(&*pb), + packing: 1, + }, + FusedSpec::Store(mmm.c_view(Some(1), Some(0)).wrap(&cc.view_mut())), + ], + ) + .unwrap() + }); + }, + ); + } + + // ---- P1: AMX-native packed kernel through MatMatMulKer, B prepared once outside the + // timed loop and reused (persistent weight); A re-prepared once per iteration inside + // the timed loop, once for the whole matrix (runtime activation) -- compare against B2 + // (same lifetime split). This is the realistic inference-shaped acceptance metric. ---- + { + let mmm = tract_linalg::x86_64::mmm::ndarray_amx_native_bf16_mmm_f32_16x16.mmm(); + group.bench_with_input( + BenchmarkId::new("P1_amx_native_runtime_a", format!("{m}x{k}x{n}")), + &(m, k, n), + |be, &(m, k, n)| { + let packing = &mmm.packings()[1]; + let a = Tensor::zero::<f32>(&[m, k]).unwrap(); + let b = Tensor::zero::<f32>(&[k, n]).unwrap(); + let pb = packing.1.prepare_one(&b, 0, 1).unwrap(); + let mut cc = Tensor::zero::<f32>(&[n, m]).unwrap(); + be.iter(|| unsafe { + let pa = packing.0.prepare_one(&a, 1, 0).unwrap(); + mmm.run( + m, + n, + &[ + FusedSpec::AddMatMul { + a: AsInputValue::Borrowed(&*pa), + b: AsInputValue::Borrowed(&*pb), + packing: 1, + }, + FusedSpec::Store(mmm.c_view(Some(1), Some(0)).wrap(&cc.view_mut())), + ], + ) + .unwrap(); + black_box(&cc); + }); + }, + ); + } + } + group.finish(); +} + +criterion_group!(benches, gap_decomposition); +criterion_main!(benches); diff --git a/linalg/benches/leaky_relu.rs b/linalg/benches/leaky_relu.rs index f0f0d37632..7093f68450 100644 --- a/linalg/benches/leaky_relu.rs +++ b/linalg/benches/leaky_relu.rs @@ -1,17 +1,18 @@ use criterion::*; use tract_data::prelude::*; +#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] use tract_linalg::element_wise::ElementWiseKer; fn leaky_relu_f16(c: &mut Criterion) { let mut group = c.benchmark_group("leaky_relu_f16"); group.throughput(Throughput::Elements(1024)); let mut input = unsafe { Tensor::uninitialized_aligned::<f16>(&[1024], 16).unwrap() }; - let input = input.as_slice_mut::<f16>().unwrap(); + let input = unsafe { input.as_slice_mut_unchecked::<f16>() }; let alpha = f16::from_f32(0.1); group.bench_function("rust", |b| b.iter(|| rust_fp16(input, alpha))); - group.bench_function("rust_with_f16", |b| b.iter(|| unsafe { rust_with_fp16(input, alpha) })); group.bench_function("linalg", |b| b.iter(|| linalg16(input, alpha))); + #[cfg(target_arch = "aarch64")] group.bench_function("linalg-asm", |b| { b.iter(|| tract_linalg::arm64::arm64fp16_leaky_relu_f16_16n::run(input, alpha)) }); @@ -24,14 +25,6 @@ fn rust_fp16(input: &mut [f16], alpha: f16) { } } -#[target_feature(enable = "fp16")] -#[inline(never)] -unsafe fn rust_with_fp16(input: &mut [f16], alpha: f16) { - for x in input { - *x = if *x > f16::ZERO { *x } else { *x * alpha } - } -} - #[inline(never)] fn linalg16(input: &mut [f16], alpha: f16) { tract_linalg::routines::Func::LeakyRelu @@ -44,14 +37,25 @@ fn linalg16(input: &mut [f16], alpha: f16) { fn leaky_relu_f32(c: &mut Criterion) { let mut group = c.benchmark_group("leaky_relu_f32"); group.throughput(Throughput::Elements(1024)); - let mut input = unsafe { Tensor::uninitialized_aligned::<f32>(&[1024], 16).unwrap() }; - let input = input.as_slice_mut::<f32>().unwrap(); + // 64-byte aligned: the AVX-512 kernels below load/store with `vmovaps`, which + // requires zmm-width (64-byte) alignment. + let mut input = unsafe { Tensor::uninitialized_aligned::<f32>(&[1024], 64).unwrap() }; + let input = unsafe { input.as_slice_mut_unchecked::<f32>() }; let alpha = 0.1f32; group.bench_function("rust", |b| b.iter(|| rust_fp32(input, alpha))); group.bench_function("linalg", |b| b.iter(|| linalg32(input, alpha))); + #[cfg(target_arch = "aarch64")] group.bench_function("linalg-asm", |b| { b.iter(|| tract_linalg::arm64::arm64simd_leaky_relu_f32_8n::run(input, alpha)) }); + #[cfg(target_arch = "x86_64")] + group.bench_function("linalg-avx512-asm", |b| { + b.iter(|| tract_linalg::x86_64::act::x86_64_avx512_leaky_relu_f32_64n::run(input, alpha)) + }); + #[cfg(target_arch = "x86_64")] + group.bench_function("linalg-ndarray-simd", |b| { + b.iter(|| tract_linalg::x86_64::act::x86_64_ndarray_leaky_relu_f32_16n::run(input, alpha)) + }); } #[inline(never)] diff --git a/linalg/benches/ndarray_bf16_gemm.rs b/linalg/benches/ndarray_bf16_gemm.rs new file mode 100644 index 0000000000..408fa4bb76 --- /dev/null +++ b/linalg/benches/ndarray_bf16_gemm.rs @@ -0,0 +1,53 @@ +// Compares the hand-tuned AVX-512 asm f32 GEMM kernel against the additive bf16-tile-based +// candidate (see linalg/src/x86_64/ndarray_bf16_gemm.rs) on a full matrix multiply -- the +// kernel's own panel-walking machinery loops its tile many times over m/n/k, so this measures +// the whole GEMM each candidate produces, not one microkernel tile call. +use criterion::*; +use tract_data::internal::*; +use tract_linalg::mmm::{AsInputValue, FusedSpec}; + +fn gemm_f32(c: &mut Criterion) { + let mut group = c.benchmark_group("gemm_f32_bf16_pilot"); + for &(m, k, n) in &[(512usize, 512usize, 512usize), (1024, 1024, 1024)] { + group.throughput(Throughput::Elements((2 * m * k * n) as u64)); + for (label, mmm) in [ + ("asm_16x8", tract_linalg::x86_64::mmm::avx512_mmm_f32_16x8.mmm()), + ( + "ndarray_bf16_16x16", + tract_linalg::x86_64::mmm::ndarray_avx512_bf16_mmm_f32_16x16.mmm(), + ), + ] { + group.bench_with_input( + BenchmarkId::new(label, format!("{m}x{k}x{n}")), + &(m, k, n), + |be, &(m, k, n)| { + let packing = &mmm.packings()[0]; + let a = Tensor::zero::<f32>(&[m, k]).unwrap(); + let pa = packing.0.prepare_one(&a, 1, 0).unwrap(); + let b = Tensor::zero::<f32>(&[k, n]).unwrap(); + let pb = packing.1.prepare_one(&b, 0, 1).unwrap(); + let mut cc = Tensor::zero::<f32>(&[n, m]).unwrap(); + be.iter(|| unsafe { + mmm.run( + m, + n, + &[ + FusedSpec::AddMatMul { + a: AsInputValue::Borrowed(&*pa), + b: AsInputValue::Borrowed(&*pb), + packing: 0, + }, + FusedSpec::Store(mmm.c_view(Some(1), Some(0)).wrap(&cc.view_mut())), + ], + ) + .unwrap() + }); + }, + ); + } + } + group.finish(); +} + +criterion_group!(benches, gemm_f32); +criterion_main!(benches); diff --git a/linalg/benches/ndarray_gemm.rs b/linalg/benches/ndarray_gemm.rs new file mode 100644 index 0000000000..30053d7591 --- /dev/null +++ b/linalg/benches/ndarray_gemm.rs @@ -0,0 +1,50 @@ +// Compares the hand-tuned AVX-512 asm 16x8 GEMM kernel against the additive +// ndarray-blas_gemm-backed candidate of the same tile geometry, on a full matrix multiply +// (not just one microkernel tile call): the kernel's own panel-walking machinery loops the +// tile many times over m/n/k, so this measures the whole GEMM each candidate produces. +use criterion::*; +use tract_data::internal::*; +use tract_linalg::mmm::{AsInputValue, FusedSpec}; + +fn gemm_f32(c: &mut Criterion) { + let mut group = c.benchmark_group("gemm_f32_16x8"); + for &(m, k, n) in &[(512usize, 512usize, 512usize), (1024, 1024, 1024)] { + group.throughput(Throughput::Elements((2 * m * k * n) as u64)); + for (label, mmm) in [ + ("asm", tract_linalg::x86_64::mmm::avx512_mmm_f32_16x8.mmm()), + ("ndarray", tract_linalg::x86_64::mmm::ndarray_avx512_mmm_f32_16x8.mmm()), + ] { + group.bench_with_input( + BenchmarkId::new(label, format!("{m}x{k}x{n}")), + &(m, k, n), + |be, &(m, k, n)| { + let packing = &mmm.packings()[0]; + let a = Tensor::zero::<f32>(&[m, k]).unwrap(); + let pa = packing.0.prepare_one(&a, 1, 0).unwrap(); + let b = Tensor::zero::<f32>(&[k, n]).unwrap(); + let pb = packing.1.prepare_one(&b, 0, 1).unwrap(); + let mut cc = Tensor::zero::<f32>(&[n, m]).unwrap(); + be.iter(|| unsafe { + mmm.run( + m, + n, + &[ + FusedSpec::AddMatMul { + a: AsInputValue::Borrowed(&*pa), + b: AsInputValue::Borrowed(&*pb), + packing: 0, + }, + FusedSpec::Store(mmm.c_view(Some(1), Some(0)).wrap(&cc.view_mut())), + ], + ) + .unwrap() + }); + }, + ); + } + } + group.finish(); +} + +criterion_group!(benches, gemm_f32); +criterion_main!(benches); diff --git a/linalg/src/arm32/mod.rs b/linalg/src/arm32/mod.rs index 9309b5778f..6a4591721c 100644 --- a/linalg/src/arm32/mod.rs +++ b/linalg/src/arm32/mod.rs @@ -47,15 +47,15 @@ fn neon_mmv_f32(suitable: &[Suitable], query: &Query) -> Option<&'static str> { Some(m) if m < 32 => { cortex_a7_mmv_linear::linear_model().preferred(suitable, Some(m), query.k, Some(1)) } - _ => Some(&armv7neon::armv7neon_mmm_f32_32x1_cortexa7.name.as_str()), + _ => Some(armv7neon::armv7neon_mmm_f32_32x1_cortexa7.name.as_str()), }, 0xc09 => match query.m { Some(m) if m < 32 => { cortex_a9_mmv_linear::linear_model().preferred(suitable, Some(m), query.k, Some(1)) } - _ => Some(&armv7neon::armv7neon_mmm_f32_32x1_cortexa9.name.as_str()), + _ => Some(armv7neon::armv7neon_mmm_f32_32x1_cortexa9.name.as_str()), }, - _ => Some(&armv7neon::armv7neon_mmm_f32_32x1_generic.name.as_str()), + _ => Some(armv7neon::armv7neon_mmm_f32_32x1_generic.name.as_str()), } } @@ -80,8 +80,8 @@ fn preferred( match (dt, query.n) { (DatumType::F32, Some(1)) => neon_mmv_f32(suitable, query), (DatumType::F32, _) => neon_mmm_f32(suitable, query), - (DatumType::I32, Some(1)) => Some(&armv7neon::armv7neon_mmm_i32_32x1.name.as_str()), - (DatumType::I32, _) => Some(&armv7neon::armv7neon_mmm_i32_8x4.name.as_str()), + (DatumType::I32, Some(1)) => Some(armv7neon::armv7neon_mmm_i32_32x1.name.as_str()), + (DatumType::I32, _) => Some(armv7neon::armv7neon_mmm_i32_8x4.name.as_str()), _ => None, } } diff --git a/linalg/src/frame/mmm/macros.rs b/linalg/src/frame/mmm/macros.rs index 011d95fd30..6cc6ac3ca9 100644 --- a/linalg/src/frame/mmm/macros.rs +++ b/linalg/src/frame/mmm/macros.rs @@ -79,6 +79,7 @@ macro_rules! MMMRustKernel { $(boost($boost:expr))? $(store($($store:ty),*))? $(row_major_store($rms:expr))? + $(lossy_no_exact_tests($lossy_no_exact_tests:literal))? ) => { paste! { mod [<sys_ $id>] { @@ -101,6 +102,7 @@ macro_rules! MMMRustKernel { $(boost($boost))? $(store($($store),*))? $(row_major_store($rms))? + $(lossy_no_exact_tests($lossy_no_exact_tests))? ); } } @@ -120,6 +122,7 @@ macro_rules! MMMKernel { $(boost($boost:expr))? $(store($($store:ty),*))? $(row_major_store($rms:expr))? + $(lossy_no_exact_tests($lossy_no_exact_tests:literal))? ) => { paste! { lazy_static::lazy_static! { @@ -160,8 +163,9 @@ macro_rules! MMMKernel { #[cfg(test)] mod [<test_$id>] { + #[allow(unused_imports)] use super::$id; - test_mmm_kernel!($ti, &*super::$id); + maybe_test_mmm_kernel!($(lossy_no_exact_tests($lossy_no_exact_tests))? ; $ti, &*super::$id); $(mmm_packed_packed_tests!(&*super::$id, $pid : $pnum);)* $($(mmm_store_test!(&*super::$id, $store);)*)? } diff --git a/linalg/src/frame/mmm/tests/mod.rs b/linalg/src/frame/mmm/tests/mod.rs index 6a46a5a787..2c8d6137ab 100644 --- a/linalg/src/frame/mmm/tests/mod.rs +++ b/linalg/src/frame/mmm/tests/mod.rs @@ -29,6 +29,21 @@ macro_rules! test_mmm_kernel { }; } +/// Gate for `MMMKernel!`'s `lossy_no_exact_tests` flag: a kernel whose accumulate arithmetic +/// isn't exact against its declared datum type (e.g. an internal bf16 truncation) can't pass +/// `test_mmm_kernel!`'s bit-exact suite by construction, and needs its own tolerance-based +/// tests instead of this one. +#[cfg(test)] +macro_rules! maybe_test_mmm_kernel { + (lossy_no_exact_tests(true) ; $ti:tt, $ker:expr) => {}; + (lossy_no_exact_tests(false) ; $ti:tt, $ker:expr) => { + test_mmm_kernel!($ti, $ker); + }; + (; $ti:tt, $ker:expr) => { + test_mmm_kernel!($ti, $ker); + }; +} + #[macro_export] macro_rules! test_mmm_kernel_f16 { ($ker: expr) => { diff --git a/linalg/src/generic/reduce.rs b/linalg/src/generic/reduce.rs index 8843526096..8a2ce184de 100644 --- a/linalg/src/generic/reduce.rs +++ b/linalg/src/generic/reduce.rs @@ -227,8 +227,8 @@ pub mod softmax_l2 { #[inline] fn exp_sum_impl(x: &mut [f32], max: f32) -> f32 { let mut acc = [0f32; 4]; - let mut it = x.chunks_exact_mut(4); - for c in &mut it { + let (chunks, remainder) = x.as_chunks_mut::<4>(); + for c in chunks.iter_mut() { for (j, v) in c.iter_mut().enumerate() { let y = accurate_exp_f32(*v - max); *v = y; @@ -236,7 +236,7 @@ pub mod softmax_l2 { } } let mut sum = (acc[0] + acc[1]) + (acc[2] + acc[3]); - for v in it.into_remainder().iter_mut() { + for v in remainder.iter_mut() { let y = accurate_exp_f32(*v - max); *v = y; sum += y; diff --git a/linalg/src/x86_64/act.rs b/linalg/src/x86_64/act.rs index 018ba49042..2ab83a0ee7 100644 --- a/linalg/src/x86_64/act.rs +++ b/linalg/src/x86_64/act.rs @@ -160,6 +160,42 @@ unsafe fn x86_64_avx512_leaky_relu_f32_64n_run(buf: &mut [f32], alpha: f32) { } } +// leaky_relu(x) = x > 0 ? x : alpha * x, built on `ndarray::simd::F32x16` instead of +// hand-rolled intrinsics/asm. Additional candidate alongside +// `x86_64_avx512_leaky_relu_f32_64n` above: `F32x16` resolves at ndarray's own compile +// time to a native `__m512`-backed type under `avx512f`, or to a pair of `__m256` +// halves otherwise, so this single body serves both without tract choosing between +// them itself. Declared at `X86_64Avx2` because that is what a tract build (no global +// `avx512f` target feature) actually links against. +routine_ew_rust!(x86_64; + f32, + x86_64_ndarray_leaky_relu_f32_16n, + 16, + 16, + #[inline(never)] + fn run(buf: &mut [f32], alpha: f32) { + debug_assert!(buf.len() % Self::nr() == 0); + debug_assert!(buf.as_ptr() as usize % Self::alignment_bytes() == 0); + x86_64_ndarray_leaky_relu_f32_16n_run(buf, alpha) + }, + func(LeakyRelu), + param, + isa(X86_64Avx2) +); + +#[cfg(target_arch = "x86_64")] +fn x86_64_ndarray_leaky_relu_f32_16n_run(buf: &mut [f32], alpha: f32) { + use ndarray::simd::F32x16; + let alpha_v = F32x16::splat(alpha); + let zero = F32x16::splat(0.0); + for chunk in buf.as_chunks_mut::<{ F32x16::LANES }>().0 { + let x = F32x16::from_slice(chunk.as_slice()); + let scaled = x * alpha_v; + let y = x.simd_gt(zero).select(x, scaled); + y.copy_to_slice(chunk.as_mut_slice()); + } +} + // Tanh-form GELU (pow=3) matching tract's GeluApproximate: // gelu(x) = 0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3))) // Composed at the kernel level (mirrors arm64): save the original x, compute diff --git a/linalg/src/x86_64/mmm.rs b/linalg/src/x86_64/mmm.rs index 3818885d2f..094cb90039 100644 --- a/linalg/src/x86_64/mmm.rs +++ b/linalg/src/x86_64/mmm.rs @@ -127,6 +127,72 @@ MMMExternKernel!(x86_64; avx512_mmm_f32_128x1<f32>(128, 1)@(512,4) isa(X86_64Avx MMMExternKernel!(x86_64; avx512_mmm_f32_16x1 <f32>( 16, 1)@(512,4) isa(X86_64Avx512f)); MMMExternKernel!(x86_64; avx512_mmm_f32_16x12<f32>( 16,12)@(512,4) isa(X86_64Avx512f)); MMMExternKernel!(x86_64; avx512_mmm_f32_16x8 <f32>( 16, 8)@(512,4) isa(X86_64Avx512f)); + +// Pilot: same 16x8 tile geometry as avx512_mmm_f32_16x8 above, so the two are directly +// comparable, but the AddMatMul accumulation calls into the AdaWorldAPI ndarray fork's +// `BlasLevel3::blas_gemm` instead of hand-written asm. +// +// Deliberately NOT registered through the `(x86_64; ...)` macro sugar, which also +// `inventory::submit!`s an `MmmRoutine` that `MmmDispatch::native()` (and so +// `core::ops::einsum::kernel_selection::strategize`'s symbolic-N fallback, which picks the +// largest-`nr` kernel per packing group and bypasses `preferred`/boost entirely) would +// discover automatically. Calling the lower-level form directly skips that +// `inventory::submit!`, so the kernel stays reachable for direct construction (this pilot's +// own bench/tests) but invisible to automatic dispatch. +MMMRustKernel!(ndarray_gemm::kernel::<16, 8> => ndarray_avx512_mmm_f32_16x8<f32>(16, 8) + built(cfg!(target_arch = "x86_64")) arch(Some(crate::isa::Arch::X86_64)) isa(X86_64Avx512f)); + +// Pilot v2: 16x16 tile geometry (matching ndarray's fixed bf16_tile_gemm_16x16 shape). The +// AddMatMul accumulation truncates operands to bf16 and calls the AdaWorldAPI ndarray fork's +// `simd::bf16_tile_gemm_16x16_packed` (AMX / AVX-512-VNNI-bf16 / FMA-polyfill tiers) instead of +// hand-written asm -- see ndarray_bf16_gemm.rs's module doc for the precision tradeoff and the +// honest read on how this compares structurally and numerically to pilot v1's blas_gemm +// candidate. +// +// Deliberately NOT registered through the `(x86_64; ...)` macro sugar, which also +// `inventory::submit!`s an `MmmRoutine` that `MmmDispatch::native()` (and so +// `core::ops::einsum::kernel_selection::strategize`) discovers automatically. This kernel's +// nr=16 is larger than every existing f32 AVX-512 kernel (max nr=12, `avx512_mmm_f32_16x12`), +// so the symbolic-N grouped fallback in `strategize` -- which picks the largest-`nr` kernel +// per packing group, bypassing `preferred`/boost entirely -- would silently select this +// bf16-truncating kernel for real f32 models with a dynamic N dimension. Calling the lower-level +// form directly skips that `inventory::submit!`, so the kernel stays reachable for direct +// construction (this pilot's own bench/tests) but invisible to automatic dispatch -- the +// concrete guarantee "purely additive, no behavior change" actually requires. +// +// `lossy_no_exact_tests(true)`: this kernel's accumulate path truncates its f32 operands to +// bf16 before compute, so it cannot pass `MMMKernel!`'s auto-generated bit-exact test suite +// (`test_mmm_kernel!`) by construction -- that suite compares against an exact f32 reference. +// `ndarray_bf16_gemm.rs`'s own `bf16_tolerance` module is this kernel's real correctness test. +MMMRustKernel!(ndarray_bf16_gemm::kernel => ndarray_avx512_bf16_mmm_f32_16x16<f32>(16, 16) + built(cfg!(target_arch = "x86_64")) arch(Some(crate::isa::Arch::X86_64)) isa(X86_64Avx512f) + lossy_no_exact_tests(true)); + +// AMX-native sibling of the kernel above: `AddMatMul` consumes operands already converted to +// bf16 and VNNI-packed at `prepare_one` time (`ndarray_amx_native_pack.rs`'s +// `NdarrayAmxBf16A`/`NdarrayAmxBf16B`, packing index 1) instead of redoing that work on every +// tile call -- see `ndarray_bf16_native_gemm.rs`'s module doc. Same non-`inventory::submit!` +// registration as the kernel above, for the same reason: reachable only by direct +// construction, never through `MmmDispatch::native()`. +// +// `lossy_no_exact_tests(true)` for the same reason as above; packing 1's own +// `mmm_packed_packed_tests!` (added by the `packing[1]` clause) is this kernel's auto-generated +// correctness coverage, on top of `ndarray_bf16_native_gemm.rs`'s own `bf16_tolerance` module. +// +// `ndarray_amx_native_pack.rs` and `ndarray_bf16_native_gemm.rs` are both +// `#![cfg(target_arch = "x86_64")]` at the module level (unlike the other pilot kernels in this +// file, which stay compiled everywhere `feature = "foreign-inventory"` reaches with an internal +// stub) -- this registration must match that gate or a foreign-inventory build on another arch +// fails to resolve the now-nonexistent symbols. +#[cfg(target_arch = "x86_64")] +MMMRustKernel!(ndarray_bf16_native_gemm::kernel => ndarray_amx_native_bf16_mmm_f32_16x16<f32>(16, 16) + built(cfg!(target_arch = "x86_64")) arch(Some(crate::isa::Arch::X86_64)) isa(X86_64Avx512f) + packing[1] = amx_bf16_native => |k| k.with_packing( + crate::x86_64::ndarray_amx_native_pack::NdarrayAmxBf16A::new(16), + crate::x86_64::ndarray_amx_native_pack::NdarrayAmxBf16B::new(16), + ); + lossy_no_exact_tests(true)); + MMMExternKernel!(x86_64; avx512_mmm_f32_32x6 <f32>( 32, 6)@(512,4) isa(X86_64Avx512f)); MMMExternKernel!(x86_64; avx512_mmm_f32_32x5 <f32>( 32, 5)@(512,4) isa(X86_64Avx512f)); MMMExternKernel!(x86_64; avx512_mmm_f32_48x4 <f32>( 48, 4)@(512,4) isa(X86_64Avx512f)); @@ -424,7 +490,7 @@ fn avx2_preferred( ) -> Option<&'static str> { match (dt, query.n) { (DatumType::I32, Some(1)) => None, - (DatumType::I32, _) => Some(&mmm::avx2_mmm_i32_8x8.name.as_str()), + (DatumType::I32, _) => Some(mmm::avx2_mmm_i32_8x8.name.as_str()), _ => None, } } diff --git a/linalg/src/x86_64/mod.rs b/linalg/src/x86_64/mod.rs index aa57af3974..2786d324a6 100644 --- a/linalg/src/x86_64/mod.rs +++ b/linalg/src/x86_64/mod.rs @@ -1,5 +1,10 @@ pub mod mmm; +mod ndarray_amx_native_pack; +mod ndarray_bf16_gemm; +mod ndarray_bf16_native_gemm; +mod ndarray_gemm; + mod amd_avx512_linear; mod amd_fma_linear; mod intel_avx512_linear; diff --git a/linalg/src/x86_64/ndarray_amx_native_pack.rs b/linalg/src/x86_64/ndarray_amx_native_pack.rs new file mode 100644 index 0000000000..a3dcfa5c45 --- /dev/null +++ b/linalg/src/x86_64/ndarray_amx_native_pack.rs @@ -0,0 +1,427 @@ +#![cfg(target_arch = "x86_64")] +#![allow(clippy::needless_range_loop)] +//! Packing formats that hold the ndarray AMX-native bf16 tile representation +//! built once at `prepare_one`/`prepare_one_view` time, so the kernel body in +//! `ndarray_bf16_native_gemm.rs` never converts or VNNI-packs anything inside +//! `AddMatMul`. +//! +//! Panel geometry mirrors `amx_bf16.rs`'s `PackedAmxBf16A`/`PackedBf16K2` -- +//! r=16, K padded to a multiple of 32 -- but panels are stored as owned Rust +//! values (`Vec<u16>` row-major bf16 for A, `ndarray::simd::PackedBf16B` for +//! B) rather than raw bytes in a `Blob`: the consuming kernel calls +//! `ndarray::simd::bf16_tile_gemm_16x16_packed`'s typed API directly, so +//! there is nothing to gain from a byte-blob indirection and every extra +//! layer would be an extra copy. +//! +//! `AMX_BF16_A` is meant for activation-lifetime operands (converted once +//! per matrix, reused across every tile of that matmul); `AMX_BF16_B` is +//! meant for constant-weight-lifetime operands (converted and VNNI-packed +//! once, reused across every matmul that shares the weight). + +use std::alloc::Layout; +use std::fmt::Display; +use std::hash::{Hash, Hasher}; + +use tract_data::internal::*; + +use ndarray::simd::{PackedBf16B, f32_to_bf16_batch_rne}; + +use crate::WeightType; +use crate::frame::mmm::{ + EagerPackedInput, MMMInputFormat, MMMInputValue, PackedExoticFact, PackedMatrixStorage, +}; + +const R: usize = 16; + +fn k_padded(k: usize) -> usize { + k.next_multiple_of(32) +} + +/// Round every f32 element of `tensor` through bf16 (round-to-nearest-even), +/// matching what the packers in this module do at pack time. Non-f32 tensors +/// pass through unchanged. Lets a reference f32 matmul reproduce the +/// kernel's bf16 rounding. +fn simulate_bf16_precision_loss(mut tensor: Tensor) -> TractResult<Tensor> { + if tensor.datum_type() == f32::datum_type() { + let mut plain = tensor.try_as_plain_mut()?; + let slice = plain.as_slice_mut::<f32>()?; + let mut bits = vec![0u16; slice.len()]; + f32_to_bf16_batch_rne(slice, &mut bits); + for (v, b) in slice.iter_mut().zip(bits.iter()) { + *v = f32::from_bits((*b as u32) << 16); + } + } + Ok(tensor) +} + +/// Packing format for the AMX-native bf16 kernel's A operand: bf16-converted, +/// panel-native (row-major `[16, k_padded]` per panel) data, built once per +/// `prepare_one`/`prepare_one_view` call. Never carries partially-converted +/// or per-tile state -- the whole matrix is converted in one +/// `f32_to_bf16_batch_rne` pass per panel row at pack time. +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct NdarrayAmxBf16A { + r: usize, +} + +impl NdarrayAmxBf16A { + pub fn new(r: usize) -> Self { + assert_eq!(r, R, "ndarray's bf16 tile primitive is fixed at r=16"); + NdarrayAmxBf16A { r } + } +} + +impl Display for NdarrayAmxBf16A { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "NdarrayAmxBf16A[{}]", self.r) + } +} + +impl MMMInputFormat for NdarrayAmxBf16A { + fn prepare_tensor(&self, t: &Tensor, k_axis: usize, mn_axis: usize) -> TractResult<Tensor> { + Ok(PackedMatrixStorage::new(self.prepare_one(t, k_axis, mn_axis)?) + .into_tensor(t.datum_type())) + } + + fn prepare_one_view( + &self, + t: &TensorView, + k_axis: usize, + mn_axis: usize, + ) -> TractResult<Box<dyn MMMInputValue>> { + let k = t.shape()[k_axis]; + let mn = t.shape()[mn_axis]; + let kp = k_padded(k); + let panels_count = mn.div_ceil(self.r); + let st = t.strides(); + let (ks, ms) = (st[k_axis], st[mn_axis]); + + let mut panels: Vec<Vec<u16>> = Vec::with_capacity(panels_count); + let mut row_f32 = vec![0f32; k]; + unsafe { + let src = t.as_ptr_unchecked::<f32>(); + for p in 0..panels_count { + let pw = self.r.min(mn - p * self.r); + let mn0 = (p * self.r) as isize; + let mut panel = vec![0u16; self.r * kp]; + for lm in 0..pw { + let srow_base = src.offset((mn0 + lm as isize) * ms); + for kk in 0..k { + row_f32[kk] = *srow_base.offset(kk as isize * ks); + } + f32_to_bf16_batch_rne(&row_f32, &mut panel[lm * kp..lm * kp + k]); + } + panels.push(panel); + } + } + + Ok(Box::new(NdarrayAmxBf16AValue { + fact: PackedExoticFact { format: Box::new(self.clone()), mn: mn.to_dim(), k }, + format: self.clone(), + panels, + k, + mn, + })) + } + + fn k_alignment(&self) -> usize { + 32 + } + + fn r(&self) -> usize { + self.r + } + + fn precursor(&self) -> WeightType { + WeightType::Plain(f32::datum_type()) + } + + fn simulate_precision_loss(&self, tensor: Tensor) -> TractResult<Tensor> { + simulate_bf16_precision_loss(tensor) + } + + fn merge_with<'o, 'a: 'o, 'b: 'o>( + &'a self, + o: &'b dyn MMMInputFormat, + ) -> Option<&'o dyn MMMInputFormat> { + o.downcast_ref::<NdarrayAmxBf16A>().filter(|x| x.r == self.r).map(|_| self as _) + } + + fn mem_size(&self, k: TDim, mn: TDim) -> TDim { + mn.divceil(self.r) * (self.r * k_padded(k.to_usize().unwrap_or(0)) * 2) + } + + fn extract_at_mn_f16(&self, _: &EagerPackedInput, _: usize, _: &mut [f16]) -> TractResult<()> { + bail!("no f16 extract") + } + + fn extract_at_mn_f32(&self, _: &EagerPackedInput, _: usize, _: &mut [f32]) -> TractResult<()> { + bail!("no f32 extract") + } +} + +/// One prepared A operand: `panels[p]` is row-major bf16 `[16, k_padded]`, +/// ready to feed `ndarray::simd::bf16_tile_gemm_16x16_packed` as `a_bf16` +/// with no further conversion. +#[derive(Clone, Debug)] +pub struct NdarrayAmxBf16AValue { + fact: PackedExoticFact, + format: NdarrayAmxBf16A, + panels: Vec<Vec<u16>>, + k: usize, + mn: usize, +} + +impl Hash for NdarrayAmxBf16AValue { + fn hash<H: Hasher>(&self, state: &mut H) { + self.format.hash(state); + self.k.hash(state); + self.mn.hash(state); + self.panels.hash(state); + } +} + +impl PartialEq for NdarrayAmxBf16AValue { + fn eq(&self, other: &Self) -> bool { + self.format == other.format + && self.k == other.k + && self.mn == other.mn + && self.panels == other.panels + } +} +impl Eq for NdarrayAmxBf16AValue {} + +impl Display for NdarrayAmxBf16AValue { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{} value (mn={} k={})", self.format, self.mn, self.k) + } +} + +impl MMMInputValue for NdarrayAmxBf16AValue { + fn format(&self) -> &dyn MMMInputFormat { + &self.format + } + fn scratch_panel_buffer_layout(&self) -> Option<Layout> { + None + } + fn panel_bytes(&self, i: usize, _buffer: Option<*mut u8>) -> TractResult<*const u8> { + Ok(self.panels[i].as_ptr() as *const u8) + } + fn mn(&self) -> usize { + self.mn + } + fn k(&self) -> usize { + self.k + } + fn exotic_fact(&self) -> &dyn ExoticFact { + &self.fact + } + fn extract_at_mn_f16(&self, _mn: usize, _slice: &mut [f16]) -> TractResult<()> { + bail!("no f16 extract") + } + fn extract_at_mn_f32(&self, _mn: usize, _slice: &mut [f32]) -> TractResult<()> { + bail!("no f32 extract") + } +} + +/// Packing format for the AMX-native bf16 kernel's B operand: bf16-converted +/// AND VNNI-packed (`ndarray::simd::PackedBf16B`) once per +/// `prepare_one`/`prepare_one_view` call, reusable across every matmul that +/// shares the packed weight -- the shape a constant weight tensor would be +/// packed into once at model-load time. +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct NdarrayAmxBf16B { + r: usize, +} + +impl NdarrayAmxBf16B { + pub fn new(r: usize) -> Self { + assert_eq!(r, R, "ndarray's bf16 tile primitive is fixed at r=16"); + NdarrayAmxBf16B { r } + } +} + +impl Display for NdarrayAmxBf16B { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "NdarrayAmxBf16B[{}]", self.r) + } +} + +impl MMMInputFormat for NdarrayAmxBf16B { + fn prepare_tensor(&self, t: &Tensor, k_axis: usize, mn_axis: usize) -> TractResult<Tensor> { + Ok(PackedMatrixStorage::new(self.prepare_one(t, k_axis, mn_axis)?) + .into_tensor(t.datum_type())) + } + + fn prepare_one_view( + &self, + t: &TensorView, + k_axis: usize, + mn_axis: usize, + ) -> TractResult<Box<dyn MMMInputValue>> { + let k = t.shape()[k_axis]; + let mn = t.shape()[mn_axis]; + let kp = k_padded(k); + let panels_count = mn.div_ceil(self.r); + let st = t.strides(); + let (ks, ms) = (st[k_axis], st[mn_axis]); + + let mut panels: Vec<PackedBf16B> = Vec::with_capacity(panels_count); + let mut row_major_bf16 = vec![0u16; kp * self.r]; + let mut row_f32 = vec![0f32; self.r]; + unsafe { + let src = t.as_ptr_unchecked::<f32>(); + for p in 0..panels_count { + let pw = self.r.min(mn - p * self.r); + let mn0 = (p * self.r) as isize; + row_major_bf16.fill(0); + for kk in 0..k { + let srow_base = src.offset(kk as isize * ks + mn0 * ms); + for lm in 0..pw { + row_f32[lm] = *srow_base.offset(lm as isize * ms); + } + let mut row_bf16 = [0u16; R]; + f32_to_bf16_batch_rne(&row_f32[..pw], &mut row_bf16[..pw]); + row_major_bf16[kk * self.r..kk * self.r + pw].copy_from_slice(&row_bf16[..pw]); + } + panels.push(PackedBf16B::pack(&row_major_bf16, kp)); + } + } + + Ok(Box::new(NdarrayAmxBf16BValue { + fact: PackedExoticFact { format: Box::new(self.clone()), mn: mn.to_dim(), k }, + format: self.clone(), + panels, + k, + mn, + })) + } + + fn k_alignment(&self) -> usize { + 32 + } + + fn r(&self) -> usize { + self.r + } + + fn precursor(&self) -> WeightType { + WeightType::Plain(f32::datum_type()) + } + + fn simulate_precision_loss(&self, tensor: Tensor) -> TractResult<Tensor> { + simulate_bf16_precision_loss(tensor) + } + + fn merge_with<'o, 'a: 'o, 'b: 'o>( + &'a self, + o: &'b dyn MMMInputFormat, + ) -> Option<&'o dyn MMMInputFormat> { + o.downcast_ref::<NdarrayAmxBf16B>().filter(|x| x.r == self.r).map(|_| self as _) + } + + fn mem_size(&self, k: TDim, mn: TDim) -> TDim { + mn.divceil(self.r) * (k_padded(k.to_usize().unwrap_or(0)) * self.r * 2) + } + + fn extract_at_mn_f16(&self, _: &EagerPackedInput, _: usize, _: &mut [f16]) -> TractResult<()> { + bail!("no f16 extract") + } + + fn extract_at_mn_f32(&self, _: &EagerPackedInput, _: usize, _: &mut [f32]) -> TractResult<()> { + bail!("no f32 extract") + } +} + +/// One prepared B operand: `panels[p]` is an `ndarray::simd::PackedBf16B` +/// ready to feed `bf16_tile_gemm_16x16_packed` directly, with no further +/// conversion or VNNI packing. +pub struct NdarrayAmxBf16BValue { + fact: PackedExoticFact, + format: NdarrayAmxBf16B, + panels: Vec<PackedBf16B>, + k: usize, + mn: usize, +} + +impl Clone for NdarrayAmxBf16BValue { + fn clone(&self) -> Self { + NdarrayAmxBf16BValue { + fact: self.fact.clone(), + format: self.format.clone(), + panels: self + .panels + .iter() + .map(|p| PackedBf16B::from_le_bytes(p.as_le_bytes(), p.k())) + .collect(), + k: self.k, + mn: self.mn, + } + } +} + +impl std::fmt::Debug for NdarrayAmxBf16BValue { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{} ({} panels)", self.format, self.panels.len()) + } +} + +impl Hash for NdarrayAmxBf16BValue { + fn hash<H: Hasher>(&self, state: &mut H) { + self.format.hash(state); + self.k.hash(state); + self.mn.hash(state); + for p in &self.panels { + p.data().hash(state); + p.k().hash(state); + } + } +} + +impl PartialEq for NdarrayAmxBf16BValue { + fn eq(&self, other: &Self) -> bool { + self.format == other.format + && self.k == other.k + && self.mn == other.mn + && self.panels.len() == other.panels.len() + && self + .panels + .iter() + .zip(other.panels.iter()) + .all(|(a, b)| a.k() == b.k() && a.data() == b.data()) + } +} +impl Eq for NdarrayAmxBf16BValue {} + +impl Display for NdarrayAmxBf16BValue { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{} value (mn={} k={})", self.format, self.mn, self.k) + } +} + +impl MMMInputValue for NdarrayAmxBf16BValue { + fn format(&self) -> &dyn MMMInputFormat { + &self.format + } + fn scratch_panel_buffer_layout(&self) -> Option<Layout> { + None + } + fn panel_bytes(&self, i: usize, _buffer: Option<*mut u8>) -> TractResult<*const u8> { + Ok(&self.panels[i] as *const PackedBf16B as *const u8) + } + fn mn(&self) -> usize { + self.mn + } + fn k(&self) -> usize { + self.k + } + fn exotic_fact(&self) -> &dyn ExoticFact { + &self.fact + } + fn extract_at_mn_f16(&self, _mn: usize, _slice: &mut [f16]) -> TractResult<()> { + bail!("no f16 extract") + } + fn extract_at_mn_f32(&self, _mn: usize, _slice: &mut [f32]) -> TractResult<()> { + bail!("no f32 extract") + } +} diff --git a/linalg/src/x86_64/ndarray_bf16_gemm.rs b/linalg/src/x86_64/ndarray_bf16_gemm.rs new file mode 100644 index 0000000000..8de0c696b7 --- /dev/null +++ b/linalg/src/x86_64/ndarray_bf16_gemm.rs @@ -0,0 +1,357 @@ +#![allow(clippy::needless_range_loop)] +//! An f32 GEMM `MatMatMulKer` body whose `AddMatMul` step truncates its operands to bf16 and +//! calls into the AdaWorldAPI ndarray fork's `simd::bf16_tile_gemm_16x16_packed` tile primitive +//! (AMX `TDPBF16PS` → AVX-512 `VDPBF16PS` → decode+FMA polyfill, selected at runtime) as an +//! additional candidate alongside the hand-tuned AVX-512 asm kernels and the f32-exact +//! `ndarray_avx512_mmm_f32_16x8` (`ndarray_gemm.rs`). +//! +//! Registered outside automatic dispatch (see `mmm.rs`'s registration comment for this kernel): +//! this kernel's `AddMatMul` step allocates and VNNI-packs its A/B operands once per output-tile +//! call (that is the granularity `MatMatMulKer`'s fused-op interpreter calls a kernel body at), +//! reachable only by direct construction, not through `MmmDispatch::native()`. +//! +//! **Precision, stated plainly:** the accumulate arithmetic (`C += A·B`) is bit-exact across +//! all three `bf16_tile_gemm` tiers for bf16-exact-integer operands with accumulation below +//! 2^24 (verified by `assert_eq!` parity tests in ndarray's own `hpc::bf16_tile_gemm`), and for +//! general float operands the tiers agree with each other exactly up to accumulation order — +//! this kernel introduces **no additional lossiness of its own**. The precision this kernel +//! trades away versus the native f32 asm kernel is entirely the one-time f32→bf16 truncation of +//! the input operands themselves before they reach any tile primitive: bf16 keeps a 7-bit +//! mantissa against f32's 23-bit, so every element of A and B loses precision at pack time, not +//! merely at accumulation time. This is a real, user-visible precision change for a general +//! inference engine and must not be read as "approximate GEMM" (the arithmetic is not +//! approximate) or as "safe to swap in for f32 workloads" (real model weights are not +//! bf16-exact integers, so the tier-parity bit-exactness above does not extend to them). +//! +//! Goes through `ndarray::simd::*` (`f32_to_bf16_batch_rne`, `PackedBf16B`, +//! `bf16_tile_gemm_16x16_packed`, `bf16_tile_gemm_tier`), the canonical consumer-facing +//! re-export, never `ndarray::hpc::bf16_tile_gemm::*` directly — see the ndarray fork's own +//! `CLAUDE.md` ("all SIMD from `ndarray::simd`"). +//! +//! Tile shape is fixed by the ndarray primitive at M=16, N=16, K a multiple of 32, so this +//! kernel is registered at the matching (16, 16) `MatMatMulKer` geometry rather than reusing +//! pilot v1's 16x8 -- packed A/B panels are padded up to the next multiple of 32 in K with +//! zero rows/columns, which contribute nothing to the accumulation. + +#[cfg(target_arch = "x86_64")] +use ndarray::simd::{PackedBf16B, bf16_tile_gemm_16x16_packed, f32_to_bf16_batch_rne}; + +use crate::frame::mmm::FusedKerSpec; +use crate::frame::mmm::OutputStoreKer; + +macro_rules! scalar { + ($ab: expr, $m: expr, $f: expr) => { + for i in 0..$ab.len() { + for j in 0..$ab[0].len() { + $ab[i][j] = $f($m, $ab[i][j]) + } + } + }; +} + +macro_rules! per_row { + ($ab: expr, $m: expr, $f: expr) => { + for i in 0..$ab.len() { + for j in 0..$ab[0].len() { + $ab[i][j] = $f(*$m.add(i), $ab[i][j]) + } + } + }; +} + +macro_rules! per_col { + ($ab: expr, $m: expr, $f: expr) => { + for i in 0..$ab.len() { + for j in 0..$ab[0].len() { + $ab[i][j] = $f(*$m.add(j), $ab[i][j]) + } + } + }; +} + +const TILE: usize = 16; + +/// `pa` is packed k-major, MR(=16) contiguous per k-step (`pa[ik * 16 + i]`); `pb` is packed +/// k-major NR(=16) contiguous per k-step (`pb[ik * 16 + j]`), which is already row-major +/// `B[K, 16]` -- no transpose needed. `A_panel` (`pa`) is transposed into a small contiguous +/// `(16, K)` buffer, same as pilot v1, because the bf16 tile primitive wants row-major `A[16, K]`. +/// +/// Both operands are truncated to bf16 with `f32_to_bf16_batch_rne` (round-to-nearest-even, +/// the hot-loop-safe path -- never the scalar RNE fn, which is test-only). K is padded up to +/// the next multiple of 32 with zero rows in A and zero rows in B: the padding columns/rows +/// contribute `0 * anything = 0` to every accumulated cell, so the padding is inert. +/// +/// B is packed into VNNI layout via `PackedBf16B::pack`, once per `AddMatMul` call -- i.e. once +/// per (MR, NR) output tile, since that is the granularity `MatMatMulKer` calls a kernel body +/// at. Unlike pilot v1 this pack is a single VNNI interleave straight into the tile primitive +/// (no BLAS-level3 entry point re-deriving packing/dispatch from scratch), but it is still +/// real per-tile allocation and work, not something hoisted above the tile loop. +#[cfg(target_arch = "x86_64")] +unsafe fn add_mat_mul_bf16(pa: *const u8, pb: *const u8, k: usize, ab: &mut [[f32; TILE]; TILE]) { + unsafe { + if k == 0 { + return; + } + let a = pa as *const f32; + let b = pb as *const f32; + + let k_padded = k.next_multiple_of(32); + + let mut a_row_major = vec![0f32; TILE * k]; + for i in 0..TILE { + for ik in 0..k { + a_row_major[i * k + ik] = *a.add(ik * TILE + i); + } + } + let mut a_bf16 = vec![0u16; TILE * k_padded]; + for i in 0..TILE { + f32_to_bf16_batch_rne( + &a_row_major[i * k..i * k + k], + &mut a_bf16[i * k_padded..i * k_padded + k], + ); + } + + let b_row_major = std::slice::from_raw_parts(b, k * TILE); + let mut b_bf16 = vec![0u16; k_padded * TILE]; + f32_to_bf16_batch_rne(b_row_major, &mut b_bf16[..k * TILE]); + + let packed_b = PackedBf16B::pack(&b_bf16, k_padded); + + let mut c_tile = [0f32; TILE * TILE]; + bf16_tile_gemm_16x16_packed(&a_bf16, &packed_b, &mut c_tile); + + for i in 0..TILE { + for j in 0..TILE { + ab[i][j] += c_tile[i * TILE + j]; + } + } + } +} + +// `linalg/src/lib.rs` compiles this module tree under `feature = "foreign-inventory"` +// on any host arch (to enumerate x86_64 kernel names as metadata for cross-compiled +// builds), but `ndarray` is only a dependency on x86_64 (`linalg/Cargo.toml`). This +// stub keeps the crate compiling there; `MMMRustKernel!(x86_64; ...)` marks the real +// kernel `built(cfg!(target_arch = "x86_64"))`, so `MmmDispatch` never selects it and +// this arm never runs off x86_64. +#[cfg(not(target_arch = "x86_64"))] +unsafe fn add_mat_mul_bf16( + _pa: *const u8, + _pb: *const u8, + _k: usize, + _ab: &mut [[f32; TILE]; TILE], +) { + unreachable!("ndarray_bf16_gemm's kernel is x86_64-only and unbuilt elsewhere") +} + +unsafe fn add_unicast(ab: &mut [[f32; TILE]; TILE], other: &OutputStoreKer) { + unsafe { + for i in 0..TILE { + for j in 0..TILE { + let value: *const f32 = other + .ptr + .offset(other.row_byte_stride * i as isize + other.col_byte_stride * j as isize) + as _; + ab[i][j] += *value; + } + } + } +} + +unsafe fn store(tile: &OutputStoreKer, ab: &[[f32; TILE]; TILE]) { + unsafe { + for i in 0..TILE { + for j in 0..TILE { + let loc: *mut f32 = tile + .ptr + .offset(tile.row_byte_stride * i as isize + tile.col_byte_stride * j as isize) + as _; + *loc = ab[i][j]; + } + } + } +} + +/// The `MatMatMulKer` inner loop, f32-only, one packing (index 0, plain f32×f32), fixed 16x16 +/// tile geometry (the shape `bf16_tile_gemm_16x16_packed` is built for). Same fused-op +/// interpreter shape as `crate::generic::mmm::kernel` and pilot v1's `ndarray_gemm::kernel`; +/// the `AddMatMul` arm is the only place this diverges from the generic reference. +pub(super) unsafe fn kernel(mut pnl: *const FusedKerSpec<f32>) -> isize { + unsafe { + let mut ab = [[0f32; TILE]; TILE]; + loop { + if pnl.is_null() { + break; + } + match *pnl { + FusedKerSpec::Done => break, + FusedKerSpec::Clear => ab = [[0f32; TILE]; TILE], + FusedKerSpec::LoadTile(col_major, _row_major) => { + for row in 0..TILE { + for col in 0..TILE { + ab[row][col] = *col_major.add(col * TILE + row); + } + } + } + FusedKerSpec::ScalarAdd(a) => scalar!(ab, a, |a, b| a + b), + FusedKerSpec::ScalarMul(a) => scalar!(ab, a, |a, b| a * b), + FusedKerSpec::ScalarMin(m) => scalar!(ab, m, |a: f32, b: f32| a.min(b)), + FusedKerSpec::ScalarMax(m) => scalar!(ab, m, |a: f32, b: f32| a.max(b)), + FusedKerSpec::ScalarSub(m) => scalar!(ab, m, |a, b| a - b), + FusedKerSpec::ScalarSubF(m) => scalar!(ab, m, |a, b| b - a), + FusedKerSpec::LeakyRelu(m) => { + scalar!(ab, m, |a, b| if b > 0.0 { b } else { a * b }) + } + FusedKerSpec::PerRowMin(m) => per_row!(ab, m, |a: f32, b: f32| a.min(b)), + FusedKerSpec::PerRowMax(m) => per_row!(ab, m, |a: f32, b: f32| a.max(b)), + FusedKerSpec::PerRowAdd(m) => per_row!(ab, m, |a, b| a + b), + FusedKerSpec::PerRowMul(m) => per_row!(ab, m, |a, b| a * b), + FusedKerSpec::PerRowSub(m) => per_row!(ab, m, |a, b| a - b), + FusedKerSpec::PerRowSubF(m) => per_row!(ab, m, |a, b| b - a), + FusedKerSpec::PerColMin(m) => per_col!(ab, m, |a: f32, b: f32| a.min(b)), + FusedKerSpec::PerColMax(m) => per_col!(ab, m, |a: f32, b: f32| a.max(b)), + FusedKerSpec::PerColAdd(m) => per_col!(ab, m, |a, b| a + b), + FusedKerSpec::PerColMul(m) => per_col!(ab, m, |a, b| a * b), + FusedKerSpec::PerColSub(m) => per_col!(ab, m, |a, b| a - b), + FusedKerSpec::PerColSubF(m) => per_col!(ab, m, |a, b| b - a), + FusedKerSpec::AddRowColProducts(rows, cols) => { + for i in 0..TILE { + for j in 0..TILE { + ab[i][j] += *rows.add(i) * *cols.add(j); + } + } + } + FusedKerSpec::AddUnicast(other) => add_unicast(&mut ab, &other), + FusedKerSpec::ShiftLeft(_) + | FusedKerSpec::RoundingShiftRight(..) + | FusedKerSpec::QScale(..) => { + // Integer-quantization epilogue ops: this kernel only declares an f32 + // accumulator packing, so a caller never reaches these arms. + unreachable!("quantization ops are not reachable on the f32-only packing") + } + FusedKerSpec::AddMatMul { k, pa, pb, packing } => { + assert_eq!(packing, 0, "this kernel only declares packing 0 (f32 x f32)"); + add_mat_mul_bf16(pa, pb, k, &mut ab); + } + FusedKerSpec::Store(tile) => store(&tile, &ab), + }; + pnl = pnl.add(1); + } + } + 0 +} + +#[cfg(all(test, target_arch = "x86_64"))] +mod dispatch_stays_default { + use crate::frame::mmm::{MmmDispatch, Query}; + use tract_data::internal::DatumType; + + /// This kernel is registered without `inventory::submit!` (see `mmm.rs`'s registration + /// comment) specifically so it never reaches `MmmDispatch::native()` -- for both a concrete + /// and a symbolic (`None`) N, since the symbolic-N fallback in + /// `core::ops::einsum::kernel_selection::strategize` picks the largest-`nr` kernel per + /// packing group, bypassing `preferred`/boost entirely, and this kernel's nr=16 exceeds + /// every existing f32 AVX-512 kernel's nr. + #[test] + fn bf16_candidate_is_not_reachable_through_automatic_dispatch() { + let dispatch = MmmDispatch::native(); + for n in [Some(32), None] { + let query = Query::plain(DatumType::F32, Some(64), Some(256), n); + let suitable = dispatch.suitable(&query); + assert!( + suitable + .iter() + .all(|(mmm, _, _)| mmm.name() != "ndarray_avx512_bf16_mmm_f32_16x16"), + "the bf16 candidate must never appear in automatic dispatch (n={n:?})" + ); + } + } +} + +/// Tolerance-based correctness test. This kernel is inherently bf16-precision (see the module +/// doc): the exact-bit `test_mmm_kernel!` macro family compares kernel output against an f32 +/// reference with `==`/ULP-tight bounds, which this kernel cannot pass by construction, so it +/// gets a dedicated relative-tolerance check instead, run directly against `MatMatMulKer` +/// through the same fused-op path the real dispatcher uses (`AddMatMul` + `Store`), against a +/// naive f32 reference GEMM over inputs deliberately chosen to be exactly bf16-representable +/// (so this test's own tolerance is measuring accumulation-order/tier drift, not re-measuring +/// the f32->bf16 truncation the module doc already documents and asserts is real). +#[cfg(all(test, target_arch = "x86_64"))] +mod bf16_tolerance { + use crate::frame::mmm::FusedSpec; + use crate::x86_64::mmm::ndarray_avx512_bf16_mmm_f32_16x16; + use ndarray::simd::f32_to_bf16_batch_rne; + use tract_data::internal::*; + + fn bf16_exact_value(x: f32) -> f32 { + let mut bits = [0u16; 1]; + f32_to_bf16_batch_rne(&[x], &mut bits); + f32::from_bits((bits[0] as u32) << 16) + } + + #[test] + fn matches_naive_f32_reference_within_bf16_tolerance() { + let (m, k, n) = (32usize, 64usize, 32usize); + let mut a = vec![0f32; m * k]; + let mut b = vec![0f32; k * n]; + for (i, v) in a.iter_mut().enumerate() { + *v = bf16_exact_value(((i % 13) as f32 - 6.0) * 0.5); + } + for (i, v) in b.iter_mut().enumerate() { + *v = bf16_exact_value(((i % 11) as f32 - 5.0) * 0.5); + } + + let mut expected = vec![0f32; m * n]; + for i in 0..m { + for j in 0..n { + let mut acc = 0f32; + for kk in 0..k { + acc += a[i * k + kk] * b[kk * n + j]; + } + expected[i * n + j] = acc; + } + } + + let mmm = ndarray_avx512_bf16_mmm_f32_16x16.mmm(); + if !mmm.built() || !mmm.runnable() { + eprintln!("skipping: ndarray_avx512_bf16_mmm_f32_16x16 not runnable on this host"); + return; + } + let packing = &mmm.packings()[0]; + let a_tensor = Tensor::from_shape(&[m, k], &a).unwrap(); + let pa = packing.0.prepare_one(&a_tensor, 1, 0).unwrap(); + let b_tensor = Tensor::from_shape(&[k, n], &b).unwrap(); + let pb = packing.1.prepare_one(&b_tensor, 0, 1).unwrap(); + let mut c = Tensor::zero::<f32>(&[n, m]).unwrap(); + + unsafe { + mmm.run( + m, + n, + &[ + FusedSpec::AddMatMul { + a: crate::mmm::AsInputValue::Borrowed(&*pa), + b: crate::mmm::AsInputValue::Borrowed(&*pb), + packing: 0, + }, + FusedSpec::Store(mmm.c_view(Some(1), Some(0)).wrap(&c.view_mut())), + ], + ) + .unwrap(); + } + + let got = unsafe { c.as_slice_unchecked::<f32>() }; + for i in 0..m { + for j in 0..n { + let e = expected[i * n + j]; + let g = got[j * m + i]; + let tol = 1e-2 * e.abs().max(1.0); + assert!( + (e - g).abs() <= tol, + "mismatch at ({i},{j}): expected {e}, got {g} (tol {tol}), tier={}", + ndarray::simd::bf16_tile_gemm_tier(), + ); + } + } + } +} diff --git a/linalg/src/x86_64/ndarray_bf16_native_gemm.rs b/linalg/src/x86_64/ndarray_bf16_native_gemm.rs new file mode 100644 index 0000000000..c3664762cf --- /dev/null +++ b/linalg/src/x86_64/ndarray_bf16_native_gemm.rs @@ -0,0 +1,302 @@ +#![cfg(target_arch = "x86_64")] +#![allow(clippy::needless_range_loop)] +//! An f32 GEMM `MatMatMulKer` body whose `AddMatMul` step consumes operands already packed +//! into `ndarray::simd::PackedBf16B` / panel-native bf16 form (`ndarray_amx_native_pack.rs`'s +//! `NdarrayAmxBf16A`/`NdarrayAmxBf16B` packing) and calls +//! `ndarray::simd::bf16_tile_gemm_16x16_packed` directly -- no allocation, no f32->bf16 +//! conversion, and no VNNI packing inside `AddMatMul` itself, unlike `ndarray_bf16_gemm.rs`'s +//! kernel, which redoes all three on every tile call. +//! +//! Registered outside automatic dispatch (see `mmm.rs`'s registration comment for this kernel): +//! reachable only by direct construction, not through `MmmDispatch::native()`. +//! +//! Precision tradeoff is identical to `ndarray_bf16_gemm.rs`'s kernel (see its module doc): +//! operands are truncated to bf16 at pack time, one-time and lossy versus f32; the tile +//! arithmetic itself introduces no further lossiness beyond bf16-precision accumulation order. + +use crate::frame::mmm::FusedKerSpec; +use crate::frame::mmm::OutputStoreKer; + +#[cfg(target_arch = "x86_64")] +use ndarray::simd::{PackedBf16B, bf16_tile_gemm_16x16_packed}; + +macro_rules! scalar { + ($ab: expr, $m: expr, $f: expr) => { + for i in 0..$ab.len() { + for j in 0..$ab[0].len() { + $ab[i][j] = $f($m, $ab[i][j]) + } + } + }; +} + +macro_rules! per_row { + ($ab: expr, $m: expr, $f: expr) => { + for i in 0..$ab.len() { + for j in 0..$ab[0].len() { + $ab[i][j] = $f(*$m.add(i), $ab[i][j]) + } + } + }; +} + +macro_rules! per_col { + ($ab: expr, $m: expr, $f: expr) => { + for i in 0..$ab.len() { + for j in 0..$ab[0].len() { + $ab[i][j] = $f(*$m.add(j), $ab[i][j]) + } + } + }; +} + +const TILE: usize = 16; + +/// `pa` points at an `NdarrayAmxBf16AValue` panel: row-major bf16 `[16, k_padded]`, ready to +/// feed `bf16_tile_gemm_16x16_packed` as `a_bf16` with no further work. `pb` points at one +/// `ndarray::simd::PackedBf16B` value (not a byte blob -- `NdarrayAmxBf16BValue::panel_bytes` +/// hands back a pointer to the `PackedBf16B` itself, cast to `*const u8`), reinterpreted back +/// in place. Both were built once at `prepare_one`/`prepare_one_view` time by +/// `ndarray_amx_native_pack.rs`; this function performs no allocation, no bf16 conversion, and +/// no VNNI packing. +#[cfg(target_arch = "x86_64")] +unsafe fn add_mat_mul_amx_native( + pa: *const u8, + pb: *const u8, + k: usize, + ab: &mut [[f32; TILE]; TILE], +) { + unsafe { + if k == 0 { + return; + } + let k_padded = k.next_multiple_of(32); + let a_bf16 = std::slice::from_raw_parts(pa as *const u16, TILE * k_padded); + let b = &*(pb as *const PackedBf16B); + debug_assert_eq!(b.k(), k_padded); + + let mut c_tile = [0f32; TILE * TILE]; + bf16_tile_gemm_16x16_packed(a_bf16, b, &mut c_tile); + + for i in 0..TILE { + for j in 0..TILE { + ab[i][j] += c_tile[i * TILE + j]; + } + } + } +} + +// See `ndarray_bf16_gemm.rs`'s identical stub for why this exists: `linalg/src/lib.rs` +// compiles this module tree under `feature = "foreign-inventory"` on any host arch, but +// `ndarray` is only a dependency on x86_64. +#[cfg(not(target_arch = "x86_64"))] +unsafe fn add_mat_mul_amx_native( + _pa: *const u8, + _pb: *const u8, + _k: usize, + _ab: &mut [[f32; TILE]; TILE], +) { + unreachable!("ndarray_bf16_native_gemm's kernel is x86_64-only and unbuilt elsewhere") +} + +unsafe fn add_unicast(ab: &mut [[f32; TILE]; TILE], other: &OutputStoreKer) { + unsafe { + for i in 0..TILE { + for j in 0..TILE { + let value: *const f32 = other + .ptr + .offset(other.row_byte_stride * i as isize + other.col_byte_stride * j as isize) + as _; + ab[i][j] += *value; + } + } + } +} + +unsafe fn store(tile: &OutputStoreKer, ab: &[[f32; TILE]; TILE]) { + unsafe { + for i in 0..TILE { + for j in 0..TILE { + let loc: *mut f32 = tile + .ptr + .offset(tile.row_byte_stride * i as isize + tile.col_byte_stride * j as isize) + as _; + *loc = ab[i][j]; + } + } + } +} + +/// The `MatMatMulKer` inner loop. `AddMatMul` only accepts packing index 1 (this kernel's +/// `NdarrayAmxBf16A`/`NdarrayAmxBf16B` packing) -- packing 0 (the framework's default f32 +/// packing) is never a valid call here, since this kernel is only ever reached by direct +/// construction with the native packing explicitly selected. +pub(super) unsafe fn kernel(mut pnl: *const FusedKerSpec<f32>) -> isize { + unsafe { + let mut ab = [[0f32; TILE]; TILE]; + loop { + if pnl.is_null() { + break; + } + match *pnl { + FusedKerSpec::Done => break, + FusedKerSpec::Clear => ab = [[0f32; TILE]; TILE], + FusedKerSpec::LoadTile(col_major, _row_major) => { + for row in 0..TILE { + for col in 0..TILE { + ab[row][col] = *col_major.add(col * TILE + row); + } + } + } + FusedKerSpec::ScalarAdd(a) => scalar!(ab, a, |a, b| a + b), + FusedKerSpec::ScalarMul(a) => scalar!(ab, a, |a, b| a * b), + FusedKerSpec::ScalarMin(m) => scalar!(ab, m, |a: f32, b: f32| a.min(b)), + FusedKerSpec::ScalarMax(m) => scalar!(ab, m, |a: f32, b: f32| a.max(b)), + FusedKerSpec::ScalarSub(m) => scalar!(ab, m, |a, b| a - b), + FusedKerSpec::ScalarSubF(m) => scalar!(ab, m, |a, b| b - a), + FusedKerSpec::LeakyRelu(m) => { + scalar!(ab, m, |a, b| if b > 0.0 { b } else { a * b }) + } + FusedKerSpec::PerRowMin(m) => per_row!(ab, m, |a: f32, b: f32| a.min(b)), + FusedKerSpec::PerRowMax(m) => per_row!(ab, m, |a: f32, b: f32| a.max(b)), + FusedKerSpec::PerRowAdd(m) => per_row!(ab, m, |a, b| a + b), + FusedKerSpec::PerRowMul(m) => per_row!(ab, m, |a, b| a * b), + FusedKerSpec::PerRowSub(m) => per_row!(ab, m, |a, b| a - b), + FusedKerSpec::PerRowSubF(m) => per_row!(ab, m, |a, b| b - a), + FusedKerSpec::PerColMin(m) => per_col!(ab, m, |a: f32, b: f32| a.min(b)), + FusedKerSpec::PerColMax(m) => per_col!(ab, m, |a: f32, b: f32| a.max(b)), + FusedKerSpec::PerColAdd(m) => per_col!(ab, m, |a, b| a + b), + FusedKerSpec::PerColMul(m) => per_col!(ab, m, |a, b| a * b), + FusedKerSpec::PerColSub(m) => per_col!(ab, m, |a, b| a - b), + FusedKerSpec::PerColSubF(m) => per_col!(ab, m, |a, b| b - a), + FusedKerSpec::AddRowColProducts(rows, cols) => { + for i in 0..TILE { + for j in 0..TILE { + ab[i][j] += *rows.add(i) * *cols.add(j); + } + } + } + FusedKerSpec::AddUnicast(other) => add_unicast(&mut ab, &other), + FusedKerSpec::ShiftLeft(_) + | FusedKerSpec::RoundingShiftRight(..) + | FusedKerSpec::QScale(..) => { + unreachable!("quantization ops are not reachable on the f32-only packing") + } + FusedKerSpec::AddMatMul { k, pa, pb, packing } => { + assert_eq!(packing, 1, "this kernel only declares packing 1 (AMX-native bf16)"); + add_mat_mul_amx_native(pa, pb, k, &mut ab); + } + FusedKerSpec::Store(tile) => store(&tile, &ab), + }; + pnl = pnl.add(1); + } + } + 0 +} + +#[cfg(all(test, target_arch = "x86_64"))] +mod dispatch_stays_default { + use crate::frame::mmm::{MmmDispatch, Query}; + use tract_data::internal::DatumType; + + /// Same guard as `ndarray_bf16_gemm.rs`'s: this kernel is registered without + /// `inventory::submit!`, so it must never surface through `MmmDispatch::native()`, for + /// both a concrete and a symbolic (`None`) N. + #[test] + fn amx_native_candidate_is_not_reachable_through_automatic_dispatch() { + let dispatch = MmmDispatch::native(); + for n in [Some(32), None] { + let query = Query::plain(DatumType::F32, Some(64), Some(256), n); + let suitable = dispatch.suitable(&query); + assert!( + suitable + .iter() + .all(|(mmm, _, _)| mmm.name() != "ndarray_amx_native_bf16_mmm_f32_16x16"), + "the AMX-native candidate must never appear in automatic dispatch (n={n:?})" + ); + } + } +} + +/// Tolerance-based correctness test, same shape as `ndarray_bf16_gemm.rs`'s `bf16_tolerance` +/// module: this kernel is bf16-precision by construction, run directly against `MatMatMulKer` +/// through the real fused-op path (`AddMatMul` + `Store`) with packing 1 explicitly selected. +#[cfg(all(test, target_arch = "x86_64"))] +mod bf16_tolerance { + use crate::frame::mmm::FusedSpec; + use crate::x86_64::mmm::ndarray_amx_native_bf16_mmm_f32_16x16; + use ndarray::simd::f32_to_bf16_batch_rne; + use tract_data::internal::*; + + fn bf16_exact_value(x: f32) -> f32 { + let mut bits = [0u16; 1]; + f32_to_bf16_batch_rne(&[x], &mut bits); + f32::from_bits((bits[0] as u32) << 16) + } + + #[test] + fn matches_naive_f32_reference_within_bf16_tolerance() { + let (m, k, n) = (32usize, 64usize, 32usize); + let mut a = vec![0f32; m * k]; + let mut b = vec![0f32; k * n]; + for (i, v) in a.iter_mut().enumerate() { + *v = bf16_exact_value(((i % 13) as f32 - 6.0) * 0.5); + } + for (i, v) in b.iter_mut().enumerate() { + *v = bf16_exact_value(((i % 11) as f32 - 5.0) * 0.5); + } + + let mut expected = vec![0f32; m * n]; + for i in 0..m { + for j in 0..n { + let mut acc = 0f32; + for kk in 0..k { + acc += a[i * k + kk] * b[kk * n + j]; + } + expected[i * n + j] = acc; + } + } + + let mmm = ndarray_amx_native_bf16_mmm_f32_16x16.mmm(); + if !mmm.built() || !mmm.runnable() { + eprintln!("skipping: ndarray_amx_native_bf16_mmm_f32_16x16 not runnable on this host"); + return; + } + let packing = &mmm.packings()[1]; + let a_tensor = Tensor::from_shape(&[m, k], &a).unwrap(); + let pa = packing.0.prepare_one(&a_tensor, 1, 0).unwrap(); + let b_tensor = Tensor::from_shape(&[k, n], &b).unwrap(); + let pb = packing.1.prepare_one(&b_tensor, 0, 1).unwrap(); + let mut c = Tensor::zero::<f32>(&[n, m]).unwrap(); + + unsafe { + mmm.run( + m, + n, + &[ + FusedSpec::AddMatMul { + a: crate::mmm::AsInputValue::Borrowed(&*pa), + b: crate::mmm::AsInputValue::Borrowed(&*pb), + packing: 1, + }, + FusedSpec::Store(mmm.c_view(Some(1), Some(0)).wrap(&c.view_mut())), + ], + ) + .unwrap(); + } + + let got = unsafe { c.as_slice_unchecked::<f32>() }; + for i in 0..m { + for j in 0..n { + let e = expected[i * n + j]; + let g = got[j * m + i]; + let tol = 1e-2 * e.abs().max(1.0); + assert!( + (e - g).abs() <= tol, + "mismatch at ({i},{j}): expected {e}, got {g} (tol {tol}), tier={}", + ndarray::simd::bf16_tile_gemm_tier(), + ); + } + } + } +} diff --git a/linalg/src/x86_64/ndarray_gemm.rs b/linalg/src/x86_64/ndarray_gemm.rs new file mode 100644 index 0000000000..97e54040a7 --- /dev/null +++ b/linalg/src/x86_64/ndarray_gemm.rs @@ -0,0 +1,228 @@ +#![allow(clippy::needless_range_loop)] +//! An f32 GEMM `MatMatMulKer` body whose `AddMatMul` step calls into the AdaWorldAPI +//! ndarray fork's `simd::BlasLevel3::blas_gemm` instead of a hand-written inner-product +//! loop, as an additional candidate alongside the hand-tuned AVX-512 asm kernels. Every +//! other fused op (bias, min/max, per-row/per-col, store) is the same scalar Rust the +//! generic reference kernel uses, so only the matmul accumulation itself is delegated. +//! +//! Goes through `ndarray::simd::BlasLevel3`, the canonical consumer-facing re-export, +//! never `ndarray::hpc::blas_level3` directly — see the ndarray fork's own `CLAUDE.md` +//! ("all SIMD from `ndarray::simd`"). + +#[cfg(target_arch = "x86_64")] +use ndarray::ArrayView2; +#[cfg(target_arch = "x86_64")] +use ndarray::simd::BlasLevel3; + +use crate::frame::mmm::FusedKerSpec; +use crate::frame::mmm::OutputStoreKer; + +macro_rules! scalar { + ($ab: expr, $m: expr, $f: expr) => { + for i in 0..$ab.len() { + for j in 0..$ab[0].len() { + $ab[i][j] = $f($m, $ab[i][j]) + } + } + }; +} + +macro_rules! per_row { + ($ab: expr, $m: expr, $f: expr) => { + for i in 0..$ab.len() { + for j in 0..$ab[0].len() { + $ab[i][j] = $f(*$m.add(i), $ab[i][j]) + } + } + }; +} + +macro_rules! per_col { + ($ab: expr, $m: expr, $f: expr) => { + for i in 0..$ab.len() { + for j in 0..$ab[0].len() { + $ab[i][j] = $f(*$m.add(j), $ab[i][j]) + } + } + }; +} + +/// `pa` is packed k-major, MR contiguous per k-step (`pa[ik * MR + i]`); `pb` likewise for +/// NR. That makes the panel-pair product `ab[i][j] += sum_ik pa[ik*MR+i] * pb[ik*NR+j]` the +/// matrix product `A_panel^T . B_panel` where `A_panel` is `(k, MR)` row-major and `B_panel` +/// is `(k, NR)` row-major. `A_panel` is transposed into a small contiguous `(MR, k)` buffer +/// (a copy, not a stride trick) so both operands reach `blas_gemm` as contiguous slices and +/// take the real backend path instead of ndarray's non-contiguous fallback loop. +#[cfg(target_arch = "x86_64")] +unsafe fn add_mat_mul_ndarray<const MR: usize, const NR: usize>( + pa: *const u8, + pb: *const u8, + k: usize, + ab: &mut [[f32; NR]; MR], +) { + unsafe { + if k == 0 { + return; + } + let a = pa as *const f32; + let b = pb as *const f32; + + let mut a_t = vec![0f32; MR * k]; + for i in 0..MR { + for ik in 0..k { + a_t[i * k + ik] = *a.add(ik * MR + i); + } + } + let a_view = ArrayView2::from_shape((MR, k), &a_t).unwrap(); + let b_slice = std::slice::from_raw_parts(b, k * NR); + let b_view = ArrayView2::from_shape((k, NR), b_slice).unwrap(); + + let prod = a_view.blas_gemm(1.0f32, &b_view, 0.0f32); + for i in 0..MR { + for j in 0..NR { + ab[i][j] += prod[[i, j]]; + } + } + } +} + +// `linalg/src/lib.rs` compiles this module tree under `feature = "foreign-inventory"` +// on any host arch (to enumerate x86_64 kernel names as metadata for cross-compiled +// builds), but `ndarray` is only a dependency on x86_64 (`linalg/Cargo.toml`). This +// stub keeps the crate compiling there; `MMMRustKernel!(x86_64; ...)` marks the real +// kernel `built(cfg!(target_arch = "x86_64"))`, so `MmmDispatch` never selects it and +// this arm never runs off x86_64. +#[cfg(not(target_arch = "x86_64"))] +unsafe fn add_mat_mul_ndarray<const MR: usize, const NR: usize>( + _pa: *const u8, + _pb: *const u8, + _k: usize, + _ab: &mut [[f32; NR]; MR], +) { + unreachable!("ndarray_gemm's kernel is x86_64-only and unbuilt elsewhere") +} + +unsafe fn add_unicast<const MR: usize, const NR: usize>( + ab: &mut [[f32; NR]; MR], + other: &OutputStoreKer, +) { + unsafe { + for i in 0..MR { + for j in 0..NR { + let value: *const f32 = other + .ptr + .offset(other.row_byte_stride * i as isize + other.col_byte_stride * j as isize) + as _; + ab[i][j] += *value; + } + } + } +} + +unsafe fn store<const MR: usize, const NR: usize>(tile: &OutputStoreKer, ab: &[[f32; NR]; MR]) { + unsafe { + for i in 0..MR { + for j in 0..NR { + let loc: *mut f32 = tile + .ptr + .offset(tile.row_byte_stride * i as isize + tile.col_byte_stride * j as isize) + as _; + *loc = ab[i][j]; + } + } + } +} + +/// The `MatMatMulKer` inner loop, f32-only, one packing (index 0, plain f32×f32). Same +/// fused-op interpreter shape as `crate::generic::mmm::kernel`; the `AddMatMul` arm is the +/// only place this diverges from it. +pub(super) unsafe fn kernel<const MR: usize, const NR: usize>( + mut pnl: *const FusedKerSpec<f32>, +) -> isize { + unsafe { + let mut ab = [[0f32; NR]; MR]; + loop { + if pnl.is_null() { + break; + } + match *pnl { + FusedKerSpec::Done => break, + FusedKerSpec::Clear => ab = [[0f32; NR]; MR], + FusedKerSpec::LoadTile(col_major, _row_major) => { + for row in 0..MR { + for col in 0..NR { + ab[row][col] = *col_major.add(col * MR + row); + } + } + } + FusedKerSpec::ScalarAdd(a) => scalar!(ab, a, |a, b| a + b), + FusedKerSpec::ScalarMul(a) => scalar!(ab, a, |a, b| a * b), + FusedKerSpec::ScalarMin(m) => scalar!(ab, m, |a: f32, b: f32| a.min(b)), + FusedKerSpec::ScalarMax(m) => scalar!(ab, m, |a: f32, b: f32| a.max(b)), + FusedKerSpec::ScalarSub(m) => scalar!(ab, m, |a, b| a - b), + FusedKerSpec::ScalarSubF(m) => scalar!(ab, m, |a, b| b - a), + FusedKerSpec::LeakyRelu(m) => { + scalar!(ab, m, |a, b| if b > 0.0 { b } else { a * b }) + } + FusedKerSpec::PerRowMin(m) => per_row!(ab, m, |a: f32, b: f32| a.min(b)), + FusedKerSpec::PerRowMax(m) => per_row!(ab, m, |a: f32, b: f32| a.max(b)), + FusedKerSpec::PerRowAdd(m) => per_row!(ab, m, |a, b| a + b), + FusedKerSpec::PerRowMul(m) => per_row!(ab, m, |a, b| a * b), + FusedKerSpec::PerRowSub(m) => per_row!(ab, m, |a, b| a - b), + FusedKerSpec::PerRowSubF(m) => per_row!(ab, m, |a, b| b - a), + FusedKerSpec::PerColMin(m) => per_col!(ab, m, |a: f32, b: f32| a.min(b)), + FusedKerSpec::PerColMax(m) => per_col!(ab, m, |a: f32, b: f32| a.max(b)), + FusedKerSpec::PerColAdd(m) => per_col!(ab, m, |a, b| a + b), + FusedKerSpec::PerColMul(m) => per_col!(ab, m, |a, b| a * b), + FusedKerSpec::PerColSub(m) => per_col!(ab, m, |a, b| a - b), + FusedKerSpec::PerColSubF(m) => per_col!(ab, m, |a, b| b - a), + FusedKerSpec::AddRowColProducts(rows, cols) => { + for i in 0..MR { + for j in 0..NR { + ab[i][j] += *rows.add(i) * *cols.add(j); + } + } + } + FusedKerSpec::AddUnicast(other) => add_unicast::<MR, NR>(&mut ab, &other), + FusedKerSpec::ShiftLeft(_) + | FusedKerSpec::RoundingShiftRight(..) + | FusedKerSpec::QScale(..) => { + // Integer-quantization epilogue ops: this kernel only declares an f32 + // accumulator packing, so a caller never reaches these arms. + unreachable!("quantization ops are not reachable on the f32-only packing") + } + FusedKerSpec::AddMatMul { k, pa, pb, packing } => { + assert_eq!(packing, 0, "this kernel only declares packing 0 (f32 x f32)"); + add_mat_mul_ndarray::<MR, NR>(pa, pb, k, &mut ab); + } + FusedKerSpec::Store(tile) => store::<MR, NR>(&tile, &ab), + }; + pnl = pnl.add(1); + } + } + 0 +} + +#[cfg(all(test, target_arch = "x86_64"))] +mod dispatch_stays_default { + use crate::frame::mmm::{MmmDispatch, Query}; + use tract_data::internal::DatumType; + + /// This kernel is registered without `inventory::submit!` (see `mmm.rs`'s registration + /// comment) so it never reaches `MmmDispatch::native()` -- for both a concrete and a + /// symbolic (`None`) N, since the symbolic-N fallback in + /// `core::ops::einsum::kernel_selection::strategize` picks the largest-`nr` kernel per + /// packing group, bypassing `preferred`/boost entirely. + #[test] + fn ndarray_candidate_is_not_reachable_through_automatic_dispatch() { + let dispatch = MmmDispatch::native(); + for n in [Some(32), None] { + let query = Query::plain(DatumType::F32, Some(64), Some(256), n); + let suitable = dispatch.suitable(&query); + assert!( + suitable.iter().all(|(mmm, _, _)| mmm.name() != "ndarray_avx512_mmm_f32_16x8"), + "the ndarray candidate must never appear in automatic dispatch (n={n:?})" + ); + } + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 73cb934de4..6990df5a6b 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,7 @@ +# Bump log (append-only): +# - 2026-09-05: pinned to a fixed channel (was floating "stable"), joining +# the workspace-wide Rust toolchain sweep. A future bump edits only the +# `channel` line below — the version lives in exactly one place. [toolchain] -channel = "stable" +channel = "1.98.1" components = ["rustfmt", "clippy"] diff --git a/tools/asset-mirror/Cargo.lock b/tools/asset-mirror/Cargo.lock deleted file mode 100644 index 0899ea5039..0000000000 --- a/tools/asset-mirror/Cargo.lock +++ /dev/null @@ -1,678 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "ascii" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "cc" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "chunked_transfer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" - -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "displaydoc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", -] - -[[package]] -name = "find-msvc-tools" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - -[[package]] -name = "flate2" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "icu_collections" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" - -[[package]] -name = "icu_properties" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" - -[[package]] -name = "icu_provider" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "libc" -version = "0.2.189" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" - -[[package]] -name = "litemap" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" - -[[package]] -name = "log" -version = "0.4.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "potential_utf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" -dependencies = [ - "zerovec", -] - -[[package]] -name = "proc-macro2" -version = "1.0.107" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom", - "libc", - "untrusted", - "windows-sys", -] - -[[package]] -name = "rustls" -version = "0.23.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" -dependencies = [ - "log", - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" -dependencies = [ - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "serde" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" -dependencies = [ - "serde_core", -] - -[[package]] -name = "serde_core" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", -] - -[[package]] -name = "shlex" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" - -[[package]] -name = "simd-adler32" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" - -[[package]] -name = "smallvec" -version = "1.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "2.0.119" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "tiny_http" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" -dependencies = [ - "ascii", - "chunked_transfer", - "httpdate", - "log", -] - -[[package]] -name = "tinystr" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tract-asset-mirror" -version = "0.1.0" -dependencies = [ - "tiny_http", - "ureq", -] - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "ureq" -version = "2.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" -dependencies = [ - "base64", - "flate2", - "log", - "once_cell", - "rustls", - "rustls-pki-types", - "url", - "webpki-roots 0.26.11", -] - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "webpki-roots" -version = "0.26.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" -dependencies = [ - "webpki-roots 1.0.9", -] - -[[package]] -name = "webpki-roots" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "writeable" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" - -[[package]] -name = "yoke" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "synstructure", -] - -[[package]] -name = "zerofrom" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" - -[[package]] -name = "zerotrie" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] diff --git a/transformers/src/ops/gdn_recurrent.rs b/transformers/src/ops/gdn_recurrent.rs index 647ab7b8d3..4a84a27475 100644 --- a/transformers/src/ops/gdn_recurrent.rs +++ b/transformers/src/ops/gdn_recurrent.rs @@ -211,6 +211,10 @@ mod tests { use super::*; use crate::ops::test_utils::arb; + // A test helper whose arguments are the layer's own inputs: three shape + // values and the six tensors the operator consumes. Grouping them would + // obscure exactly what each case is feeding in. + #[allow(clippy::too_many_arguments)] fn run( s_len: usize, heads: usize, From aeafcd3d986ac229444279c62f4b58295a412573 Mon Sep 17 00:00:00 2001 From: Claude <noreply@anthropic.com> Date: Thu, 29 Oct 2026 07:10:44 +0000 Subject: [PATCH 2/2] Build our squashed work against current upstream Upstream renamed Tensor::try_as_plain_mut to try_as_plain_ram_mut, so the AMX native packer uses the new name. The toolchain moves to 1.99.0 because the AdaWorldAPI ndarray fork now requires it and 1.98.1 no longer resolves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v --- linalg/src/x86_64/ndarray_amx_native_pack.rs | 2 +- rust-toolchain.toml | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/linalg/src/x86_64/ndarray_amx_native_pack.rs b/linalg/src/x86_64/ndarray_amx_native_pack.rs index a3dcfa5c45..d3789c321f 100644 --- a/linalg/src/x86_64/ndarray_amx_native_pack.rs +++ b/linalg/src/x86_64/ndarray_amx_native_pack.rs @@ -43,7 +43,7 @@ fn k_padded(k: usize) -> usize { /// kernel's bf16 rounding. fn simulate_bf16_precision_loss(mut tensor: Tensor) -> TractResult<Tensor> { if tensor.datum_type() == f32::datum_type() { - let mut plain = tensor.try_as_plain_mut()?; + let mut plain = tensor.try_as_plain_ram_mut()?; let slice = plain.as_slice_mut::<f32>()?; let mut bits = vec![0u16; slice.len()]; f32_to_bf16_batch_rne(slice, &mut bits); diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 6990df5a6b..589638b7d7 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -2,6 +2,8 @@ # - 2026-09-05: pinned to a fixed channel (was floating "stable"), joining # the workspace-wide Rust toolchain sweep. A future bump edits only the # `channel` line below — the version lives in exactly one place. +# - 2026-10-29: 1.98.1 -> 1.99.0; the AdaWorldAPI ndarray fork now requires +# rustc 1.99.0, so 1.98.1 no longer resolves the dependency graph. [toolchain] -channel = "1.98.1" +channel = "1.99.0" components = ["rustfmt", "clippy"]