0191 — A unit of work is not a request, and must not have to pretend
Status: Accepted Date: 2026-09-22
Context
RequestContext required method and path. Work that never arrived over a
transport therefore had to invent them, and a consuming agent loop wrote
method: 'AGENT' — which is not a verb, it is the absence of one recorded in
the field for verbs. The scheduled work beside it did not invent anything and
ran with no context at all, which made setRequestDimensions a silent no-op and
left its mutations out of the audit entirely.
Decision
method, path and statusCode are optional; kind (request | job) and
name say what the row describes; runUnitOfWork runs such work inside one
context and writes the completion record with the same hand a request’s is
written with.
statusCode is the part that was nearly missed, and the reason this is an
ADR. The obvious change is dropping the fabricated verb. Doing only that, and
still requiring an HTTP status, would have put a second invented transport field
in the same row for the same reason — one lie removed and one left, which reads
as a fix. A job’s outcome is ok, plus errorCode when it failed.
kind is written on every row, requests and tool calls included. A filter
that has to infer “no method means a job” is carrying exactly the implicit
knowledge this field exists to remove.
Consequences
auditChanges keeps a row it cannot examine. A job has no verb, so whether it
changed anything is unanswerable from the row, and an audit filter that silently
drops what it did not look at reports zero — indistinguishable from a system in
which nothing happened.
What was deliberately not done. runInMcpRequestContext still writes
method: 'MCP' and path: '/mcp/<tool>'. That is not the same lie: an MCP tool
call arrives as a request, over a transport, naming an operation. The decision
is recorded in that file as well, because six months from now it is otherwise
indistinguishable from an oversight.
A wrapper around the request context with the same fields — “like a request, but
for background work” — was refused. It does not remove the invention, it moves
it inside the library, and method: 'AGENT' keeps existing with a better
address.
This is the first breaking change on stitchkit/observability since 0.83.0. The
migration names one audience: whoever writes these fields into NOT NULL columns.