Receipts and grades
Every call Anlyon made on your agent's behalf, what the destination replied, which action version ran, and the grade that says how far the outcome was verified.
Because Anlyon makes the production call, it can say what happened to it. An invocation is that record: one intended operation, its outcome, and everything needed to attribute it.
A governed action adds an effect to the invocation, and every effect carries a grade. The grade says how far the outcome was verified. An action with no declaration and no adapter creates no effect and returns no grade. Its succeeded status records an HTTP result, not independent verification of the business outcome.
The seven grades
| Grade | Meaning |
|---|---|
confirmed |
The outcome is succeeded, and a read-back matched or an adapter read the provider’s own record of the operation. |
acknowledged |
The provider accepted the request with a 2xx. No read-back has matched. An operator’s manual succeeded also grades here. |
unknown |
No usable response. Anlyon does not send it again. It stays unknown until a read-back or an operator settles it. The allowance stays held. |
failed |
The provider rejected the request, or the request provably never left Anlyon. |
refused |
Anlyon refused at dispatch. Causes include an exhausted or unbounded limit, an expired preview or approval, a changed action version and a digest mismatch. |
denied |
The approval was denied. |
pending |
No receipt yet. The effect is waiting for approval, is being dispatched, or the provider accepted it and has not finished it. |
confirmed, acknowledged, unknown, failed, refused and denied are the six receipt grades. pending exists so the API returns a grade on every effect, including one that has not run.
How the grade relates to stage, outcome and verification
An effect has a stage (where it is in Anlyon’s pipeline), an outcome (what happened in the world) and a verification (who established the outcome). The grade is derived from them by one rule in the database. No code path writes it.
The rule is read top to bottom, and the earliest match wins:
| Order | Condition | Grade | verification |
|---|---|---|---|
| 1 | stage is rejected and the rejection reason is approval_denied |
denied |
not_dispatched |
| 2 | stage is rejected for any other reason |
refused |
not_dispatched |
| 3 | outcome is succeeded, established by the provider, and a read-back or the provider’s own record verified it |
confirmed |
provider |
| 4 | outcome is succeeded in any other case |
acknowledged |
provider or manual |
| 5 | outcome is failed |
failed |
provider, manual, or not_dispatched when the request never left Anlyon |
| 6 | outcome is unknown |
unknown |
unverified |
| 7 | Anything else: outcome is not_dispatched or pending |
pending |
not_dispatched or unverified |
A rejected effect always has outcome: not_dispatched. Nothing was sent.
Two rows deserve a second look. An acknowledged effect can show verification: provider. The provider’s 2xx established the outcome, and nothing read it back. And an approval that expired before anyone decided grades refused, not denied. Its rejection reason is approval_expired.
Reading the grade
The invoke response and a single invocation read carry the grade of the effect the invocation created. It is absent on list reads and when the invocation has no effect. List effects by grade to find the receipts that need attention.
const { data } = await anlyon.actions.invoke('send-email', { messageId: 'm-1', subject: 'Welcome', to: ['a@example.com'] });
console.log(data!.status, data!.grade);
const { data: unread } = await ops.actions.effects({ grade: 'acknowledged' });
const { data: open } = await ops.actions.effects({ grade: 'unknown' });result = anlyon.actions.invoke("send-email", {"messageId": "m-1", "subject": "Welcome", "to": ["a@example.com"]})
print(result.data["status"], result.data["grade"])
unread = ops.actions.effects(grade="acknowledged").data
open_effects = ops.actions.effects(grade="unknown").dataA declared action whose read-back matched:
{
"stage": "settled",
"outcome": "succeeded",
"grade": "confirmed",
"verification": "provider",
"adapter": { "type": "http.declared", "version": "1" }
}
The invocation’s status and the effect’s grade answer different questions. An effect refused on an exhausted limit has invocation status: failed and grade: refused. A lost response has status: unknown and grade: unknown. See Governed effects for evidence, reconciliation and resolution of an effect, and When the outcome is unknown.
The rest of this page describes the invocation record, which every action has.
Reading an invocation
const { data } = await anlyon.actions.invocation('inv_01JABCDEF');
console.log(data!.status); // succeeded | failed | unknown | ...
console.log(data!.responseStatus); // what the destination replied, or null
console.log(data!.durationMs);
console.log(data!.approvalId); // set when the call was gatedresult = anlyon.actions.invocation("inv_01JABCDEF")
print(result.data["status"])
print(result.data["responseStatus"])List them with actions.invocations(), or narrow to one action with actions.invocationsFor(name). The dashboard shows the same records under Actions → Invocations.
The statuses are not interchangeable
This is the part worth reading carefully, because two of these look similar and mean opposite things.
| Status | What it means | What to do |
|---|---|---|
pending_approval |
Parked. A human has not decided yet. | Wait, or poll the approval. |
running |
Dispatching now. | Wait. |
succeeded |
The destination replied with a 2xx. If the response body could not be read after the status arrived, the invocation is still succeeded and body says the body was unavailable. |
Read responseStatus and body. |
failed |
The call did not happen. It was refused before dispatch, failed before anything was sent, or the destination refused it with a 4xx (a 402, for example, is failed). |
Safe to retry once you have fixed the cause. |
unknown |
The request may have reached the destination and Anlyon cannot confirm the outcome. A timeout, a socket that died after the request was written, or a 5xx from the destination: a server error does not say the change was undone, so Anlyon treats a 5xx as indeterminate. responseStatus is kept when one arrived. |
Reconcile with the destination before doing anything else. Do not retry blind. |
denied |
A human declined. | Nothing ran. |
expired |
Nobody decided in time. | Nothing ran. |
failed and unknown exist as separate statuses because the safe response to each is different. Splitting them is the whole reason an operator reading failed can retry, and the reason an operator reading unknown should go and look at Stripe first.
An unknown invocation carries a message saying exactly that: the destination may have received this request, Anlyon cannot confirm the outcome, reconcile with the provider before retrying.
Resolving an unknown outcome
Once you have checked the destination, record what happened. Nothing is sent to the destination:
const { data: queue } = await operator.actions.invocations({ status: 'unknown' });
await operator.actions.resolveInvocation('inv_01JABCDEF', {
outcome: 'failed',
evidence: 'Stripe shows no charge for order 4417.',
externalReference: 'order_4417',
});queue = operator.actions.invocations(status="unknown")
operator.actions.resolve_invocation(
"inv_01JABCDEF",
outcome="failed",
evidence="Stripe shows no charge for order 4417.",
external_reference="order_4417",
)- Who can resolve. A workspace admin in the console, or a key holding
actions:resolve, which is never granted by default. The key that made the call can never resolve it, so an agent cannot declare its own uncertain call failed and try again. - Evidence is required and kept on the invocation with who resolved it and when. The invocation shows it as
resolution. - Billing follows the answer. Resolved
succeededis billed. Resolvedfailedreleases the reserved usage and refunds the key’s budget reservation. - One resolution wins. An invocation that is no longer
unknownreturns409, including when two operators resolve it at the same moment.
Retrying on purpose
Resolving does not free the original’s idempotency key. Running the action again is a new decision, made visible:
await operator.actions.invoke('refund-order', input, {
retryOf: 'inv_01JABCDEF',
idempotencyKey: 'refund:4417:retry-1',
});
A retry needs its own idempotency key and names the invocation it retries. It is allowed only when the original is failed (on its own or resolved as failed) and of the same action. An unknown original must be resolved first, and a succeeded one cannot be retried this way. Each invocation can be retried once: if the retry fails, retry the retry. The link shows on both invocations as retryOf and retriedBy.
What is recorded, and what is scrubbed
| Recorded | Not recorded |
|---|---|
| Action name and the action version that executed | Any resolved secret value |
| Response status, truncated response body, duration | The interpolated URL or headers |
| The approval id and the decision with its policy name and version, when gated. Who decided is on the approval | |
| The run and span the invocation belongs to |
Log lines carry the action’s template, never the interpolated request. Anything the upstream sends back is scrubbed for secret values before it is persisted or logged, and the response body is truncated, so a chatty API that echoes your request cannot smuggle a credential into your own audit trail.
Attribution
Pass a runId and the invocation joins that run’s trace alongside the model calls and approval waits that led to it. That is what lets you answer the question that actually gets asked after an incident: which agent issued this refund, on which version of the action, and who approved it.
Runs and spans are OpenTelemetry-compatible and exportable, so the record outlives any one vendor, including this one. They are never metered on any plan.
See Analytics for aggregate reporting, and Approvals and policies for the decision half of the record.
How it behaves
- A receipt is a database record with provider evidence.
confirmedon a declared action means your own verify rule matched. A resource that already existed can match, so write a rule that distinguishes this write.- Confirmed is Resend’s record of the send, not inbox delivery.
acknowledgedmeans the provider answered2xxand nothing read the result back. A declared action with noverifyrule gradesacknowledged.- A manual
succeededgradesacknowledged. It keepsverification: manualand is never shown as provider verification. - A lost response that lookup cannot find stays
unknown. The effect keeps its allowance until evidence or an operator settles it. - A verify URL that reads the provider response needs that response. After a lost response that effect stays
unknownuntil an operator resolves it. - A request is sent once. When the outcome cannot be confirmed, the invocation is recorded
unknownand you reconcile before retrying. - A grade belongs to a governed action. For an action with no declaration and no adapter,
succeededrecords the destination’s2xx.

