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. v0.7.0 adds the K9 permission system on top: enforce / advisory / off modes with declarative [[permissions.rules]] overlays + fail-CLOSED defaults across SSRF, governance-error, and federation signature paths.
v0.7.0 secure-default posture flips (operators upgrading from v0.6.x):
permissions.mode default advisory → enforce. Opt-out: AI_MEMORY_PERMISSIONS_MODE=advisory.AI_MEMORY_FED_REQUIRE_SIG=1 + AI_MEMORY_FED_REQUIRE_NONCE=1 default-on (federation Ed25519 X-Memory-Sig + anti-replay X-Memory-Nonce required). Set =0 during peer Ed25519 enrolment.AI_MEMORY_GOVERNANCE_FAIL_OPEN_ON_ERROR=0 (governance fail-CLOSED on rule-consultation error). #1054.AI_MEMORY_PASSPHRASE_FILE_ALLOW_LAX_PERMS=0 (passphrase file mode 0400 required). #1055.AI_MEMORY_SSRF_GUARD_ALLOW_DNS_FAIL=0 (SSRF guard fail-CLOSED on DNS NXDOMAIN). #1053.Operator-facing tools: ai-memory governance migrate-to-permissions [--apply] (translate legacy [governance] policies into the v0.7 [[permissions.rules]] shape). See MIGRATION_QUICKSTART.md for the tiered upgrade recipe with staged-rollout env-var opt-outs.
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 link validation only; KG edges are not gated by the write/promote/delete policy (the v0.7.0 hook pipeline can run link-write hooks on top).enforce / advisory / off + [[permissions.rules]] overlays, Bucket 3) SHIPPED — both extend, rather than replace, the namespace-policy gate documented on this page.