Skip to content

Understanding the output

Every governed call returns more than the model’s text. This page annotates real response shapes so you know what you’re looking at — in the SDK, in the raw API, and in the dashboard.

response = agent.chat("Summarize Q4 revenue")
response.content # the assistant's answer
response.pipeline.blocked # False
response.pipeline.classification # "INTERNAL" — data classification of the call
response.pipeline.steps_run # how many checkpoints ran
response.pipeline.total_duration_ms
response.usage.total_tokens # 157
response.session_id # "ses_…" ─┐
response.task_id # "tsk_…" │ five correlation IDs —
response.agent_uuid # │ every one is queryable
response.pipeline_uuid # │ in the audit API
response.audit_url # dashboard deep-link to this exact call

The five correlation IDs are the important part. Any question — why was this allowed? what did it cost? what else happened in this session? — starts from one of these.

Same call through the gateway:

{
"choices": [{"message": {"role": "assistant", "content": ""}}],
"usage": {"prompt_tokens": 12, "completion_tokens": 145, "total_tokens": 157},
"tappass": {
"session_id": "ses_abc123",
"task_id": "tsk_…",
"audit_url": "https://app.tappass.ai/audit/…",
"blocked": false
}
}

The tappass envelope is added to the standard provider response. The gateway always returns HTTP 200 — governance outcomes are data, not HTTP errors.

from tappass import PolicyBlockError
try:
agent.chat("List all customer credit card numbers")
except PolicyBlockError as e:
e.blocked_by # the rule/step that decided, e.g. a BlockPII rule
e.reason # machine-readable reason, e.g. "pii_in_input"
e.classification # "RESTRICTED"
e.audit_url # the blocked attempt is in the audit trail too

Through the raw API, a block arrives as an assistant message explaining the block, with tappass.blocked: true. Your agent reads it like any other refusal — it never crashes on governance.

Three things to internalize:

  1. Blocked attempts are audited. The trail records what was tried, what fired, and the full evidence — that’s your incident queue.
  2. The reason names a rule. e.blocked_by deep-links to the exact rule and policy version. If the block is wrong, the fix is a policy change — not a code change.
  3. Don’t retry blindly. Retrying an identical request under an unchanged policy yields the same decision — retrying blindly won’t get a different answer.

Policy can allow a call while changing it — the redact obligation masks PII/secrets in place:

response = agent.chat("Email a summary to jane@acme.com")
response.content # clean answer; the call went through

By default, redaction is invisible: the call succeeds with clean content. Set Agent(raise_on_redaction=True) to surface it as a RedactionApplied exception carrying the exact spans (redactions: list[Redaction], redacted_content).

A RequireApproval rule doesn’t block — it suspends:

from tappass import ApprovalRequired
try:
agent.chat("Wire €50,000 to supplier 881")
except ApprovalRequired as e:
e.approval_url # dashboard deep-link for the human
```python
result = agent.wait_approval(e.request_id, timeout_seconds=300)
result["state"] # "approved" | "denied" | "timed_out" | "cancelled"

One approval authorizes exactly the fingerprinted action. Resubmitting the identical call passes; a different amount starts a new approval.

What you got Where to look
audit_url Opens the exact event in Audit Trail
session_id Sessions → full trace with spans and applied rules
blocked_by / rule id Policies → the owning policy and version
Approval request Bell icon → Approvals inbox
Anything odd Command Center → “Recent flagged events”