Skip to content

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.

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.

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.

A single /v1/chat/completions call:

1. Edge (Cloud Run) terminates TLS
2. 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 decides
6. 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 agent

Steps 5-9 run in a PipelineContext that carries: the agent, the tenant, detected findings so far, the original + modified payload, and counters (tokens, cost).

  • 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.

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.

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.

  • 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)
  • 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.