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 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 directly: 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.