Three decisions: Allow, Deny, Pending. Three actions: store, delete, promote. Four levels: Any, Registered, Owner, Approve. Three approver types: Human, Agent, Consensus. Every governed write either returns immediately or queues for approval — never silently succeeds.
Every governed write goes through one function and produces one of three outcomes. The decision is not advisory — callers MAY execute on Allow, MUST reject on Deny, and SHOULD queue + return the pending_id on Pending.
Proceed with the action. Caller receives the new memory or success response. No row in pending_actions.
Reject with HTTP 403. The wrapped String surfaces the human-readable reason — not a placeholder. AI clients can quote it verbatim.
Queued in pending_actions; caller receives the new pending_id and HTTP 202. The action replays once approved.
For every governed action, the daemon walks this tree. The lookup is namespace-local — the policy lives in the namespace's standard memory at metadata.governance; absent a standard, the default policy applies.
Each policy field (write, promote, delete) holds one GovernanceLevel. Pick the field by action; the level determines the outcome shape.
| Level | store action | promote action | delete action | Used when |
|---|---|---|---|---|
| Any | Allow | Allow | Allow | Wide-open namespaces (no gate). Default for write and promote. |
| Registered | Allow if in _agents | Allow if in _agents | Allow if in _agents | Caller must have run POST /agents/register first. Otherwise Deny("agent not registered"). |
| Owner | Allow if caller == agent_id | Allow if caller == agent_id | Allow if caller == agent_id | Default for delete. Compares caller_agent_id to memory.metadata.agent_id. |
| Approve | Pending(id) | Pending(id) | Pending(id) | Routes to ApproverType: Human / Agent(id) / Consensus(N). |
These are the only operations that pass through the governance gate. Reads (memory_get, memory_list, memory_recall, memory_search) bypass governance — they go through the visibility/scope filter instead.
policy.write.policy.delete. Default Owner — unowned memories can be deleted by anyone unless the namespace is opted into stricter rules.policy.promote. Long-tier memories don't expire — promotion is the only edit that affects retention.When a level resolves to Approve, the approver shape determines what unblocks the pending action. ApproverType serializes externally-tagged so policy JSON stays unambiguous.
Out-of-band — UI button, email, Slack approve. Resolves via POST /pending/:id/approve with a human session.
A single specific registered agent must approve. Used for AI-supervisor patterns: junior agents queue, supervising agent approves.
N approval votes from any mix of human/agent registrations. Each vote appends to PendingAction.approvals; clears at threshold.
A namespace's standard memory carries the policy at metadata.governance. Absent a standard, the default applies. The default opens writes/promotes wide and gates deletes to owners with human-approver fallback for any Approve usage.
Defensive defaults (v0.6.2 S34): promote, delete, and approver carry #[serde(default)] so partial-policy payloads (a common shape for operator CLIs / test harnesses that only care about write) round-trip instead of 400-ing on missing fields. write remains required.
An Approve verdict isn't terminal — it parks the action. The original payload sits in pending_actions.payload until a decision arrives, then the daemon replays it as if the caller had just submitted it.
policy.write.pending_actions: action_type, namespace, payload (full action JSON), requested_by, status="pending".{"status":"pending","pending_id":"..."}. The HTTP request returns immediately — no blocking poll.POST /pending/:id/approve or /reject). Each consensus vote appends to approvals[]; threshold flips status.approved; decided_by, decided_at stamped.rejected. Payload retained for audit. Caller's original write does not happen.Without propagation, a decision on node-2 wouldn't reach node-1, and a caller on node-1 would still see pending after the action was already approved. Two additive sync_push fields close that gap:
sync_push.pending_decisions carries { id, approved, decider }. Peers apply via db::decide_pending_action. Already-decided rows: idempotent no-op.
sync_push.namespace_meta carries { namespace, standard_id, parent_namespace }. Without this, peers see the standard memory itself but miss the parent-link tuple, breaking inheritance walks.
Decisions also use quorum-fanout writes via broadcast_store_quorum at W of N — a single decision sticks across the federation with the same durability guarantee as a memory write.
Governance is a write-side gate. Reads, federation, and audit live elsewhere. Calling these out so AI clients don't assume the wrong contract.
memory_get, memory_list, memory_recall, memory_search) — those use the scope/as_agent visibility filter, not governance.memory_link) — passes validate_link only; KG edges are not gated. May change in v0.7 (Hook Pipeline can run KG-write hooks).