Skip to content

Write evidence back

An agent that reads but never writes leaves the corpus exactly as good as the day you seeded it. Writing back is what turns a retrieval system into one that learns.

Conclusions, not transcripts. A corpus of logs is a corpus of noise, and noise costs you twice — once in retrieval quality, once in the tokens spent carrying it.

WriteDo not write
“Reconcile job needs fixtures regenerated when billing/reconcile.py changes”the CI log
“Rolled back 2.4.0 — connection pool exhaustion under load”the deploy transcript
“Superseded: the legacy queue is no longer used”the whole design thread

One sentence a future agent can act on, scoped to where it is true.

vd.client.call_tool("evidence.ingest", evidence_args(
agent="release-bot",
items=[{
"source_type": "incident",
"summary": "2.4.0 rolled back — connection pool exhaustion at 3x normal load",
"scope_key": "service:checkout",
}],
))
_, err := client.Ingest(ctx, "release-bot", []wire.EvidenceItem{{
SourceType: "incident",
Summary: "2.4.0 rolled back — connection pool exhaustion at 3x normal load",
ScopeKey: "service:checkout",
}}, wire.IngestOptions{})

The set you may send is SourceTypePublic: run, pr, ci, deploy, incident, doc, review, correction.

Evidence is never edited or deleted. Correct by writing a correction that supersedes the earlier claim — both survive, and an audit of a run from before the correction still shows what was true then. That is what makes the audit trail worth having.

Identity is derived from content, so replaying identical bytes does not create a second record. A retry after a response that was actually delivered is therefore safe.

The trap: reusing an idempotency key is necessary and not sufficient. The server derives dedupe identity from the key together with the content, so an attempt that re-stamps any field — event_ts above all — is different content and appends a second record even though the key matched.

Serialize the body once per logical call and replay those exact bytes. Nothing stamped per attempt: not event_ts, not a timestamp inside metadata, not a regenerated key.

The SDKs do this for you. If you build the call yourself, do not rebuild the payload inside the retry loop — a fresh timestamp per attempt is the subtle version of this bug, and the hash chain makes the duplicate permanent.

A caller-supplied idempotency_key is preserved, never overwritten: if you are deduping against your own system you have already chosen the identity.

A batch can be partly accepted. You get 200 with accepted[] and rejected[] — a successful call with a mixed result.

Do not retry the whole batch. Replaying it re-sends records the server already committed, and your intent was “retry what failed”, not “send everything again”. Resubmit the rejected subset as a new logical call with its own frozen payload. The SDKs deliberately do not retry a partial batch.

A known gap, stated rather than hidden: a rejected record carries index, code and message but no retryable flag, so you cannot distinguish a permanently invalid record from one that hit a transient dependency without interpreting the code yourself. That is a deficiency in our contract, not something you are missing.

Both paths — acted and held:

try:
result = do_the_thing()
outcome = f"Applied: {result.summary}"
except Exception as e:
outcome = f"Failed: {e}"
vd.client.call_tool("evidence.ingest", evidence_args(
agent="release-bot",
items=[{"source_type": "run", "summary": outcome, "scope_key": scope}],
))

Failures are the valuable ones. A corpus that only records successes cannot warn anyone about anything.