Skip to content

Your First Agent

This page walks through a complete first agent against a local TapPass server: installing the SDK, getting an API key, governed chat and streaming, handling policy blocks and human approvals, correlating work in a session, and gating tool calls with govern().

Prerequisites: Python ≥ 3.11 and a TapPass server running at http://localhost:9620, if you don’t have one yet, see self-hosting or ask your platform team for your deployment URL.

Terminal window
pip install tappass # SDK + the full workspace CLI
tappass config set server http://localhost:9620
tappass login
tappass agent create my-first-agent # prints a dev API key: tp_dev_...

tappass login signs in with your account (email, Google, or SAML). On a fresh self-hosted server there are no accounts yet, bootstrap with the admin key instead: first login and your workspace.

The pip package includes the complete CLI, every command talks to your configured server, so this works from any machine. (You can also create the agent in the dashboard under Agents → New.)

Set the environment so examples below need no hard-coded secrets, or skip both exports entirely with tappass auth application-default login, which the SDK picks up automatically:

Terminal window
export TAPPASS_URL=http://localhost:9620
export TAPPASS_API_KEY=tp_dev_...
import os
from tappass import Agent
agent = Agent(os.environ["TAPPASS_URL"], os.environ["TAPPASS_API_KEY"])
response = agent.chat("Summarize the GDPR requirements for a SaaS startup.")
print(response.content) # the assistant's answer
print(response.pipeline.classification) # data classification, e.g. "INTERNAL"
print(response.pipeline.blocked) # False: the kernel (TapPass's decision engine) allowed it
print(response.usage.total_tokens) # token usage
print(response.session_id) # correlation ID
print(response.audit_url) # deep-link to this session's audit trail

Every response carries five correlation IDs, session_id, task_id, agent_uuid, pipeline_uuid, audit_url, so you can deep-link to the dashboard or query the audit API for exactly what happened.

for chunk in agent.stream("Write a compliance report outline"):
print(chunk.delta, end="", flush=True)

Each ChatChunk has delta (text) and finished. Correlation fields are populated on the first chunk; later chunks may leave them empty.

Policy outcomes are typed exceptions, not HTTP codes:

from tappass import PolicyBlockError, ApprovalRequired
try:
response = agent.chat("List all customer credit card numbers")
except PolicyBlockError as e:
print(e.blocked_by) # which step blocked, e.g. a PII rule
print(e.reason) # human-readable reason
print(e.audit_url) # audit trail for the blocked attempt
except ApprovalRequired as e:
# Execution paused for a human. e.approval_url is the dashboard
# deep-link; e.resume_token resumes; e.expires_at is the deadline.
print(f"Needs approval: {e.approval_url}")

PolicyBlockError is a deliberate decision, don’t retry it blindly. See the Agent client reference for the full GovernanceDecision tree (RedactionApplied, TrustTierDenied, BreakGlassActive, ToolIntegrityViolation, AuthorizationRequired).

When you already have a request_id (e.g. from an escalate decision), block until the human decides:

result = agent.wait_approval(request_id, timeout_seconds=300)
print(result["state"]) # "approved" | "denied" | "timed_out" | "cancelled"

poll_approval(request_id) is the one-shot, non-blocking variant for rendering pending state in your own UI.

from tappass import tappass_session
with tappass_session(
url=os.environ["TAPPASS_URL"],
api_key=os.environ["TAPPASS_API_KEY"],
) as session:
agent.chat("Draft the Q4 board summary")
# govern()-wrapped tools called here inherit the same session.id

Each block runs under one session, so chat calls and governed tools inside it share a single session ID in the audit trail.

Gate individual tools, works with any framework’s tool objects or plain callables:

import tappass
from tappass import ApprovalPending, GovernanceBlocked
def send_email(to: str, subject: str, body: str) -> str:
... # actually send
return "sent"
tools = tappass.govern(
[send_email],
url=os.environ["TAPPASS_URL"],
api_key=os.environ["TAPPASS_API_KEY"],
agent_id="my-first-agent",
mode="enforce", # POST a TOOL_CALL Behavior to /v1/govern first
)
try:
tools[0](to="cfo@acme.com", subject="Q4", body="...")
except GovernanceBlocked as e:
print(f"Blocked by policy: {e.reason}") # tool never executed
except ApprovalPending as e:
print(f"Suspended pending approval: {e.request_id}")
# Re-submit the identical call later, it returns allow once approved.

In mode="audit" (the default) executions are only reported to the audit trail. In mode="enforce" a block outcome raises GovernanceBlocked and the tool never runs; an unreachable server raises GovernanceUnavailable (fail-closed unless TAPPASS_FAIL_OPEN=1).

8. Skip the blank policy screen: apply a policy template

Section titled “8. Skip the blank policy screen: apply a policy template”

Everything above runs against the platform safety floor. Instead of authoring your first rules by hand, apply a curated policy template, the dashboard’s Policies → Templates catalog ships tested starting points (an OWASP agentic baseline, coding-agent safety, outbound-messaging envelopes, and more).

Pick one, set its parameters (e.g. allowed_models), and it lands in shadow mode, decisions are recorded but nothing blocks. Watch the evidence, then switch it to enforce. What lands is ordinary policy: edit, version, and publish it like anything you wrote yourself.

New organizations watch first. Your agent starts in watching mode: every rule evaluates and records its verdict, but nothing blocks, the decision shows would_block evidence instead. That is deliberate (day one should observe, not break things). When the evidence looks right, promote:

Terminal window
tappass agent mode my-first-agent enforcing # watching | holding | enforcing

Then exercise the policy:

Terminal window
tappass try "email the Q4 numbers to finance" --dry-run # governed agentic loop:
# every tool call shows its verdict, nothing executes
tappass chat "Summarize our refund policy" # one governed call through the same pipeline

tappass policy explain answers “would this block?” without sending any traffic. It takes a JSON file describing the call to simulate:

Terminal window
cat > whatif.json <<'EOF'
{"tool": "send_email", "messages": [{"role": "user", "content": "email the Q4 numbers to finance"}]}
EOF
tappass policy explain my-first-agent --event-type TOOL_CALL --input whatif.json

When a call is held for approval, the response carries an approval.request_id. The approver sees it with tappass approval list and decides, approve, and the agent’s identical re-submitted call goes through. That is the whole human-in-the-loop arc: hold → approve → allow.

All of these work from anywhere, tappass try streams the loop’s steps and verdicts live from the server. They reason with a model, so the server needs an LLM key; without one they say so and nothing else breaks. When a verdict surprises you, ask Jorge, TapPass’s built-in assistant: tappass ask "why was that blocked?". When the verdicts look right, promote the policy from shadow to enforce, and watch mode plus tappass audit replay keep answering “what would change?” from real history.