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:
emithook diagnose event evt_01JX9ABCDEF --jsondiagnose_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:
{
"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
| Subject | Ask 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:
emithook metrics get ep_acme --jsonget_metrics(key="ep_acme"){
"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:
# 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 --confirmReading raw logs is the fallback when those four do not answer it, not the starting point.
See also
- Connecting an agent — stdio and hosted Connect.
- MCP server — the full tool catalog.
- CLI —
emithook guideprints all of this in one page, from the terminal.