Files
factory/vibe/PRD/ai-back-office/agent-architecture.md
T
arcodangeandClaude Fable 5 a00f5cb065 docs(prd): sandbox-vs-prod posture + certified-accounting-grade operations
New compliance.md leaf: French bookkeeping obligations mapped to fleet
mechanisms — inaltérabilité (L.123-22) via an append-only production
ledger grammar (create/validate/pay/avoir, never mutate a validated
document) enforced by a promote-plan compliance linter; FEC (L.47 A
LPF) with quarterly export + Test Compta Demat validation (accounting-
module binding flagged as unverified gap); piste d'audit fiable (289
VII CGI) framed as a by-product of journals + tx-id reco + monthly
packs; retention, numbering, copie fiable; loi anti-fraude scoped out
(B2B-only) with BlockedLog as sandbox-first belt-and-braces.

New Environments section in agent-architecture: prod = the ledger
(grammar-bound), sandbox = disposable iso-prod rehearsal (exempt, never
wired to production third parties); side_effect_class -> environment/
credential mapping; POCs write on sandbox only; evals target fresh
checkpoints; irreversible-by-design features trial on checkpoints.

Woven through hub (goal, requirement, success criteria, leaves table),
T03/T05/T15 guardrails, QA (linter suite, pure-append snapshots, FEC
cadence, PAF evidence framing), C2, POC-1 exit criteria.

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

17 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), a compliance linter in promote-plan (a manifest with any operation outside the ledger grammar never reaches the approval card), and approval cards as the human interface to the gate.

Environments — sandbox vs production

The environment split is not an implementation detail — it is both the safety device (ADR-0003) and the compliance device (compliance): the sandbox may host any experiment because its state is disposable; production is held to append-only ledger discipline because it is the books.

Production (erp.arcodange.lab) Sandbox (erp-sandbox.arcodange.lab)
Role the ledger — book of record rehearsal, POCs, evals, drills
State permanent, append-shaped only disposable; re-seeded iso-prod on demand (arcodange sandbox checkpoint refresh)
Credentials read-only ai_agent; prod write key human-held, ENV-only at promote time write-scoped ai_agent_sandbox, host-guarded (structurally cannot reach prod)
Ledger grammar enforced (linter + locking + snapshot detection) exempt — but manifests destined for prod are linted before rehearsal
Third parties real (Qonto/PA, Zoho, Telegram) never wired to production externals: no PA emission, no outbound mail — side channels are stubbed or blackholed

Every atom's side_effect_class maps to an environment posture:

side_effect_class Runs against Credential
read prod (and sandbox for evals) read-only ai_agent
draft no ERP at all none
write-sandbox sandbox only ai_agent_sandbox (host-guarded)
write-prod prod, only through the promote gate human-held key + explicit confirm
outbound production channels allowlisted recipients, human-gated

Standing rules: every POC's write legs run on the sandbox and enter prod only through the gate with a real approval; ERP-dependent eval runs target a fresh checkpoint (iso-prod refresh = a reproducible fixture); restore drills and game-days land on the sandbox by construction (T14, QA strategy); anything designed to be irreversible in prod (e.g. Dolibarr's BlockedLog module) is trialed on a checkpoint first, because the sandbox provides exactly the reversibility production denies.

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, T17 vault capture/retrieval, interactive Claude Code sessions (the atom factory) hermes cron ticker (already driving the vault jobs) + on-wake queue drain Not a server: availability model in model fleet; time-critical work must not depend on it. hermes = the local agent runtime (skills, cron, the Ornith model).
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).

Knowledge layer — the second brain

The operator's second brain is already in place and already agent-integrated: a PARA Obsidian vault (00-Inbox06-Zettel), git-synced to the forge (arcodange/SecondBrain) via obsidian-git, exposed to agents through mcp-obsidian (local REST API), and automated by .automation/sb.py (weekly digest, inbox triage, daily prefill, idempotent Gitea→Inbox ingest) scheduled on the hermes cron ticker — with Ornith, hermes's local reasoning model (127.0.0.1:18080), as the confidential/offline lane. The vault even declares its own AI routing doctrine — Claude by default, Mistral for well-defined tasks, Ornith/hermes for the confidential — which is precisely the policy the model fleet generalizes.

The integration contract (T17):

  • Division of truth: the ERP is the book of record; the vault is context and institutional memory (contract nuances, client history, decisions, REX). No accounting fact is authoritative in the vault.
  • Capture: fleet outputs worth remembering land as append-only inbox/area notes with idempotent frontmatter — the pattern the Gitea ingest already proves; human-authored notes are never edited in place.
  • Retrieval: context-hungry atoms query the vault and carry facts with their note dates — notes are trusted-but-stale: anything contradicting the ERP, or older than its subject's last change, triggers re-verification rather than belief.
  • Rails reused, not rebuilt: M4-side access is direct filesystem + mcp-obsidian; the weekly digest and the human's PARA filing ritual remain the curation loop; cluster-side access is open decision D7.

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 for cluster-triggered jobs + hermes for M4-side lanes (leaning — hermes already runs skills + cron there) 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 PA — e-invoicing platform (plateforme agréée, ex-PDP) Leaning: Qonto (operator direction, 2026-07 — the capital-deposit bank, DGFiP-registered PA, e-invoicing included in every plan, and the fleet's richest existing API integration); POC-6 validates reception + API pull before the ADR — 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)
D7 Cluster↔vault access git clone/pull of the SecondBrain remote (leaning — the Gitea remote exists, offline-friendly, reviewable) vs. tunneled Obsidian REST API (M4-only today) vs. keeping vault access M4-exclusive

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; D7 closes when the first cluster-side atom needs vault context (phase 3 at the earliest).