Files
factory/vibe/PRD/ai-back-office/agent-architecture.md
T
arcodangeandClaude Fable 5 169c8debb4 docs(prd): AI back-office — agent fleet for daily admin & accounting
New PRD tree vibe/PRD/ai-back-office/ (hub + 6 leaves + STATUS): task
inventory T01-T16 with mode operatoire, atom/contract architecture on
the ADR-0003 write gate, four-tier model fleet (Claude/Mistral/M4/Pi),
12 challenges with mitigations, POC plan with exit criteria, QA
strategy with autonomy promotion gates. Index row + bidirectional
backlinks (erp guidebook, safe-prod PRD).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-11 14:25:04 +02:00

12 KiB
Raw Blame History

vibe > PRD > AI back-office > Agent architecture

Agent architecture — atoms, contracts, gates

Status: In design Last Updated: 2026-07-11 Up: AI back-office hub Related: Task inventory · Model fleet · Challenges · ADR 0003 — sandbox state lifecycle

Design principles

  1. Atoms, not monoliths. Each capability (classify, extract, validate, record, reconcile, report, remind) is one narrow agent with a strict I/O contract. Workflows are compositions of atoms with explicit gates — never one prompt that "does the accounting".
  2. The LLM proposes, code disposes. Formats, arithmetic, checksums, dedup, and referential integrity are enforced by deterministic validators. A model output that fails validation is quarantined, never auto-corrected.
  3. Data is never instructions. Inbound content (mails, PDFs, bank labels) flows through typed fields; extraction atoms hold zero credentials and zero action tools.
  4. Writes are rehearsed, gated, and replayable. The only path to prod mutation is manifest → sandbox rehearsal → human approval → gated promote (ADR 0003).
  5. Silence is an alert. Every standing loop heartbeats; a quiet fleet must be provably quiet, not possibly dead.
  6. Earn autonomy. Levels (A0A3) are granted per-atom from measured evals and revoked on incident (QA strategy).

Atom contract

Every atom is registered in a versioned YAML registry (git) with:

Field Meaning
name, version Identity; version bumps on any behavioral change (re-triggers evals).
input_schema / output_schema JSON Schema; enforced at runtime (constrained decoding where the tier supports it).
invariants Deterministic post-conditions (e.g. HT + TVA == TTC ± 0.01).
side_effect_class read · draft · write-sandbox · write-prod · outbound — drives which gates apply.
idempotency_key How a replay is recognized (e.g. supplier + ref_supplier + TTC).
autonomy Current earned level (A0A3) + link to the eval evidence.
model_policy Preferred tier, fallbacks, escalation rule (model fleet).
eval_ref Golden set + scoring script for this atom.

The registry is the source of truth for what the fleet may do; an atom absent from the registry does not run.

The pipeline shape

