Skip to content

Domain objects

The platform has ~12 domain objects that do real work. If you can name each one and explain in one sentence what it's for, you can reason about any code path. This page is that glossary.

Conventions

  • Hot path = request-time, per-LLM-call.
  • Control path = admin/dashboard, per-user-action.
  • All paths below are rooted in tappass/tappass/.

First-class citizens — if you remember five, remember these

Section titled “First-class citizens — if you remember five, remember these”

These are the load-bearing objects. Every other domain class is either a field on one of these, or a helper that one of these consumes.

ObjectOne-line purposeWhere you meet it first
AgentThe governed AI workload — what TapPass regulatesEvery request: ctx.agent; every route: resolve_agent_or_404
PipelineThe ordered governance plan this agent runs underCompiled once per request by the runner; defines which steps fire
DecisionThe policy outcome (allow / block / escalate / modify / dispatch)Emitted by OPA + the in-process dispatcher; shapes the response
MandateSigned authorization, minted when a Decision allowsEmbedded JWS on Decision.mandate; AP2-compatible wire format
AuditEventThe tamper-evident trail that everything decides againstWritten by every step that matters; hash-chained + Ed25519-signed

Everything else — PipelineContext, Detection, AgentSession, AgentPact, AgentTrustProfile, MandateCapability — either carries state between these five or provides evidence one of them needs. Read the five, then the rest will fall into place.

flowchart LR
  A[Agent] -->|governed by| P[Pipeline]
  A -->|session of| S[AgentSession]
  P -->|produces findings| DT[Detection]
  DT -->|evaluated by OPA| DEC[Decision]
  DEC -->|if allow, mints| M[Mandate]
  A -.->|every action emits| AE[AuditEvent]
  P -.-> AE
  DEC -.-> AE
  M -.-> AE
  classDef first fill:#2a3555,stroke:#6f8bd8,stroke-width:2px,color:#fff
  classDef second fill:#1b2136,stroke:#394668,color:#cfd4e6
  class A,P,DEC,M,AE first
  class S,DT second

Bold blue = the five first-class citizens. Dotted lines = "emits audit". Solid lines = data dependency.

models/agent.py:43 — Pydantic model. The governed entity.

An Agent is an AI workload that makes LLM calls under our supervision. Every agent belongs to exactly one org and has a slug (agent_id, human-readable, mutable over time) and a UUID (id / agent_uuid, immutable, what APIs address).

Key fields: id, agent_id, org_id, owner, framework, declared_capabilities, active, governance_paused, metadata.

Created by /api/agents/onboard; the UUID is the stable identifier — never key off the slug in code that crosses a rename boundary.

models/agent.py:76 — Pydantic model. A conversation thread.

Groups consecutive LLM calls from one agent into one session so we can aggregate cost, detections, and escalation state. Bounded by inactivity timeout + explicit session-end.

Key fields: session_id, org_id, agent_id, message_count, started_at, last_activity, max_classification, block_count, violation_count, escalated.

One AgentSession per conversation; many AuditEvents roll up to one session.

models/pipeline.py:50 — Pydantic model. The ordered governance plan for an agent.

A Pipeline is a tree: categoriessteps. It's compiled from the org's OPA policy, the agent's Profile, and any per-agent overrides. The runner walks it top-to-bottom for every LLM call.

Key fields: id, org_id, name, categories, assigned_agents, enforcement_mode, version.

Stored in pipelines Postgres table. Per-agent mapping is a second lookup in the org's policy store.

registry/pact.py:55 — dataclass. The declared contract.

A Pact is what the operator promises about the agent: its purpose, max data classification, allowed tools, cost envelope, PII exposure posture. It's the intent. Compared at runtime against observed behaviour to produce adherence / drift signals.

Key fields: agent_id, purpose, max_classification, allowed_data_types, allowed_tools, cost_envelope, pii_exposure.

verified/profile.py — dataclass. The verifiable evidence package.

A signed (ES256) artefact summarising what an agent actually did over a window — not a judgement, just observations. Shared with partners or auditors who don't have access to our database.

Generated from the audit trail; cannot be forged without the signing key.

pipeline/context.py:106 — dataclass. The per-request flow container.

Created once per governed call and passed through every step. Carries the agent, session, the original + possibly modified payload, the running list of Detection objects, token/cost counters, and the audit-event buffer.

When you add a field to the request-time flow, it goes on PipelineContext — not on request.state, not on a module global.

pipeline/backends/__init__.py:28 — dataclass. Normalized finding from any backend.

