Skip to content

Agent workflows ​

Two things an agent does over and over: work out why one thing failed, and work out whether it is one thing or everything. Emithook has a purpose-built answer for each, and reaching for the right one first saves an agent from reading logs it does not need.

Diagnose: why did this fail, and what now ​

Do not start by listing events and reading response bodies. Ask the question directly:

bash
emithook diagnose event evt_01JX9ABCDEF --json
text
diagnose_event(id="evt_01JX9ABCDEF")

You get back a verdict, the evidence it was drawn from, and next actions in both vocabularies — the CLI command and the MCP call — so whichever door you came in by, the answer ends the loop instead of starting a thread:

jsonc
{
  "subject": { "kind": "event", "id": "evt_01JX9ABCDEF" },
  "verdict": "event evt_01JX9ABCDEF — The destination accepted the connection but did not answer in time.",
  "diagnosis": {
    "class": "destination_timeout",
    "confidence": "certain",
    "why": "the engine recorded this failure class when the attempt failed",
    "retryable": true,
    "owner": "destination",
    "nextAction": {
      "mcp": "replay_event { id }",
      "cli": "emithook events replay <id>",
      "prose": "Slow handlers time out under load. If this repeats, the destination needs to acknowledge fast and do its work asynchronously."
    }
  },
  "evidence": [
    { "source": "event", "fact": "status failed, type invoice.created", "at": "2026-08-20T09:14:02Z" },
    { "source": "attempt 3", "fact": "no HTTP response · 30000ms", "at": "2026-08-20T09:14:32Z" },
    { "source": "recent events on ep_acme", "fact": "4 of 49 recent events also failed" }
  ]
}

Three parts of that response are worth understanding.

owner says whose problem it is. destination means the fix is at the other end; you means the payload, path or credentials are wrong; emithook means it is ours. An agent that reports "the destination is timing out" is more useful than one that reports "delivery failed".

retryable is about the unchanged call. A 4xx is not retryable because replaying it produces the same 4xx. A 5xx is, because the destination may simply have been unwell.

The last evidence line answers "is this one thing or everything". A single failure among 49 healthy siblings is a different situation from 49 out of 49, and the response tells you which without a second query.

Pick the subject that matches the question ​

SubjectAsk it when
diagnose event <id>A delivery failed and you want to know why.
diagnose request <id>Something was sent to you and never became an event at all.
diagnose endpoint <id>A route has gone quiet and you do not have one failure to point at.
diagnose destination <id>One target is failing for everything, not one event.
diagnose domain <name>DNS or verification is not completing.
diagnose email <id>An outbound message did not arrive.

The distinction between the first two matters more than it looks. An event exists only once a request was accepted and routed; if a provider says it delivered a webhook and you cannot find the event, the request is where it went — including the requests the front door dropped.

Diagnostics never mutate ​

A diagnosis may recommend replay_event, but the agent has to make that call itself, through the confirm gate. A diagnostic that silently replayed would be a write tool wearing a disguise.

Report: is this one thing, or everything ​

Rollups are pre-aggregated hourly per scope, so a report is a read rather than a scan:

bash
emithook metrics get ep_acme --json
text
get_metrics(key="ep_acme")
jsonc
{
  "data": [
    {
      "key": "ep_acme",
      "window": "2026-08-20T09:00:00.000Z",
      "volume": 1284,
      "success_rate": 0.981,
      "p50": 84,
      "p95": 240,
      "p99": 512,
      "error_mix": { "destination_timeout": 19, "destination_server_error": 5 }
    }
  ],
  "next_cursor": null
}

error_mix is keyed by failure class, not by status code — and it is the same class diagnose_* returns, because the engine recorded it at the moment the failure happened rather than inferring it later. A breakdown and a diagnosis cannot disagree about one event. That is what makes this a usable first call: read the mix, then diagnose one event from the dominant class rather than picking one at random.

A null p99 means we do not have one for that window. It is never a relabelled p95. All three percentiles are computed exactly over the window's real durations.

Rollups outlive raw events — deliberately, so a trend can span a period the event log no longer covers. A window longer than retention is refused or clamped and says which, so a report never quietly covers a shorter period than it claims.

Putting them together ​

The shape of a competent investigation, in four calls:

bash
# 1. Is it one endpoint or all of them?
emithook metrics get ep_acme --json

# 2. The mix says destination_timeout. Find one.
emithook events list --endpoint ep_acme --status failed --limit 1 --json

# 3. Confirm the cause and get the remedy.
emithook diagnose event evt_01JX9ABCDEF --json

# 4. Act on it — deliberately, not automatically.
emithook dlq redrive --endpoint ep_acme --confirm

Reading raw logs is the fallback when those four do not answer it, not the starting point.

See also ​

Emithook · a Finnoto product