Three-Lane Communication Model
The platform enforces three supervised lanes for all data movement: Canonical (events), Operator (internal RPC), and Proxy (egress). Plugins run in isolated containers with no direct database or network access; every lane terminates in Core, where policy, audit, redaction, rate limits, and allowlists are applied. When safety cannot be proven, the action is blocked (fail-safe).
Actors & Guards
- Plugin runtime: isolated container, no outbound network; authenticates to Core with a unique token.
- Core Gateway: entry point for Operator calls (
/gateway/{target_plugin}/{operation}). - Canonical Engine: normalized event hub (
core/canonical/*) with validation and policy. - Proxy: the only egress path; enforces domain/path allowlists and TLS; blocks IP literals/localhost.
- Policy Engine: evaluates traits, roles, data classification, and operation allowlists.
- Audit Service: structured logs with
trace_idand redacted PHI/PII; every decision/action recorded. - Storage Service: AES‑256 at rest; PHI tagging and retention metadata.
- Identity: derives claims/traits for policy and trait‑gated UI.
Why Three Lanes
- Canonical abstracts data semantics and decouples producers/consumers with strong validation and policy.
- Operator provides tightly scoped, synchronous actions without exposing network primitives.
- Proxy centralizes egress, secret handling, and compliance controls to reduce exfiltration risk.
Visuals
```mermaid flowchart TB %% Three-Lane overview with wrapped labels classDef box fill:#eef5ff,stroke:#4c6ef5,color:#222,stroke-width:1px classDef edge stroke:#888,color:#444
subgraph Plugin[Plugin Container\n(no outbound network)] P1["LLM Plugin\nclassify=PHI\ntrace_id=...\nrefs not raw PHI"]:::box end
CoreGW["Core Gateway\n(Operator RPC)"]:::box Canonical["Canonical Engine\n(schema + policy + audit)"]:::box Proxy["Core Proxy\n(allowlist + TLS + secrets)"]:::box Vectorizer["Vectorizer Plugin\n(Embed op)"]:::box
%% Edges (use short phrases to avoid overflow) P1 -- "Operator: Embed" → CoreGW CoreGW -- "dispatch" → Vectorizer P1 -- "Canonical: DocumentSummarized" → Canonical P1 -- "Proxy: chat completions" → Proxy
style Plugin fill:#f8fbff,stroke:#aac6ff,stroke-width:1px class P1,CoreGW,Canonical,Proxy,Vectorizer box ```
```mermaid flowchart TB %% Default backends and fail-safe fallbacks classDef box fill:#f4fff4,stroke:#2e7d32,color:#222,stroke-width:1px classDef warn fill:#fff8e1,stroke:#f9a825,color:#222,stroke-width:1px
subgraph Core[Core Services] DB[(Postgres\npostgresql+asyncpg)]:::box RAG[(Redis\nredis://localhost:6379/0)]:::box end
subgraph Fallbacks[Graceful Fallbacks] MemDB[(SQLite in-tests\nTEST_DB_URL or in-memory)]:::warn MemRAG[(In-memory RAG\nif Redis unreachable)]:::warn end
Core → Fallbacks DB -. tests only .-> MemDB RAG -. on failure .-> MemRAG ```
Journey: A Piece of Data Inside the LLM Plugin
Imagine a PHI‑bearing text snippet the LLM plugin must summarize and then notify others.
1) Born in the LLM plugin - The plugin tags payload classification=["phi"], sets tenant_id, and creates a trace_id. - It constructs requests carrying identity (plugin token → plugin id + traits), classification tags, policy context (actor, tenant, purpose, operation), and observability metadata (trace_id, span_id, timestamps, idempotency key).
2) Proxy Lane: call an external LLM safely - The plugin cannot open sockets. It builds an EgressRequest describing target domain/path, method, and payload. No scheme, no IP literals, no localhost; no plugin-supplied Authorization. - Core Proxy enforces allowlists, injects secrets from vault, enforces TLS 1.3+, applies rate limits, redacts and audits.
3) Canonical Lane: publish the normalized result - The plugin wraps the result in a normalized canonical event (e.g., DocumentSummarized) including schema version, tenant, classification tags, references/hashes (not raw PHI). - Canonical validates schema, enforces policy (e.g., handles_phi), audits, persists with encryption, and fans-out only to authorized consumers.
4) Operator Lane: invoke another plugin synchronously - For embeddings, the LLM plugin calls POST /gateway/vectorizer/Embed with a compact reference or redacted text. - Gateway authenticates, checks operation allowlist, applies SSRF guards, evaluates policy and rate limits, audits, and dispatches to the vectorizer plugin.
5) Secondary events and notifications - Emit DocumentEmbedded canonical event for indexing. - Notifications rules evaluate events and may emit a NotificationRequest to audiences by trait (e.g., sales). Any external fan‑out uses Proxy with allowlists and secret injection.
6) Admin Console visibility (trait‑aware) - Diagnostics shows lane health and provider status. - Audit views show lineage: Proxy call → Canonical events → Operator call. Payloads remain redacted; classification is visible; everything linked by trace_id.
Example Payloads
Below are reference envelopes. Fields are indicative; adapt to your SDK structures. Avoid including raw PHI—prefer references/hashes and classification metadata.
Proxy Lane — EgressRequest (request)
{
"trace_id": "6b3c8a10-6d3f-4f1e-9d41-9e5a7a12c0c7",
"span_id": "08a9c3b2d8f1a7e2",
"idempotency_key": "llm:complete:doc-9f3f:v1",
"tenant_id": "tenant_abc",
"actor": { "type": "plugin", "id": "plugin.llm" },
"actor_traits": ["handles_phi", "ai_client"],
"classification": ["phi"],
"purpose": "clinical_summary",
"policy_context": {
"operation": "proxy.egress",
"requested_scopes": ["egress:api.openai.com:/v1/chat/completions:POST"],
"data_tags": ["phi"]
},
"target": {
"domain": "api.openai.com",
"path": "/v1/chat/completions",
"method": "POST"
},
"headers": {
"content-type": "application/json"
},
"body": {
"model": "gpt-4o-mini",
"messages": [
{ "role": "system", "content": "You are a helpful assistant." },
{ "role": "user", "content_ref": "urn:doc:9f3f#segment:summary-input" }
],
"temperature": 0.2
},
"constraints": { "require_tls": true, "disallow_ip_literals": true }
}
Proxy Lane — EgressResponse (response)
{
"trace_id": "6b3c8a10-6d3f-4f1e-9d41-9e5a7a12c0c7",
"request_id": "req_01HZXK2FQ0R2M3WZQ8",
"policy_decision": {
"allowed": true,
"rule": "allow:openai.com:completions",
"redactions": ["headers.authorization", "body.choices[].message.content"]
},
"rate_limit": { "domain": "api.openai.com", "remaining": 149, "reset_ms": 42000 },
"egress": {
"status": 200,
"duration_ms": 512,
"response_headers": { "content-type": "application/json" },
"response_body": {
"id": "chatcmpl-abc123",
"choices": [
{ "index": 0, "message_ref": "urn:doc:9f3f#segment:summary-output", "finish_reason": "stop" }
]
}
},
"audit_id": "aud_01HZXK39W8HK3Z9XPK"
}
Canonical Lane — Event (DocumentSummarized)
{
"trace_id": "6b3c8a10-6d3f-4f1e-9d41-9e5a7a12c0c7",
"tenant_id": "tenant_abc",
"producer": { "type": "plugin", "id": "plugin.llm" },
"producer_traits": ["handles_phi"],
"classification": ["phi"],
"topic": "canonical.documents.summarized",
"schema": { "name": "DocumentSummarized", "version": "1.2.0" },
"occured_at": "2025-09-29T09:32:41.120Z",
"data": {
"document_ref": "urn:doc:9f3f",
"summary_ref": "urn:doc:9f3f#segment:summary-output",
"summary_hash": "sha256-3d9212...",
"language": "en",
"model": "gpt-4o-mini",
"tokens_used": { "prompt": 1024, "completion": 128 }
},
"policy_context": { "operation": "canonical.emit", "data_tags": ["phi"] }
}
Operator Lane — Gateway RPC (request) to Vectorizer.Embed
{
"trace_id": "6b3c8a10-6d3f-4f1e-9d41-9e5a7a12c0c7",
"span_id": "7e2c1a0b4f9d3c8a",
"tenant_id": "tenant_abc",
"caller": { "type": "plugin", "id": "plugin.llm" },
"caller_traits": ["handles_phi", "ai_client"],
"classification": ["phi"],
"operation": "vectorizer.Embed",
"params": {
"content_ref": "urn:doc:9f3f#segment:summary-output",
"normalize": true,
"dims": 1536
},
"policy_context": {
"operation": "gateway.invoke",
"requested_scopes": ["rpc:vectorizer.Embed"],
"data_tags": ["phi"]
}
}
Operator Lane — Gateway RPC (response)
{
"trace_id": "6b3c8a10-6d3f-4f1e-9d41-9e5a7a12c0c7",
"policy_decision": {
"allowed": true,
"rule": "allow:rpc:vectorizer.Embed",
"redactions": []
},
"result": {
"embedding_ref": "urn:vec:9f3f#v1",
"dims": 1536,
"vector_checksum": "sha256-91ba77...",
"duration_ms": 34
},
"audit_id": "aud_01HZXK4VJCCEV5Q2V8"
}
Failure Modes (blocking conditions)
- Proxy: domain not allowlisted; IP literal/localhost; non‑TLS; forbidden path; missing secrets; rate‑limit exceeded → request denied and audited.
- Canonical: schema mismatch; missing/invalid classification; producer lacks
handles_phi; tenant/policy violation → event rejected and audited. - Operator: operation not allowlisted; SSRF‑unsafe parameters (scheme present, non‑internal host); caller lacks traits/permissions; rate‑limit exceeded → call denied and audited.
Practical Tips for LLM Plugin Authors
- Always tag payloads with correct classification (
phi,pii,confidential) andtenant_id. - Use Canonical for sharing results/events; minimize PHI content in events; prefer references/hashes.
- Use Operator for internal, synchronous helpers; pass references instead of raw PHI when possible.
- Use Proxy for any external API; never embed credentials or full URLs; request domains/paths that are pre‑allowlisted.
- Keep
trace_idconsistent across lanes to preserve end‑to‑end lineage.
Admin Console Parity
- Expose
/auth/mefor trait discovery; Admin Console hides/shows features accordingly. - Provide
/admin/ui-configand/admin/user/traitsfor feature flags and trait mapping. - Diagnostics: health, provider status, and lane metrics; all trait‑aware and least‑privilege by default.