Skip to content
Anlyon
Esc
↑↓navigate↵open⌘Jpreview
On this page

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").data

A 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 gated
result = 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 succeeded is billed. Resolved failed releases the reserved usage and refunds the key’s budget reservation.
  • One resolution wins. An invocation that is no longer unknown returns 409, 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.
  • confirmed on 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.
  • acknowledged means the provider answered 2xx and nothing read the result back. A declared action with no verify rule grades acknowledged.
  • A manual succeeded grades acknowledged. It keeps verification: manual and 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 unknown until an operator resolves it.
  • A request is sent once. When the outcome cannot be confirmed, the invocation is recorded unknown and you reconcile before retrying.
  • A grade belongs to a governed action. For an action with no declaration and no adapter, succeeded records the destination’s 2xx.

Where to go next

Was this page helpful?