Skip to content
← Back to home

§ DOCUMENTATION

Effect Confirmation

“The system said yes” is not “it happened”. Execlave records how each outcome is known, keeps later observations next to the original record instead of over it, and shows exactly how much of what your agents report as done rests on someone having seen it.

§ 01

Four outcomes, not two

An audit trail that only knows success and failure merges cases an investigator must keep apart. Execlave records them separately:

What happenedTrace status
A policy stopped the actionpolicy_blocked
The call never reached the target, or the target said noerror / timeout
The target said yes, and the effect was observedsuccess
The target said yes, and nobody has observed the effecteffect_unconfirmed

The fourth is the one a plain success/failure model hides: a 202, a queued job, a pending charge. Recording it as success lets the trail state something nobody knows.

§ 02

Saying how you know: effectEvidence

A trace may carry effectEvidence. Its basis is the field that matters: it says how the agent knows what it claims.

basisMeansCounts as confirmation
read_backThe agent queried the target and saw the effect.Yes
callbackThe target came back and reported it.Yes
acknowledgementThe target accepted the request. Not that it performed it.No
noneNo signal at all.No

Optional fields: acknowledgementId (up to 255 characters, the receipt the target gave you), effectRef (up to 512, the resource the action produced), confirmedAt (ISO 8601) and detail (up to 2000). Two rules always apply at ingest:

  • status: "effect_unconfirmed" needs a basis. none is a valid answer.
  • A basis of read_back or callback needs an effectRef or an acknowledgementId: a confirmation that names nothing is not evidence.
§ 03

Reporting it from the SDK, and resolving it later

Report what you know when the trace finishes. When you learn more, append a resolution with resolveTrace (or POST /api/v1/traces/:traceId/resolutions, which needs the developer role or above, from an API key or a signed-in user):
// JavaScript (@execlave/sdk 1.8.0+)const trace = exe.startTrace({ agentId: 'refund-agent', input });const job = await payments.refund(orderId, amount);   // returns 202 + a job id // The provider accepted the refund. Nobody has seen it happen yet.trace.finish({  status: 'effect_unconfirmed',  effectEvidence: { basis: 'acknowledgement', acknowledgementId: job.id },}); // Later, when the webhook arrives or a poller sees the refund:await exe.resolveTrace(trace.traceId, {  resolvedStatus: 'success',  basis: 'callback',  acknowledgementId: job.id,  effectRef: 'refund_8Hc2',  observedAt: event.created_at,}); # Python (execlave-sdk 1.9.0+)trace.finish(    status="effect_unconfirmed",    effect_evidence={"basis": "acknowledgement", "acknowledgement_id": job.id},)exe.resolve_trace(    trace.trace_id,    resolved_status="success",    basis="callback",    acknowledgement_id=job.id,    effect_ref="refund_8Hc2",)

A resolution carries resolvedStatus (success, error or timeout), a basis (read_back, callback, manual or reconciliation), and two times: observedAt, when the effect happened, and when Execlave recorded it. Each one writes an audit log entry.

The trace is never rewritten. Resolutions are append-only. The trace detail shows the recorded status unchanged, the resolution timeline, and a currentOutcome labelled as derived: the resolution with the latest observedAt wins, and if two resolutions disagree the outcome is marked contradicted instead of one silently winning.

§ 04

Outcome strictness: off, monitor, strict

Each agent has an outcome strictness that decides what ingest does with a success carrying no confirming evidence (no effectEvidence, or a basis other than read_back or callback). It mirrors the enforcement ladder you already use for policies.

ModeA success without confirming evidence
offAccepted.
monitorAccepted, and shown as “strict would reject N” on the agent page. The default for new agents.
strictRefused at ingest with OUTCOME_EVIDENCE_REQUIRED, naming effect_unconfirmed as the honest status.
  • New agents start in your organization's default, monitor unless an admin changed it. Agents that existed before the setting behave as off and move to monitor at their next update; an explicit off stays off.
  • Raising needs edit access to the agent; lowering needs the admin role. An agent's own registration cannot choose its strictness.
  • off and monitor measure the same. Coverage counts every agent's successes whatever its strictness, so setting an agent to off cannot remove findings from a signed report.

Set it on the agent page (Settings tab), or with PATCH /api/v1/agents/:id and outcomeStrictness. Under strict, a batch in which nothing was stored answers 400; in a batch where other traces were stored, the refused ones are listed in errors with the same code:

