Add Maritime/Logistics support assistant with safety/eval layer #1

Merged
majid merged 3 commits from add-maritime-assistant into main 2026-09-19 16:01:28 +00:00
Showing only changes of commit a72be63574 - Show all commits
+34 -10
View File
@@ -14,18 +14,42 @@ trade-offs documented in [`docs/SAFETY_WRITEUP.md`](docs/SAFETY_WRITEUP.md).
## Architecture
Three narrow agents behind a deterministic supervisor -- the supervisor is
plain Rust control flow, not another LLM call, so *what's allowed through*
is never itself something the model can be talked out of:
Three narrow agents behind a deterministic supervisor:
```mermaid
flowchart TD
Q["Customer Query"] --> PRE{{"Guardrail: Pre-check<br/>regex injection scan"}}
PRE -- allow --> RET["Retrieval Agent<br/>vector search + LLM relevance filter"]
RET <--> QD[("Qdrant Vector Store<br/>chunked KB docs")]
RET --> DRAFT["Drafting Agent<br/>tool-calling loop"]
DRAFT -- lookup_shipment --> SHIP[("Shipment Repository")]
DRAFT -- escalate_to_human --> ESC[("Escalation Desk")]
DRAFT --> POL["Policy-Compliance Agent"]
POL --> DOCS[("acceptable_use.md +<br/>tracking_policy.md")]
POL --> POST{{"Guardrail: Post-check<br/>PII/injection scan + policy verdict"}}
POST -- allow / escalate --> FINAL["Final Answer + Citations -> Customer"]
PRE -- block --> FINAL
PRE -.-> AUDIT[("Audit Log<br/>JSONL, append-only")]
POST -.-> AUDIT
classDef agent fill:#eef2ff,stroke:#4f46e5,color:#312e81
classDef guardrail fill:#fff7ed,stroke:#ea580c,color:#7c2d12
classDef store fill:#f1f5f9,stroke:#64748b,color:#334155
classDef audit fill:#f0fdf4,stroke:#16a34a,color:#14532d
classDef io fill:#f8fafc,stroke:#334155,color:#0f172a
class RET,DRAFT,POL agent
class PRE,POST guardrail
class QD,SHIP,ESC,DOCS store
class AUDIT audit
class Q,FINAL io
```
query -> guardrail.pre_check (rule-based, blocks prompt injection)
-> RetrievalAgent (Qdrant vector search -> LLM relevance filter)
-> DraftingAgent (tool-calling: lookup_shipment, escalate_to_human)
-> PolicyAgent (LLM compliance check against acceptable_use.md)
-> guardrail.post_check (rule-based PII/injection scan + policy verdict)
-> answer, audit record
```
The supervisor (`src/agents/supervisor.rs`) is what runs this graph -- plain
Rust control flow, not another LLM call, so *what's allowed through* is
never itself something the model can be talked out of. The dashed edges are
the audit trail: every guardrail decision is logged, not just the blocked
ones.
Every LLM call asks for a schema-validated structured output (via a forced
tool-call, see `src/llm/client.rs`) rather than free text -- there's no