record_ungoverned()¶
An agent that cannot reach Coalex has two choices: stop, or proceed and say so. If your agent proceeds, call record_ungoverned() (TypeScript recordUngoverned()) beside that decision. Without it the action leaves no trace at all: evaluate() is a single request, so a failed call leaves no escalation and no audit row, and the compliance report omits the action instead of labelling it.
The record is written to a local spool first and delivered when the platform is back, because the reason you are calling it is that the platform was unreachable a moment ago.
Signature¶
| Parameter | Description |
|---|---|
request_id / requestId |
The conversation or run it happened in, so the row lands on the right session's trail. |
action |
What ran: the tool name, as the audit trail will show it. |
error |
Why governance was not consulted: the error evaluate() raised. Truncated to 500 characters. |
Both functions never raise and the returned promise never rejects. They are meant for an except / catch block, and a recorder that can break the thing it records is worse than none. Called before register(), they log a warning and record nothing: there is no endpoint to send to and no account to attribute the action to.
Example¶
import coalex
try:
decision = coalex.evaluate(request_id=rid, input=args, output=result, tool_name="wire_funds")
except Exception as exc:
# Coalex is unreachable: the business goes on, and the record says nobody was asked.
coalex.record_ungoverned(request_id=rid, action="wire_funds", error=str(exc))
decision = None
import { evaluate, recordUngoverned } from "@coalex-ai/sdk";
try {
await evaluate({ requestId: rid, input: args, output: result, toolName: "wire_funds" });
} catch (err) {
// Coalex is unreachable: the business goes on, and the record says nobody was asked.
await recordUngoverned({ requestId: rid, action: "wire_funds", error: String(err) });
}
In TypeScript, awaiting recordUngoverned() is optional, but awaiting is what guarantees the record is on disk before the process may exit.
How a record is delivered¶
- The SDK sends the record straight away. If it lands, it also drains any older backlog.
- If it does not land, the record is appended to the spool. It is marked
ungovernedin the audit trail, withoutcome: ran, and a record delivered after the fact says so (recorded_late). - After any successful
evaluate(), the SDK drains the spool, because success is the only honest signal that an outage has ended. In Python the drain runs on a background thread and never delays the call that found it; in TypeScript it is not awaited either. - A drain sends at most 50 records, one at a time, and stops at the first refusal. Only one drain runs per process at a time, so a record is sent once even when several
evaluate()calls succeed together. - Each record carries an id, sent as the
Idempotency-Keyheader and aspolicy_inputs.record_id, so one record is recognisable as one record in the audit trail. The platform writes one audit row per key: a record sent again, after a reply the SDK never saw or by two flushes at once, answers like the first and adds no row.
What the platform answers decides what happens to a record:
| Answer | Meaning | Record |
|---|---|---|
| 2xx | Delivered | Removed from the spool |
| 400, 404, 422 | The platform will never accept this record | Dropped, with a warning that names the action and request id, so one bad record cannot block the queue |
| 401, 403, 429, 5xx, unreachable | A problem with the credentials or the platform, not with the record | Kept |
The spool¶
| Location | The directory in COALEX_SPOOL_DIR, or coalex in the system temporary directory. The default does not survive a container restart; point the variable at a volume that does. |
| One file per identity | The file name carries a hash of the endpoint and API key (never the key itself), so two agents with different keys on one machine never deliver each other's records. Two processes with the same key share a file, and whichever is alive drains it. |
| Size | At most 1,000 records. Beyond that the oldest are dropped, with a warning. |
| After a crash | A drain works on a renamed copy. If its process dies, the copy is taken back by the next drain once its process is gone or it is ten minutes old, so a record is delivered at least once. |
Both SDKs
record_ungoverned is in the Python SDK from 1.27 and recordUngoverned in the TypeScript SDK from 1.18.