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.
1. Install and onboard
Section titled “1. Install and onboard”pip install tappass # SDK + the full workspace CLItappass config set server http://localhost:9620tappass logintappass 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:
export TAPPASS_URL=http://localhost:9620export TAPPASS_API_KEY=tp_dev_...2. First governed chat
Section titled “2. First governed chat”import osfrom 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 answerprint(response.pipeline.classification) # data classification, e.g. "INTERNAL"print(response.pipeline.blocked) # False — the kernel allowed itprint(response.usage.total_tokens) # token usageprint(response.session_id) # correlation IDprint(response.audit_url) # deep-link to this session's audit trailEvery 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.
3. Streaming
Section titled “3. Streaming”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.
4. Handle governance decisions
Section titled “4. Handle governance decisions”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 attemptexcept 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).
5. Wait for a human approval
Section titled “5. Wait for a human approval”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.
6. Correlate a run with tappass_session
Section titled “6. Correlate a run with tappass_session”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.idEach block runs under one session, so chat calls and governed tools inside it share a single session ID in the audit trail.
7. Govern tool calls against /v1/govern
Section titled “7. Govern tool calls against /v1/govern”Gate individual tools — works with any framework’s tool objects or plain callables:
import tappassfrom 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 executedexcept 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.
9. Test it — watch your policy act
Section titled “9. Test it — watch your policy act”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:
tappass agent mode my-first-agent enforcing # watching | holding | enforcingThen exercise the policy:
tappass try "email the Q4 numbers to finance" --dry-run # governed agentic loop: # every tool call shows its verdict, nothing executestappass chat "Summarize our refund policy" # one governed call through the same pipelinetappass policy explain answers “would this block?” without sending any traffic. It takes a JSON file describing the call to simulate:
cat > whatif.json <<'EOF'{"tool": "send_email", "messages": [{"role": "user", "content": "email the Q4 numbers to finance"}]}EOFtappass policy explain my-first-agent --event-type TOOL_CALL --input whatif.jsonWhen 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.
Next steps
Section titled “Next steps”- The TapPass path — where this fits in the full journey, and what comes after
- Agent client reference — full method and exception reference
- Governance from code —
govern()modes, sessions, enforce-mode exceptions - Resilience — surviving TapPass outages
- Troubleshooting — common errors and fixes