HTTP/1.1 400 Bad Request{  "error": {    "code": "OUTCOME_EVIDENCE_REQUIRED",    "message": "This agent requires confirmed outcomes (outcomeStrictness \"strict\"): status \"success\" needs effectEvidence with basis \"read_back\" or \"callback\". If the effect is not confirmed, report status \"effect_unconfirmed\"."  },  "accepted": 0,  "failed": 1,  "errors": [{ "traceId": "tr_9f2c", "code": "OUTCOME_EVIDENCE_REQUIRED", "error": "…" }]}

From SDK 1.9.0 a refused trace reaches your code instead of disappearing. flush() returns what was sent, refused and not delivered; with no handler the JavaScript SDK writes to console.error and the Python SDK logs at ERROR. A refused trace is never resent, and the SDK never falls back to reporting it as success.

// JavaScript (1.9.0+): a refused trace is never resent, so handle itconst exe = new Execlave({  apiKey: process.env.EXECLAVE_API_KEY!,  onTraceRejected: (rejections) => {    for (const r of rejections) alert(r.traceId, r.code, r.message);  },});const { sent, rejected, undelivered } = await exe.flush(); # Python (1.9.0+)exe = Execlave(api_key=os.environ["EXECLAVE_API_KEY"], on_trace_rejected=alert)
§ 05

The confirmation window and the unconfirmed queue

An unconfirmed action is given a window to be resolved: 24 hours by default, set per organization (effect_confirmation_window_hours via PATCH /api/v1/organizations/settings/org, admin) and overridable per agent from 1 to 8760 hours. A payment and an overnight batch need different windows. A trace with basis none has nothing to wait for and is aged as soon as it lands.

Aging is computed when you look, never stamped onto the trace. Once an action is past its window with no resolution, it appears in the unconfirmed queue (Traces → Unconfirmed), oldest first, with its evidence and the last read-back result. Every hour, aged items are escalated:

  • an action on which a policy in require_approval mode, or one with failure_mode: fail_closed, recorded a violation opens a high-severity incident;
  • any other aged action fires the trace.effect_unconfirmed_aged webhook with the trace id, agent, age, window and basis.
§ 06

Read-back checks: let Execlave ask

Instead of writing your own poller, you can name an endpoint Execlave asks about an agent's unconfirmed actions. A read-back check is one of your custom validators: the same URL, secret, HMAC signature, timeout and network guard, with a request of type read_back:

POST https://billing.internal.example.com/execlave/read-backX-Execlave-Signature: sha256=…        (HMAC-SHA256 of the body, your validator's secret)X-Execlave-Timestamp: 2026-10-04T14:05:00.000ZX-Execlave-Delivery-Id: 5d0c…X-Execlave-Validator-Id: 2b7e… {  "type": "read_back",  "deliveryId": "5d0c…",  "timestamp": "2026-10-04T14:05:00.000Z",  "organizationId": "…",  "agentId": "…",  "agentName": "refund-agent",  "traceId": "tr_9f2c",  "acknowledgementId": "job_42",  "effectRef": null,  "tracedAt": "2026-10-04T14:00:12.000Z",  "test": false}

Verify the signature as you do for validator calls, look the action up by acknowledgementId or effectRef, and answer with JSON:

{ "status": "success", "effectRef": "refund_8Hc2", "observedAt": "2026-10-04T14:01:30Z" }{ "status": "pending", "detail": "still in the provider's batch" }{ "status": "unknown" }
Answer, or what went wrongWhat Execlave records
success, error, timeoutA resolution with basis reconciliation and source system, which counts as confirmation. observedAt defaults to the time of the check; one more than 5 minutes in the future makes the answer invalid.
pending, unknownNothing about the action. The attempt is kept, shown in the queue, and the action is asked about again.
Timeout, network or HTTP error, wrong shape, inactive validatorNothing about the action, whatever the validator’s failMode. The attempt and its reason are kept.

A check that cannot answer never invents an outcome. That is the opposite of a validator's default, and for the same reason: a validator that cannot answer must not let an action through, and a read-back that cannot answer must not write history.

  • When. Only actions that are still inside their confirmation window and carry an acknowledgementId or effectRef. The first check runs within about 5 minutes, then the interval doubles from 5 minutes, up to a quarter of the window. When the window passes, checks stop and the item belongs to a person in the queue.
  • Who. An admin registers or removes the check; a developer with edit access to the agent can send a test request ("test": true, nothing recorded). Both on the agent page, Settings tab, or over the API:
