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
successand thenfailurefor the same event, the final state isfailurewith no trace of the earliersuccess. 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.