Suppose an agent inspects the code of your daily data-quality job. It sees the branch that writes a successful result. In a new session tomorrow you ask, "Did yesterday's batch check finish?" That branch says what the job should do, not whether yesterday's run ever reached it.
You could export the logs each time. Or the job could report events to a store the agent can query within its permissions. Those records persist outside the chat and can be checked again next week. This article uses an invented job; none of the events or conversations below are a real incident.
The point is to retain what the runner reported, not what the agent thought would happen yesterday. A chat summary could have been written before the job even started. Even a fresh summary cannot prove that a particular batch completed. A durable event trail gives the next session something original to check.
Give reporting and querying separate credentials
In a schema-first event system, the paths look like this:
Controlled job runner --write token--> Schema validation --> Queue acknowledgment --> Async storage
|
Agent --read token--> Schema discovery --> Bounded record query <---------------+
|
+--> Analyze retrieved evidence --> Person
The runner submits events with a write token. The service validates schema, ownership, and permissions before asynchronous storage. The agent discovers readable fields and queries details with a separate read token. Server-side credentials determine tenant and project scope; the event body cannot choose them.
The two paths fail differently. If a connection drops after sending, the writer should reconcile whether the event was accepted before retrying. A query-tool error means "I cannot confirm the result," not "the job never ran."
They need different health checks, too. On the write side, look for acknowledgments and sends whose outcome is still unknown. On the read side, check whether an acknowledged event becomes visible after an acceptable delay. Both paths can be healthy while the job itself fails. Conversely, a failed job does not mean the event pipeline is broken. Separating those conditions keeps an incident investigation from starting in the wrong system.
An existing platform can provide the store if it offers suitably scoped queries. A runbook tells the agent how the job is meant to behave; the event tells it what the runner reported. Both are useful during an investigation.
Record a start and an end
Call the job daily-quality-check. Its target_id finds runs over time; a fresh trace_id for each execution pairs the start and end. At 16:20 Singapore time on November 5, 2026, a controlled backend runner submits this invented run-started event:
{
"schema_id": "default_behavior_events",
"schema_version": 1,
"data": {
"report_source": "be",
"client_time": "2026-11-05T16:20:00+08:00",
"scene_type": "agent_workflow",
"action_type": "run_started",
"target_type": "workflow",
"target_id": "daily-quality-check",
"trace_id": "run-demo-20261105-01",
"extra_params": { "batch_id": "batch-demo-42" }
}
}
target_id and trace_id are exact-match filters. The batch_id in extra_params returns with the record, but you cannot filter directly on that nested key. If finding runs by batch becomes a routine question, the platform team must define, activate, and authorize a schema with a queryable batch field. Adding a JSON key alone does not create a new query capability. The server generates event_id and server_time.
Nor does one trace_id guarantee exactly one start event. If the runner sends run_started and loses the response, a retry may create a second event with the same trace_id and a different server-generated event_id. Treat them as possible duplicate reports, not automatically as two runs. Conversely, two different trace_id values for the same job name should be investigated as separate executions before anyone merges their outcomes.
Agree on what run_completed means. Did the process reach its last line, or did its output pass a quality check? If both matter, report separate outcomes or keep independent acceptance evidence. Otherwise the agent may mistake a technically completed run for a delivered result.
When the run ends, the runner reports run_completed or run_failed with the same trace_id. Until then, the answer has a gap; even an end event only records the runner's stated outcome.
For a long-running job, you might also define progress events or a separate timeout alarm. That helps distinguish "still working" from "runner disappeared." Without such evidence, those two states look identical when the end event is absent. The agent cannot invent a heartbeat. A business acceptance check still needs its own result rather than borrowing the runner's completion status.
What happens after 202
HTTP 202 means the message queue acknowledged the event. Keep the returned event_id and server_time so you can check it after asynchronous loading. An uncertain send followed by a blind retry can produce duplicate records.
Do not conflate the clocks. client_time is when the runner says the event occurred; server_time is when the platform received it and is the time used to filter this query. A clock error or offline reporting can separate them by hours. To answer "what happened yesterday," first locate receipts by server time, then inspect event times; expand the window for late arrivals and watch the result count.
Suppose a job ends at 23:59 Singapore time but the runner is offline until 00:05 the next day. A query for "events received yesterday" will miss that end event even though the job finished yesterday. The end of a query window is exclusive: an event received exactly at 00:00 belongs to the next window. Showing both the occurrence and receipt times in the analysis makes this discrepancy visible.
An empty query immediately after 202 calls for an ingestion check. Finding the start event still says nothing about completion. Build the query window around server time.
What to say when the end event is missing
To compare the November 4 and 5 runs the next day, the agent calls list_schemas -> describe_schema -> query_schema. The server-time window from November 4 at 00:00 through November 6 at 00:00 Singapore time (exclusive) is November 3 at 16:00 through November 5 at 16:00 UTC. The query arguments are:
{
"schema_id": "default_behavior_events",
"schema_version": 1,
"start_time": "2026-11-03T16:00:00Z",
"end_time": "2026-11-05T16:00:00Z",
"filters": { "target_id": "daily-quality-check" },
"limit": 20
}
This finds events received during those two local dates. An event that happened on November 5 but was reported on November 6 needs a later server-time query.
Filtering by target_id=daily-quality-check finds candidate runs on both days. Once those records are back, pair starts and ends by trace_id. For a busy job, a second exact-match query on one trace_id can prevent unrelated runs from filling limit=20. Knowing a run ID does not relax project authorization or the server-time window.
Suppose both days' events have loaded and the query is not truncated. November 4 has a run_started and a matching run_completed. November 5 only has the run_started shown above. The agent should give the returned event ID and time, then say: "Within this authorized query, I have not found an end event with the same trace_id."
Perhaps the job is still running; perhaps it finished but did not report. Neither possibility justifies calling it failed. If the query hits limit=20, narrow the time range or raise the total within the policy. If next_cursor appears, continue with the same filters.
To investigate the gap, check whether the writer kept a receipt for the end event. If it did, wait for loading and query around that receipt's server_time; if still absent, check the read token's project, schema authorization, and filters. If there was no receipt, return to the runner logs to see whether it ever reached the reporting step.
Also distinguish a successful query with zero records from a query-tool error. An expired credential or an unavailable service tells us nothing about job status. A zero-result response only speaks for the time range, schema, and filters actually used. Include that scope and any returned event IDs in the agent's answer so another engineer can reproduce it.
How far detail queries can go
New events must match an active, authorized schema; the initial template is for behavior events. Ordinary users cannot create arbitrary tables, and the query tool does not execute free-form SQL. The current production defaults allow at most 31 days per window and 100 rows per page. Longer histories need multiple authorized windows; more records may require cursor pagination. Nested keys in extra_params cannot be filtered directly, and this is not a task-checkpoint system.
Report only necessary fields, leaving out secrets and unnecessary personal data. The agent reads the details it needs into its context even with a scoped credential; decide what it should be allowed to see.
If the business question needs batch-level quality details, consider whether check names, counts, and error categories will suffice, with authorized staff inspecting raw samples in their original system. Putting unreviewed customer rows into extra_params does not make them directly filterable; it does expose more data to the next agent query. Field design and access policy belong in the same review.
Test this with a dedicated job: can you retrieve start and end with the same trace_id? How long between 202 and query visibility? If you delay the end event, will the agent say "not yet confirmed"? Keep the test data private and leave customer details out of public examples.
Sometimes the most useful answer is: "Yesterday's run reported a start, but I cannot find its end status yet." It is incomplete, and more honest than inventing a completion from the code path.
Project website: https://d2ggep2t73r4ny.cloudfront.net/
Technical and business discussion: lorry@antlertec.com
Top comments (0)