The Ithura Blog
Deep dive6 min read

A queue with provenance, not a hosted agent

Ithura's Agent Dispatch Queue hands a task to an agent runner your team controls, on infrastructure your team controls. HMAC-signed both ways, audited on every hop, with a per-run token so a leaked runner secret cannot forge callbacks for other runs. Not a chatbot. Not autonomy-by-default. The shape a tracker should have.

Height 2.0 tried a hosted, autonomous, in-SaaS agent product and shut down in September 2025. The lesson was not that agents are useless. It was that a tracker is the wrong place to run the model, and autonomy-by-default is the wrong posture. What a team actually wants from its tracker is a queue with provenance: I hand a task off, the work happens on my infrastructure, and the trail lands somewhere I can audit next quarter.

Ithura's Agent Dispatch Queue is exactly that shape. Register a runner (a webhook URL to your box running Claude Code, Cursor CLI, a local model, whatever you like), dispatch a task with a runner id and acceptance criteria, and Ithura signs the payload and POSTs it out. Your runner does the work, signs a callback, and Ithura moves the run to completed with a result URL. Every hop lands in the audit log.

Three audited transitions for one dispatch
TimeEventActorDetail
10:04:12agent_dispatch.queuedalice@acme.comrun 8a2f, runner Claude Code, PAR-482
10:04:13agent_dispatch.in_progressrunneroutbound POST 202, sig verified
10:07:41agent_dispatch.completedrunnerresult_url github.com/acme/repo/pull/301

Why not a chat surface

The interesting agent products are agents talking to other agents. A tracker is not that surface. A tracker is where the work is defined, the reviewer signs off, and the deliverable lives. If the tracker also streams a chat, two things happen: the chat becomes the source of truth (bad), and every message multiplies the blast radius of a leaked bearer token (worse).

So dispatch is a one-shot handoff. The runner may post intermediate in_progress callbacks as often as it wants (they land in the audit log, not the UI), and posts one terminal callback with either completed and a result_url (the PR / MR link) or failed and an error_text. That is it. No back-and-forth.

How the signing works

Two directions, two signatures, one shared secret plus a per-run token so a leaked secret cannot forge callbacks for an unrelated run.

Outbound (Ithura, to your runner):

X-Ithura-Signature: hex(hmac_sha256(runner_secret, request_body))

# The runner verifies:
want = hex_hmac_sha256(os.environ["ITHURA_RUNNER_SECRET"], body)
assert hmac.compare_digest(want, request.headers["X-Ithura-Signature"])

Inbound (your runner, back to Ithura):

X-Ithura-Signature: hex(hmac_sha256(runner_secret + callback_token, callback_body))

# Ithura verifies:
want = hex_hmac_sha256(runner_secret + run.callback_token, body)
assert hmac.compare_digest(want, header)

The callback_token addition on the return trip is deliberate. A runner that gets compromised could otherwise forge callbacks against every past and future run for that runner. With the per-run token, a leaked secret still needs the token for the specific run to forge that run's terminal state, and the token lives in Ithura's DB and on the outbound POST only.

What the audit log gets

Every operation writes a structured audit entry:

  • workspace.agent_runner.created and workspace.agent_runner.deleted for lifecycle. Runners are soft-deleted; the audit trail for their runs stays intact.
  • workspace.agent_dispatch.queued, then .in_progress, then .completed or .failed. If the runner posts a callback for a run that has already reached a terminal state, .callback_ignored fires and the request body is preserved for investigation.

Enterprise workspaces stream the same trail as CSV via the audit CSV export endpoint. The SIEM ingest recipes on the audit export page cover Splunk, Elastic, and Datadog.

What is not in v1

  • UI dispatch action. v1 is API-first: the Settings tab registers runners and reads history; the dispatch call is a curl. A per-task "Dispatch to runner..." action is next.
  • Auto-dispatch rules, e.g. "every task labelled agent-eligible goes to runner X when it moves to Ready". The primitive is there (labels, states, hooks), the wiring is next.
  • Cost tracking. The callback can already carry any extra fields; a first-class tokens / seconds pair rolled up per runner and per workspace is the next reporting surface.
  • Retry in place. A failed run today requires a fresh dispatch to keep the audit trail linear. A one-click retry that opens a new run linked to the failed one is the ergonomics fix.

Where dispatch fits with the rest of Ithura

The outbound signing pattern is deliberately the same as workspace webhooks. A team that already has a webhook subscriber hardened against replay and signature failures can reuse the same verification code path for a runner. The audit-log surface is the same one that catches SAML SSO events, SCIM provisioning changes, and every write to the workspace's settings.

The tracker is still the tracker. The agent runs somewhere you control. Provenance is not a bolt-on.

Related