ticket-triage
An http input takes support tickets ({subject, body}), an agent node asks Claude for a category, a priority and a one-line summary (checked against triage.schema.json), and the answer is appended to ./out/triaged.jsonl. agent_budget caps the cost: $0.50 a day for this pipeline and 4,000 tokens per ticket, with a warning at 80%.
Run it with the mock provider (no API key, no cost)#
mock-claude.ts answers like the Anthropic Messages API, classifying by keywords, and reports 400 input and 100 output tokens per call ($0.0009 at the built-in Haiku price). Use a throwaway Pipo home so nothing touches ~/.pipo:
export PIPO_HOME=/tmp/pipo-triage MOCK_CLAUDE_KEY=unused
mkdir -p $PIPO_HOME
cat > $PIPO_HOME/config.yaml <<'EOF'
engine:
timezone: UTC
agent_budget: { per_day: 1.00 } # all pipelines of this home together (spec §3.11)
agents:
claude_api:
base_url: http://127.0.0.1:8790
api_key: env:MOCK_CLAUDE_KEY
EOF
bun examples/ticket-triage/mock-claude.ts & # the mock, on port 8790
bun pipo run examples/ticket-triage/ticket-triage.pipo
# in another terminal:
curl -s localhost:8791/in/ticket-triage/tickets -H 'content-type: application/json' \
-d '{"subject": "Site is down", "body": "Checkout fails for everyone since 9:00"}'
bun pipo status --no-engine shows the day's agent spend against engine.agent_budget.per_day. Lower per_day in the pipeline or in config.yaml to see a budget pause: packets keep being accepted, and the pipeline resumes by itself when the next budget day starts (or at once with pipo resume, which goes over the cap until then).
For real calls, drop base_url and point api_key at a secret reference such as op://vault/anthropic/key (1Password).
Test it#
bun pipo test examples/ticket-triage
fixtures/stubs.json answers the agent node, so the test makes no calls at all.
ticket-triage.pipo#
# Triages support tickets with an agent node, under a daily and a per-ticket budget.
# Runs against the bundled mock of the Claude API (no API key, no cost); see README.md:
# bun examples/ticket-triage/mock-claude.ts # then point agents.claude_api.base_url at it in config.yaml
# bun pipo run examples/ticket-triage/ticket-triage.pipo
# curl -s localhost:8791/in/ticket-triage/tickets -H 'content-type: application/json' -d '{"subject": "Site is down", "body": "Checkout fails for everyone"}'
pipo: 1
name: ticket-triage
description: Classify each support ticket (category, priority, one-line summary) with an agent, within a budget.
agent_budget:
per_day: 0.50
per_packet: 4000
warn_at: 80%
input:
via: http
with:
listen: 8791
path: /tickets
format: json
validate:
- type(data.subject) == "string" && len(trim(data.subject)) > 0
- type(data.body) == "string"
on_invalid:
respond: 422
message: "Rejected: rule '${error.rule}' failed; send {subject, body}"
nodes:
triage:
from: input
agent: claude_api
with:
model: claude-haiku-4-5
prompt: |
Triage this support ticket. Pick its category and priority and summarise it in one sentence.
Subject: ${data.subject}
Body: ${data.body}
schema: ./triage.schema.json
timeout: 30s
max_tokens: 512
on_error:
retry: 2
backoff: exponential
delay: 1s
then: dead_letter
output:
from: triage
to: file
with:
path: ./out/triaged.jsonl
format: jsonl
mode: append
delivered:
check: line_contains
with: { value: "${meta.packet_id}" }
within: 5s
Source: examples/ticket-triage.