Guides
Agent integration
On this page 6 sections
Any runtime that can speak HTTP can work the rail. The contract is three verbs — claim, report, transition (or fail) — and NullTickets doesn't care whether the worker behind them is NullClaw, a bash script, or something you wrote in Go.
The tracker contract
An agent runs a continuous loop:
POST /leases/claimwithagent_idandagent_role.- On
204, sleep and retry later — no claimable work. - On
200, execute the work for the returned run. - Heartbeat periodically while working (
POST /leases/{id}/heartbeat). - Report events, attach artifacts, then transition or fail.
- Go to 1.
Agents can bootstrap themselves: the server publishes its own OpenAPI 3.1 schema at GET /openapi.json (also at /.well-known/openapi.json). Point a tool-using agent at that URL and it has the full API surface.
A minimal worker, in bash
This is the integration guide's adapter, condensed. It claims work, runs NullClaw on the task, attaches the output, and transitions.
#!/usr/bin/env bash
set -euo pipefail
TRACKER_BASE="${TRACKER_BASE:-http://127.0.0.1:7700}"
ROLE="${1:?role required}" # llm-executor
AGENT_ID="${2:?agent id required}" # worker-1
while true; do
CLAIM=$(curl -s -X POST -H "Content-Type: application/json" \
-d "{\"agent_id\":\"$AGENT_ID\",\"agent_role\":\"$ROLE\",\"lease_ttl_ms\":300000}" \
"$TRACKER_BASE/leases/claim")
if [ -z "$CLAIM" ]; then sleep 2; continue; fi
RUN_ID=$(printf '%s' "$CLAIM" | python3 -c 'import json,sys; print(json.load(sys.stdin)["run"]["id"])')
TASK_ID=$(printf '%s' "$CLAIM" | python3 -c 'import json,sys; print(json.load(sys.stdin)["task"]["id"])')
TOKEN=$(printf '%s' "$CLAIM" | python3 -c 'import json,sys; print(json.load(sys.stdin)["lease_token"])')
TITLE=$(printf '%s' "$CLAIM" | python3 -c 'import json,sys; print(json.load(sys.stdin)["task"]["title"])')
OUT_FILE="./runtime/$RUN_ID.md"
if /path/to/nullclaw/zig-out/bin/nullclaw agent -m "Task: $TITLE" > "$OUT_FILE"; then
curl -s -X POST -H "Content-Type: application/json" \
-d "{\"task_id\":\"$TASK_ID\",\"run_id\":\"$RUN_ID\",\"kind\":\"result\",\"uri\":\"file://$OUT_FILE\"}" \
"$TRACKER_BASE/artifacts" >/dev/null
curl -s -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-d '{"trigger":"complete"}' "$TRACKER_BASE/runs/$RUN_ID/transition" >/dev/null
else
curl -s -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-d '{"error":"nullclaw execution failed"}' "$TRACKER_BASE/runs/$RUN_ID/fail" >/dev/null
fi
doneRun it: ./worker.sh llm-executor worker-1. The full version, with progress events and model selection, lives in the repo's nullclaw.md.
Handoff between roles
Multi-role pipelines need no extra wiring. When a transition moves a task into a stage whose agent_role is review, the next review worker picks it up on its next poll. Handoff is just the FSM plus polling — asynchronous by construction.
Making writes safe to retry
Network hiccups happen mid-loop. Two mechanisms keep retries harmless:
- Idempotency: send an
Idempotency-Keyheader on writes; the server deduplicates, and reusing a key with a different body returns409. - Optimistic checks: pass
expected_stage/expected_task_versionon transitions to catch races.
Linking telemetry
If your runtime emits OpenTelemetry, point its OTLP exporter at POST /v1/traces (or the collector-compatible /otlp/v1/traces) and set the span attributes nulltickets.run_id and nulltickets.task_id. Traces then land next to the runs they describe.
Adoption path
- NullClaw alone — one-off tasks, one answer.
- NullClaw + NullTickets — a durable backlog and one sequential worker loop. The recommended starting point.
- Add NullBoiler — only when you need multi-agent scheduling, balancing and policy automation.