agent-ticketing
v0.1.5
Published
Agent SDK for Agent Mailbox — poll tickets, heartbeat, ask the requester, complete. Zero dependencies.
Maintainers
Readme
agent-ticketing
Agent SDK for Agent Ticketing — claim tickets, heartbeat, ask the requester, complete. Zero dependencies, Node 18+.
npm install agent-ticketing # (or use it from this repo via workspaces)Quick start
import { MailboxAgent } from "agent-ticketing";
const mailbox = new MailboxAgent({
baseUrl: "https://agent-ticketing.<you>.workers.dev",
apiKey: process.env.MAILBOX_API_KEY, // mbx_live_… from Settings → Agents
});
await mailbox.poll(async (ticket, ctx) => {
await ctx.progress("reading the request"); // heartbeat + visible note
const env = await ctx.ask("staging or prod?"); // → pending, waits for the human
// ...do the work...
const id = await ctx.attach("./report.pdf"); // staged upload
await ctx.complete(`Deployed to ${env}`, { attachmentIds: [id] }); // → done
});Polling modes
await mailbox.poll(handler); // continuous: pickup ≤ ~3s
await mailbox.poll(handler, { mode: "interval", every: "5m" }); // lazy: near-free, pickup ≤ 5mThe server holds each poll cheaply (facade claim gate) — continuous polling does NOT keep the Durable Object hot, so pick the mode by latency preference, not cost.
Semantics you get for free
- Auto-heartbeat every 45s while your handler runs (server expiry: 3 min of silence).
- Crash recovery: the instance id defaults to
host:<hostname>(stable), so if your process dies mid-ticket, the nextpoll()re-delivers the same ticket to you. - Claim-loss detection: if the human resolves/cancels the ticket while you work,
ctx.signalaborts and the nextctx.*call throwsClaimLostError— stop quietly. - Handler errors → release: the ticket goes back to human triage (unassigned) with
the error in the thread. Use
onError: "abandon"to let the claim expire and retry yourself later instead. - Returning a string from the handler is shorthand for
ctx.complete(thatString). - Retries: 429s honor
Retry-After; 5xx/network errors back off exponentially. Message posts are idempotent (client-generated ULIDs), so retries never duplicate. - Graceful shutdown: SIGINT/SIGTERM stop the loop; in-flight claims are re-delivered to this instance on restart.
Examples
examples/simple-agent.mjs— the full context API.examples/runner-claude.mjs— session-based agents (Claude Code, Codex): the runner owns the loop and heartbeats,claude -powns the thinking, the ticket is completed with its output.
# persistent service (recommended): starts now, restarts on crash, survives reboots
MAILBOX_URL=https://… MAILBOX_API_KEY=mbx_live_… \
npx -y -p agent-ticketing mailbox-claude-runner --install-service
# one-off foreground run: drop --install-service (add --interval 5m for lazy polling)
# remove the service later: mailbox-claude-runner --uninstall-service--install-service writes a launchd LaunchAgent (macOS) or a systemd user unit
(Linux) with your env baked in. Linux + reboot-without-login also needs
loginctl enable-linger $USER once.
Runners are agent-specific — mailbox-claude-runner launches headless Claude Code
sessions. Runners for Codex and daemon-style agents (Hermes, OpenClaw) are planned;
for a custom harness, embed the SDK instead (below).
Runner behavior (v0.1.3)
- Runs Claude with
--permission-mode bypassPermissions— an autonomous runner must edit files and run commands unattended. Override withCLAUDE_PERMISSION_MODE(default,acceptEdits,plan). - Ask → pending → resume: if Claude asks a question mid-task (AskUserQuestion),
the runner posts it to the ticket (
pending), and after the requester replies it resumes the same Claude session with the answer — full conversational loop. - Announces the Claude session id in the ticket thread and the ndjson log
(
claude_sessionevent) — inspect a stuck run withclaude --resume <id>. - Live progress: tails the session's JSONL and posts throttled ⚙ steps
("Bash: npm test", "Edit: app.ts") to the ticket — watch the agent work from
the dashboard. Sessions run in
MAILBOX_WORKDIR(default~/.agent-ticketing/workspace). - Deliverables: the prompt tells Claude to save output files into a per-ticket deliverables directory; the runner uploads them as ticket attachments on completion (≤10 files, ≤25MB each — anything skipped is named in the summary). Images preview inline in the dashboard.
CLAUDE_TIMEOUT_MS(default 10 min, 0 disables) with SIGTERM→SIGKILL escalation; JSON output is salvaged from timed-out processes when possible.
Low-level client
Every REST endpoint is also exposed directly if you don't want the loop:
claimNext, getTicket, heartbeat, postMessage, complete, release,
uploadAttachment.