Every workflow instantiates the same stage skeleton (skipping stages it doesn't need):

watch → classify → extract → validate → stage → approve → apply → verify → journal

The flagship instance — supplier invoice end-to-end (T01T03, POC-1):

%%{init: {'theme':'base'}}%%
flowchart TB
    mail["Zoho books@<br>new message"]:::src
    triage["T01 classify<br>(Pi tier, constrained)"]:::proc
    extract1["T02 extract A<br>(M4 local)"]:::proc
    extract2["T02 extract B<br>(Mistral EU)"]:::proc
    agree{"critical fields<br>agree?"}:::gate
    escal["escalate<br>(Claude tier)"]:::proc
    valid["deterministic validators<br>arithmetic · rates · SIREN · IBAN · dedupe"]:::gate
    quarantine["quarantine queue<br>(review in digest)"]:::store
    manifest["T03 manifest + sandbox rehearsal<br>predicted-delta check"]:::proc
    card["Telegram approval card"]:::gate
    promote["gated promote to prod<br>(human key + confirm)"]:::gate
    ged["attach PDF (GED)<br>re-read + snapshot delta"]:::proc
    journal["run journal<br>+ golden-set feedback"]:::store

    mail --> triage --> extract1
    triage --> extract2
    extract1 --> agree
    extract2 --> agree
    agree -- "no" --> escal --> valid
    agree -- "yes" --> valid
    valid -- "fail" --> quarantine
    valid -- "pass" --> manifest --> card --> promote --> ged --> journal
    quarantine --> journal

    classDef src fill:#2563eb,stroke:#1e40af,color:#fff
    classDef proc fill:#059669,stroke:#047857,color:#fff
    classDef store fill:#7c3aed,stroke:#6d28d9,color:#fff
    classDef gate fill:#b45309,stroke:#92400e,color:#fff
  1. A new message on books@ is classified by the T01 sentinel (Pi tier, schema-constrained output).
  2. The PDF is extracted twice independently — locally on the M4 and on the Mistral EU cloud.
  3. Critical fields (amounts, IBAN, ref, dates) must agree exactly; disagreement escalates to the Claude tier; still-ambiguous items stop here.
  4. Deterministic validators check arithmetic, VAT rates, SIREN/IBAN checksums, and duplicates; any failure lands in the quarantine queue, surfaced in the digest.
  5. A write manifest is rehearsed on the sandbox and its result re-read and compared to the draft (predicted-delta check).
  6. The human gets a Telegram approval card; approval triggers the gated promote to prod (human-held key + explicit confirm).
  7. The source PDF is attached in the GED (Dolibarr's document store), the write is verified by re-read + snapshot delta, and the full run is journaled — rejections and corrections feed the golden set.

Write safety (inherited, not reinvented)

ADR 0003 already delivers the hard part, proven live on the erp repo:

  • Sandbox host-guard: the write skill structurally refuses any host that is not erp-sandbox — a sandbox atom cannot mutate prod.
  • Manifests with portable refs: @ref (created earlier in the run) and #entity:field=value business-key lookups (aborts on 0 or >1 match — never guesses ids).
  • Gated promote: promote-plan (human-readable review) → promote-apply --target prod requiring the prod write key from ENV only (never stored) + an explicit confirm variable.
  • Iso-prod checkpoints: the sandbox is re-seedable from prod at will, so rehearsals run against today's real state.

This PRD adds around it: idempotency keys on every write atom, predicted-delta assertions (rehearse → re-read → compare before asking for approval), pre/post snapshots (T13), and approval cards as the human interface to the gate.

Security model

  • Least privilege per atom. Extraction and classification atoms hold no credentials at all. Read atoms use the read-only ai_agent key. Sandbox writes use the sandbox-only agent. The prod write key exists only in the human's hands at promote time.
  • Ephemeral scoped ERP workers. For orchestrated batches, the orchestrator mints short-lived Dolibarr users scoped to the subtask (supplier-ingest, bank-reconciler, readonly — the PERMISSION_SCOPES pattern prototyped in erp test/orchestratorExample.ts + test/scripts/admin/permissions.ts), and deletes them when the batch ends. A leaked worker key is narrow and already dead.
  • Secrets discipline. All standing credentials live in Vault (house pattern, VSO-injected); skill .env files are mode-600 and gitignored; agents never echo credentials into journals or prompts.
  • Blast-radius honesty. Bank access is read-only by construction (no payment-initiation scopes are ever requested). The mailbox OAuth is read-only. The single irreversible surface is prod ERP writes — hence the gate.

Prompt-injection defenses

Inbound documents are adversarial by default — an invoice PDF or a mail body can contain text addressed to an LLM. Defense in depth:

  1. No-tool extraction: atoms that read untrusted content can only emit schema-constrained JSON — there is nothing to hijack.
  2. Typed handoffs: downstream atoms receive extracted fields, never raw document text; the raw source travels as an opaque attachment (hash-addressed) for human eyes.
  3. Instruction-shaped content is a finding: validators flag imperative/LLM-addressed text in extracted fields; such items are quarantined and surfaced verbatim to the human.
  4. Action allowlists: outbound mail only to allowlisted recipients; calendar mutations sourced from mail content require human confirmation (T11).
  5. Injection fixtures in evals: every extraction atom's golden set includes adversarial documents; a regression here blocks autonomy promotion (QA strategy).

Runtimes & scheduling

Runtime Runs Scheduling Notes
k3s cluster (Pis) T01 sentinel inference, T11 reminders, T13/T14 verifications, queue + gateway CronJobs + long-running Deployments (ArgoCD apps per the lab's <app> join-key convention) Proven pattern: the erp backup CronJob. No LLM heavier than the Pi tier.
M4 MacBook T02/T16 local extraction, T09 report, interactive Claude Code sessions (the atom factory) opportunistic — on-wake/launchd + queue pull Not a server: availability model in model fleet; time-critical work must not depend on it.
Cloud APIs Mistral extraction/OCR; Claude reasoning steps (headless claude -p / Agent SDK) invoked by pipeline stages Budget-capped; degraded modes defined.
telegram-gateway digests, approval cards, human commands webhook-driven Roadmapped phases (durable Postgres queue, async handlers) are exactly what the fleet needs — see open decisions.

Work queue. Pipeline stages communicate through a durable queue with dead-letter semantics (an item that fails N times parks in the DLQ and appears in the digest). Start minimal; the queue technology is an open decision below.

Graduation path. New atoms are prototyped as Claude Code skills (fast iteration, human in the loop), then frozen into deterministic scripts + tests once stable — the house already does this (.claude/skills/ scripts wrapped by bin/arcodange). Claude-tier involvement in a mature atom shrinks to escalation handling.

Human channel

  • One daily digest (Telegram, morning): items awaiting approval, quarantined items, aging unresolved work, heartbeat summary, upcoming deadlines (D-30/D-7/D-1). An empty day still sends "all green" — silence must be distinguishable from failure.
  • Approval cards: one decision per card (approve / edit / reject-with-reason); rejection reasons are first-class data feeding golden sets.
  • Escape hatch: every automated lane has a documented manual runbook fallback (the fleet augments the operator; it never becomes the only way to run the company).

Open decisions

To be settled by POC evidence, each closing with a short ADR:

# Decision Options (leaning)
D1 Work queue telegram-gateway's planned Postgres durable queue (leaning — already roadmapped, transactional, one less system) vs. flat files in git vs. Redis
D2 Orchestration runtime Claude Agent SDK headless on cluster-triggered jobs (leaning) vs. bespoke TS orchestrator (erp test/ Deno codebase) vs. pure CronJobs + scripts
D3 KM monthly invoice firing enable Dolibarr template auto-fire (frequency>0) vs. agent-fired via sandbox+promote (leaning — keeps the gate + mention audit in-line)
D4 PDP (e-invoicing platform) shortlist + Dolibarr 22 module compatibility test on sandbox — must close before 2026-09-01 (C12)
D5 OCR provider for scanned docs Mistral OCR (EU cloud) vs. local vision model on M4 vs. Tesseract baseline
D6 Pi inference serving llama.cpp server vs. Ollama on arm64, resource limits, node pinning (C5)

D4D6 close with their mapped POCs (POC-6, POC-5, POC-2); D1D2 are settled while building phase 3's standing fleet (the queue and scheduler are its skeleton); D3 lands with phase 4's money loops.