Provenance

Canary policies

A canary family is a handful of neurons, on a specific model, whose activations were calibrated against an evaluation set for one behavior — prompt_injection, credential_exfiltration. With Trace you can make an agent's authorization depend on what those neurons read while its model processed the request: an injected instruction the agent's chain-of-thought never mentions still shows up in the measurement.

Write the rule

{
  "rules": [
    { "id": "block-prompt-injection",
      "when": [{ "fact": "canary", "set": "prompt_injection", "op": "gte", "value": 1 }],
      "require": { "deny": true } },
    { "id": "review-credential-requests",
      "when": [{ "fact": "canary", "set": "credential_exfiltration", "op": "gte", "value": 0.8, "best_effort": true }],
      "require": { "approval": true } }
  ]
}

Give verify a reading

The agent presents the trace of the inference that produced the action:

# 1. the agent's model call, traced
curl $API/v1/trace/chat/completions … → { "provenance": { "trace_id": "trc_…", "canary_readings": {…} } }

# 2. bind it at authorize (the token carries trace: {id, canary_hash}) …
curl $API/v1/intent/authorize -d '{ "action_description": "…", "agent_session_id": "…", "device_id": "…", "trace_id": "trc_…" }'

# 3. … or present it (or a fresher one) at verify
curl $API/v1/intent/verify -d '{ "intent_token": "…", "action_description": "…", "trace_id": "trc_…" }'

Source order at verify: the trace_id you pass to verify (else the token's), as a traced inference no older than TRACE_READING_MAX_AGE_S (15 minutes by default) → a sidecar reading for that id → inference_input: {model, messages}, re-read now (metered trace_canary_read). The response then carries canary_readings, canary_source and trace_id; the decision ledger and the signed receipt carry trace_id + canary_hash.

Fail-closed

The policy engine treats a missing fact as "doesn't match", which would silently pass a canary rule. So unavailability is decided before evaluation:

| Situation | Outcome | | --- | --- | | No trace, stale trace, another account's trace, worker 502/503, unrecorded sandbox prompt | canary_unavailable (canary_set names the family) | | …but every condition on that family is best_effort: true | the condition evaluates false; canary_source: "unavailable" | | Same trace_id as the token, different canary_hash | canary_reading_mismatch |

A trace.canary_tripped webhook fires alongside intent.denied on every canary_tripped.

To stop the model itself on a trip, use the kill switch. It withholds the completion and can lock the agent session until someone reviews it. Canary policies still apply, and a locked session is denied session_canary_locked at verify.

Limitations

SDK

const r = await prov.trace.chat.completions.create({ model, messages });
const auth = await prov.intent.authorize({ actionDescription, agentSessionId, deviceId, traceId: r.provenance.traceId });
const v = await prov.intent.verify({ intentToken: auth.intentToken, actionDescription }); // → canary_tripped?