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..d3789c321f --- /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_ram_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..589638b7d7 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,9 @@ +# 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. +# - 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 = "stable" +channel = "1.99.0" 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,