Navigate docs

Outcome Reporting

The audit trail records what an agent intended to do and what the policy decided. Outcome reporting records what actually happened. These are two separate concepts stored in two separate places.

An action can be executed by policy (the agent was authorized to proceed) but still result in a failure outcome (the downstream operation broke). Without outcome reporting, you can see that the agent was allowed to act but not whether that action succeeded. Outcome reporting closes the loop.

Reporting an Outcome

After an agent's action has been evaluated by the policy engine, the agent reports what happened using the SDK's report() method:

const al = new AgentLattice({ apiKey: process.env.AL_API_KEY! });

await al.report(auditEventId, {
  status: "success",
  message: "Migration applied cleanly",
  metadata: { rows_affected: 14200 },
});
import os
from agentlattice import AgentLattice, ReportOutcome

al = AgentLattice(api_key=os.environ["AL_API_KEY"])

await al.report(audit_event_id, ReportOutcome(
    status="success",
    message="Migration applied cleanly",
    metadata={"rows_affected": 14200},
))

The audit_event_id is the ID returned by al.execute() or al.gate() when the action was originally submitted. An agent can only report outcomes for its own audit events. Attempting to report on another agent's event returns a 403 error.

The response status code is 201 (Created), not 200. If you are calling the API directly (not through the SDK), make sure your status code checks account for this.

Outcome Statuses

Status Meaning Example
success The action completed fully as intended Database migration applied, all rows updated
failure The action did not complete. No useful work was done API call timed out, file write rejected by permissions
partial Some parts succeeded, others did not 12 files written but post-write notification failed

Choose partial when the action produced some useful result but did not complete fully. This is distinct from failure (nothing happened) and gives compliance reviewers a truthful record of what occurred.

Idempotency

Outcome reporting uses upsert semantics with last-write-wins. If you call report() multiple times for the same audit_event_id, each call overwrites the previous outcome. There is no conflict error.

This means:

  • Safe to retry on network errors. If you are unsure whether a report was received, call it again.
  • Overwrites are silent. If you accidentally report success and then failure for the same event, the final state is failure with no trace of the earlier success. Be careful with retry logic that changes the status between attempts.

The metadata field is an optional free-form JSON object. Use it for structured context that compliance reviewers or downstream systems might need: error codes, affected resource counts, timing information, or references to external systems.

Complete Lifecycle Example

This shows the full execute-work-report pattern:

const al = new AgentLattice({ apiKey: process.env.AL_API_KEY! });

// 1. Submit the action for policy evaluation
const { audit_event_id, status } = await al.execute("db.migrate", {
  metadata: { migration: "0043_add_users_index" },
  event_id: "deploy-2026-03-28-migration",
});

if (status === "denied") {
  console.error("Policy denied the migration");
  process.exit(1);
}

// 2. Do the actual work
try {
  await runMigration("0043_add_users_index");

  // 3a. Report success
  await al.report(audit_event_id, {
    status: "success",
    message: "Migration applied cleanly",
    metadata: { rows_affected: 14200, duration_ms: 3420 },
  });
} catch (err) {
  // 3b. Report failure
  await al.report(audit_event_id, {
    status: "failure",
    message: `Migration failed: ${(err as Error).message}`,
    metadata: { error_code: "MIGRATION_TIMEOUT" },
  });
  throw err;
}
import os
from agentlattice import AgentLattice, ActionOptions, ReportOutcome

al = AgentLattice(api_key=os.environ["AL_API_KEY"])

# 1. Submit the action for policy evaluation
result = await al.execute("db.migrate", ActionOptions(
    metadata={"migration": "0043_add_users_index"},
    event_id="deploy-2026-03-28-migration",
))

if result.status == "denied":
    raise SystemExit("Policy denied the migration")

# 2. Do the actual work
try:
    await run_migration("0043_add_users_index")

    # 3a. Report success
    await al.report(result.audit_event_id, ReportOutcome(
        status="success",
        message="Migration applied cleanly",
        metadata={"rows_affected": 14200, "duration_ms": 3420},
    ))
except Exception as err:
    # 3b. Report failure
    await al.report(result.audit_event_id, ReportOutcome(
        status="failure",
        message=f"Migration failed: {err}",
        metadata={"error_code": "MIGRATION_TIMEOUT"},
    ))
    raise

A partial outcome example:

const results = await deployToAllRegions(config);
const succeeded = results.filter(r => r.ok).length;
const failed = results.filter(r => !r.ok).length;

if (failed === 0) {
  await al.report(audit_event_id, { status: "success", message: `Deployed to ${succeeded} regions` });
} else if (succeeded === 0) {
  await al.report(audit_event_id, { status: "failure", message: `All ${failed} regions failed` });
} else {
  await al.report(audit_event_id, {
    status: "partial",
    message: `${succeeded}/${succeeded + failed} regions deployed`,
    metadata: { succeeded_regions: results.filter(r => r.ok).map(r => r.region) },
  });
}
results = await deploy_to_all_regions(config)
succeeded = [r for r in results if r.ok]
failed = [r for r in results if not r.ok]

if not failed:
    outcome = ReportOutcome(status="success", message=f"Deployed to {len(succeeded)} regions")
elif not succeeded:
    outcome = ReportOutcome(status="failure", message=f"All {len(failed)} regions failed")
else:
    outcome = ReportOutcome(
        status="partial",
        message=f"{len(succeeded)}/{len(results)} regions deployed",
        metadata={"succeeded_regions": [r.region for r in succeeded]},
    )

await al.report(audit_event_id, outcome)

Error Codes

HTTP Status Error Code Cause
201 Outcome reported successfully
400 INVALID_AUDIT_EVENT_ID audit_event_id is missing or not a valid UUID
400 INVALID_OUTCOME outcome.status is not success, failure, or partial
400 INVALID_BODY Request body is not parseable JSON
403 FORBIDDEN The audit event belongs to a different agent
404 AUDIT_EVENT_NOT_FOUND No audit event exists with this ID
503 REPORT_FAILED Database write failure (retryable)

Outcome data surfaces in compliance exports, giving reviewers a complete picture of what agents intended, what policy decided, and what actually happened.