# Register (ADMIN): one of your organization's custom validatorscurl -X PUT "https://api.execlave.com/api/v1/agents/$AGENT_ID/read-back-check" \  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \  -d '{ "validatorId": "2b7e…" }' # Test (DEVELOPER with edit access to the agent). Records nothing.curl -X POST "https://api.execlave.com/api/v1/agents/$AGENT_ID/read-back-check/test" \  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \  -d '{ "acknowledgementId": "job_42" }' # Removecurl -X PUT ".../read-back-check" -d '{ "validatorId": null }' …
§ 07

Coverage, and the signed compliance report

Coverage is computed over actions that ran: policy-blocked, limit-exceeded and flagged traces had no effect to confirm and are left out.

FigureMeaning
coveragePctShare of actions with a conclusive record: any status other than effect_unconfirmed, or an unconfirmed action later resolved by read_back, callback, or your read-back check.
unevidencedSuccessOf those, successes resting on no observed effect: what strict would refuse.
evidencedPctShare of actions confirmed AND evidenced. The number to move toward 100.
backlogUnconfirmed actions with no resolution at all yet.
medianMinutesToConfirmationAnd p95MinutesToConfirmation: time from the trace to its first confirming resolution. In GET /api/v1/agents/:id/confirmation-coverage; not in the signed report.

A manual resolution, or a reconciliation posted by an agent or a person, closes an item but is not counted as confirmation: nobody observed the effect. Every figure is null, never 0 or 100, when the period has no actions.

Compliance reports carry the same figures in confirmationCoverage, inside the signed body, for the report's own period and agent scope. A report that showed only successes and blocks would let a reader assume everything else landed.

"confirmationCoverage": {  "coveragePct": 91.4,  "confirmed": 1828,  "unevidencedSuccess": 212,  "evidencedPct": 80.8,  "unconfirmed": 172,  "total": 2000,  "backlog": 38,  "scope": "organization",  "windowFrom": "2026-09-01T00:00:00.000Z",  "windowTo": "2026-09-30T23:59:59.999Z"}
§ 08

What this does not cover

  • Evidence is attested, not verified. A read_back basis, a callback resolution or a read-back answer is your system's statement, authenticated by your API key or your validator's signature. Execlave does not contact the downstream system itself.
  • An agent that reports success with no evidence is counted in unevidencedSuccess, not detected as wrong. Moving the agent to strict is what stops it.
  • Read-back checks need an identifier. An unconfirmed trace with neither acknowledgementId nor effectRef is never asked about.
  • Older SDKs. Effect evidence needs @execlave/sdk 1.8.0 or execlave-sdk 1.9.0; handling refused traces needs 1.9.0 in both.
§ 09

Frequently asked questions

Why not just report success when the target accepted the request?
Because accepted is not done. A queue that took the job, an API that answered 202, a payment provider that returned a pending charge: each said yes and may still not do it. If that is recorded as success, the audit trail says the action happened when nobody knows that. Report effect_unconfirmed with basis acknowledgement and the acknowledgementId you were given, then resolve the trace when you learn the outcome.
Is basis "none" allowed?
Yes. Demanding evidence that does not exist would make the honest status expensive and the dishonest one free, and integrations would report success to avoid the friction. A trace with basis none is accepted, counts as unconfirmed from the moment it lands, and is visible in coverage. It is legal to send and impossible to hide.
Does a resolution change the original trace?
No. The trace keeps the status it was recorded with, which is what makes "what did we know at 14:02" answerable. Resolutions are appended to a separate, append-only ledger, and the trace detail shows a currentOutcome that is labelled as derived from them.
What is the difference between off and monitor?
Intent, not measurement. Both accept every trace, and coverage counts every agent whatever its setting, so choosing off cannot take findings out of a signed report. monitor means you are measuring and intend to move the agent to strict; off means the agent is deliberately not checked, for example because its effects cannot be observed.
My read-back check timed out. Is the action marked failed?
No. A check that errors, times out, returns an answer in the wrong shape, or answers pending or unknown records nothing about the action. The attempt is shown in the unconfirmed queue so you can see why the item is still open, and the platform asks again later. A check that cannot answer must never invent an outcome.
Effect Confirmation — Execlave Docs