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
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”