Architecture overview
The public architecture page is correct but it's the customer-facing version. This page is the internal one — the mental model you need to debug, extend, or explain the system to a colleague.
One-sentence summary
Section titled “One-sentence summary”TapPass is a governed proxy in front of AI agents — everything flows through a configurable pipeline that detects, blocks, redacts, and audits, then out to a real LLM provider.
If you understand that sentence, the rest is detail.
The three planes
Section titled “The three planes”TapPass has three logical planes that run in the same process but should be understood separately:
flowchart TB
subgraph Data["DATA PLANE — /v1/chat/completions, /v1/messages"]
D1["Runs the pipeline
Calls providers
Streams back"]
end
subgraph Control["CONTROL PLANE — /api/v1/admin/*, /audit/*, /health/*"]
C1["Manages agents, policies, keys
Mandate lifecycle"]
end
subgraph Observ["OBSERVABILITY PLANE — /export/*, webhooks"]
O1["Ships events out to customer SIEMs"]
end
classDef data fill:#2a3555,stroke:#6f8bd8,color:#eef1ff
classDef ctrl fill:#1b2a3a,stroke:#406f8a,color:#c7dff5
classDef obs fill:#2e2a1b,stroke:#8a7840,color:#f5eecb
class D1 data
class C1 ctrl
class O1 obs
They share the database but are scoped by different auth: data plane uses customer tp_ keys, control plane uses session auth (SSO), observability uses API tokens.
See Key flows for how each plane looks in practice.
Request lifecycle
Section titled “Request lifecycle”A single /v1/chat/completions call:
1. Edge (Cloud Run) terminates TLS2. Gateway authenticates the tp_ key (identity/api_key.py)3. Request is matched to an agent + tenant (registry.py)4. Pipeline context is built (pipeline/context.py)5. Pre-LLM steps run in order (pipeline/engine.py) - each returns Detection[]; policy decides6. Credentials resolved from vault (vault/*)7. Provider client constructed (gateway/<provider>.py)8. Streaming response from provider (httpx or SSE)9. Post-LLM steps run on chunks (pipeline/engine.py)10. Audit event written (hashed, signed) (audit/writer.py)11. Response streams back to the agentSteps 5-9 run in a PipelineContext that carries: the agent, the tenant, detected findings so far, the original + modified payload, and counters (tokens, cost).
Why this shape
Section titled “Why this shape”- Stateless per request. No shared mutable state between requests. Lets us horizontally scale on Cloud Run.
- Pipeline is data, not code. Steps are registered in a config file, not a Python list. Customer-specific pipelines are YAML, not forks.
- Policy is separate from detection. A step detects; policy decides. Same step set serves different customers with different policies.
- Audit-first. Every decision must land in the audit trail. If you touch the request path and your change doesn't emit an audit event, it's incomplete.
What lives where
Section titled “What lives where”Repo: tappass/tappass
flowchart LR
subgraph Ingress
API["api/
FastAPI routes — THIN, no business logic
v1/ · admin/ · health/"]
end
subgraph HotPath["Hot path (per request)"]
direction LR
GW["gateway/
provider clients
anthropic.py · openai.py · …"]
PL["pipeline/
engine.py · runner.py
steps/ · backends/ · context.py"]
POL["policy/
engine.py (OPA wrapper)
rules/ defaults"]
VAULT["vault/
protocol.py
providers/ (postgres, file, HashiCorp)"]
AUDIT["audit/
writer.py (hash chain + Ed25519)
integrity.py · archive.py"]
end
subgraph Identity
ID["identity/
api_key.py (tp_)
sso.py · saml.py · spiffe/"]
end
subgraph Observability
OBS["observability/
export/ (Splunk, Sentinel, webhooks)
webhooks.py"]
end
subgraph Shared
DOM["domain/
pure Python, no I/O"]
CFG["config/
Pydantic Settings"]
MAIN["main.py
composition root"]
end
API --> ID
API --> PL
PL --> POL
PL --> VAULT
PL --> GW
PL --> AUDIT
GW --> AUDIT
AUDIT --> OBS
classDef ingress fill:#2a3555,stroke:#6f8bd8,color:#eef1ff
classDef hot fill:#1f3a2c,stroke:#468a68,color:#c7f5d7
classDef ident fill:#3a2e1b,stroke:#8a7240,color:#f5e7c7
classDef obs fill:#2e2a1b,stroke:#8a7840,color:#f5eecb
classDef shared fill:#1b2a3a,stroke:#406f8a,color:#c7dff5
class API ingress
class GW,PL,POL,VAULT,AUDIT hot
class ID ident
class OBS obs
class DOM,CFG,MAIN shared
When in doubt, trace a request top-to-bottom through the api → pipeline → gateway → audit chain.
The frontend
Section titled “The frontend”React 19 + TypeScript + Tailwind + Radix. Lives at tappass/frontend/ inside the tappass/ repo (not a separate repo). Talks to the control plane via /api/v1/*. See Frontend architecture.
Subsystems worth their own page
Section titled “Subsystems worth their own page”- Domain objects — canonical glossary of
Agent,Pipeline,Decision,Mandate, and friends - Key flows — real traces (LLM call, policy change, audit export) with sequence diagrams
- Pipeline step anatomy — how to write and wire a step
- Hooks — external agent-tool hooks (
POST /hooks) and internal pipeline phase hooks - Data model — Postgres schema, which tables actually matter
- Frontend architecture — React structure, state, theming
- Deployment architecture — runtime topology (Cloudflare → Cloud Run → VPC → Cloud SQL)
Things we deliberately do NOT do
Section titled “Things we deliberately do NOT do”- No shared monolith. SDK is HTTP-only. Fracturing it would hurt customers.
- No agent SDK state on the server. Multi-turn conversation lives client-side.
- No blocking waits on external systems in the hot path. Detection backends and policy must have local-first paths or fail-open-to-audit.
- No caching of customer data. Redaction findings are computed fresh every call.
- No "fix in the API layer". Business rules belong in domain/pipeline, not in routes.