When NeMo Guardrails, LLM Guard, Azure Content Safety, or a hand-rolled regex step sees something interesting, it emits a Detection. Same shape for every backend so policy can reason over all of them uniformly.

Key fields: category, severity, label, score, text, backend, metadata.

Steps produce Detection[]; policy consumes them.

behaviors/decisions.py:46 — frozen dataclass. The policy outcome.

The structured answer from the policy engine: one of allow | block | escalate | modify | dispatch_to_sandbox, plus why and what to do next. When outcome is allow and a downstream call is going to execute, the Decision also carries an embedded Mandate (JWS).

Key fields: outcome, reason, behavior_id, pipeline_id, decided_at, details, mandate (JWS string or None).

Produced by /v1/govern and by the in-process dispatcher (behaviors/dispatch.py).

mandates/models.py:78 — frozen dataclass. The live authorization primitive.

A signed, scoped, revocable authorization issued when policy allows an action. Supersedes the older CapabilityToken for in-process flows. Wire format is AP2-compatible so it's portable across A2A / gateway / sandbox contexts.

Key fields: subject, issuer, capabilities (tuple of MandateCapability), mandate_id, issued_at, expires_at, correlation_id, signature.

Minted by dispatch_behavior() whenever a Decision resolves to allow. Embedded in the Decision.mandate field as a JWS string so callers can verify offline.

mandates/models.py — part of Mandate. One permission slice.

Each Mandate holds a tuple of these. Structured as action:resource:qualifier — e.g. call_llm:anthropic:claude-opus. Downstream enforcers check this tuple against the action they're about to perform, not the string-y outcome name.

_sdk/capability_token.py:338 — dataclass.

Historical context: tokens supported offline PoP + delegation chains. Those features migrated into Mandate's JWS + capability tuple model.

models/audit.py:19 — Pydantic model. The tamper-evident log entry.

Every governed action emits one. Hash-chained (each event's hash commits to the previous event's hash) and Ed25519-signed so the trail is provably unbroken. Retained per DPA (7 years in cold storage for compliance; see Customer data export for the retention carve-out).

Key fields: event_id, timestamp, org_id, event_type, agent_id, session_id, details, source_framework.

Written by audit/writer.py; verified by audit/integrity.py (runs every 4 hours in the background).

Full map, split into control-path (who an Agent is and what it's promised to do) and hot-path (what happens on each LLM call).

flowchart TB
  subgraph Control["Control path — who / what"]
    Org[Org / Tenant]
    Agent[Agent]
    Pact[AgentPact
declared contract] Profile[AgentTrustProfile
signed evidence] Pipeline[Pipeline
governance plan] Org --> Agent Agent --> Pact Agent --> Profile Agent -->|assigned| Pipeline end subgraph Hot["Hot path — per request"] Ctx[PipelineContext] Steps[49 pipeline steps] Det[Detection array] Policy[OPA policy engine] Dec[Decision] Mand[Mandate JWS] Ctx --> Steps Steps --> Det Det --> Policy Policy --> Dec Dec -->|if allow| Mand end Agent -.->|loaded into| Ctx Pipeline -.->|drives| Steps subgraph Audit["Audit trail — hash-chained"] AE[AuditEvent] end Agent -.-> AE Dec -.-> AE Mand -.-> AE Steps -.-> AE classDef control fill:#1b2f3a,stroke:#3c6a82,color:#cfe6f2 classDef hot fill:#2a3555,stroke:#6f8bd8,color:#eef1ff classDef audit fill:#2e2a1b,stroke:#8a7840,color:#f5eecb class Org,Agent,Pact,Profile,Pipeline control class Ctx,Steps,Det,Policy,Dec,Mand hot class AE audit
RelationshipCardinality
Org → Agent1 org : many agents
Agent → AgentSession1 agent : many sessions
Agent → AgentPact1 : 0-or-1 (optional)
Agent → Pipelinemany agents : 1 pipeline (share org defaults)
PipelineContext → Detection1 : many (one per step per finding)
Decision → Mandate1 : 0-or-1 (only if outcome == allow)
Any of above → AuditEvent1 : many (each action logs)

Before adding a 13th domain class, ask:

  1. Is it actually new, or is it a field that belongs on an existing object? (90% of the time, it's a field.)
  2. Does it have its own lifecycle, or is it just a shape one of these objects takes? (Prefer composition over a new class.)
  3. Will the audit trail need to reference it by ID? If not, consider a plain dict or tuple.

If it passes all three, add it here on the same PR that creates it. Undocumented classes in models/ are a liability.