Skip to content
kazma.
ع Star 7 Get Started

Commitment Layer (resolve-before-act)

A policy gate between the LLM and durable mutations. Before the agent is allowed to schedule, send, execute, or change config, Kazma resolves its intent against memory and policy. Ambiguous acts are disambiguated with an interactive semantic clarify/confirm card on every platform. All phases (0–8) are shipped. The layer kill-switch and import-unavailable degradation are fail-open (treated as layer-off). Authorization-engine exceptions on semantic tools fail CLOSED at both chokes — a broken policy engine must not free-fire remind/exec. Invariants: AGENTS.md §20.

Related: Security & Safety (three HITL execution paths + registry), Memory & RAG (the belief store the gate reads), Slash commands.


1. Why it exists — the incident class it blocks

Section titled “1. Why it exists — the incident class it blocks”

Consider a scheduling agent. The user says “remind me about the meeting.” Without a commitment layer, an LLM can invent a plausible date, schedule it, and — worse — overwrite the user’s real belief about when the meeting is. This is a real class of failure (the “CoPilot incident”): the model acts confidently on a hallucinated fact and the system lets it persist.

Kazma resolves intent against memory before acting, and blocks the bad path at two layers:

  1. The schedule layer — a relative phrase (“next Tuesday”, “in two days”) is anchored to a real memory event, and the tool’s fire_at is rewritten to the memory-correct value (the gate’s value wins over the model’s).
  2. The memory layer — a user_explicit belief may not be superseded by a lower-trust (llm_inferred / system_tool) source (the source-trust gate).

Removing either invariant reintroduces the incident class.


Every mutating path goes through authorize_effect (in kazma_core/safety/commitment/authorize.py). There are three call sites:

Choke pointPathRole
tool_worker_node (agent/graph_tool_worker.py, _commitment_resolve_gate)Single-agent chatFull decisions; runs before the security HITL split so it can rewrite args first
LocalToolRegistry.execute (agent/tool_registry.py)IDE / swarmExec denylist / outbound allowlist / config protected keys do enforce here. Remind/cancel_job need graph context (audit-only on this path). H-8: apply rewritten_args; clarify/confirm fail closed (“run from chat”) — never mint a second HITL gate
_mutate_functional (memory/belief_mutation.py)Memory writesThe corruption half — source-trust gate (independent of the policy gate)

authorize_effect returns an EffectDecision with one of four outcomes:

DecisionMeaningWhat happens
allowExecuteOptionally with rewritten_args (e.g. the gate computed the correct fire_at for a remind)
clarifyInterrupt with a targeted questionA real interrupt card fires with discrete options (see §4)
confirmInterrupt for explicit OKSame card UX, used for critical acts
denyBlockedClear error returned to the model; no card, no execution

Plus an audit-only path for read tools, mutate_fs (containment is in IdeService.resolve), and delegate (HMAC trust checked at skill-load time).

ActResolver behavior
remind (schedule)Relative time anchored to a memory event → allow + rewrite fire_at. Ambiguous relative phrase with a nearby event → clarify. Unsatisfiable → deny.
cancel_jobResolves against pending cron jobs.
exec (shell)A denylist blocks catastrophic commands (rm -rf /, fork bombs, curl | sh, dd of=/dev/, mkfs, shutdown, chmod 777 /) before the HITL card. Safe commands pass through (HITL still applies).
send_outboundWhen agent.commitment.outbound_allowed_targets is configured, unknown targets → clarify with the allowlist.
config_changeProtected keys (safety.*, agent.commitment.*, notifications.lifecycle.*) cannot be mutated by the agent — self-protection.
proposal-backed postx_post / x_schedule_post / book_x_post refuse without a resolvable proposal_id. The gate rewrites text to the stored artifact (id wins over model context). Broken artifact store denies fail-closed.

Rewrite-on-allow: the gate’s computed fire_at (for remind) always wins over the model’s original args. The original, possibly-wrong timing never reaches the scheduler.


When a decision is clarify or confirm, the graph suspends via LangGraph interrupt() with a payload that carries a question and a list of options — each option bundles a slots_patch (a partial argument update).

flowchart LR
U[User: "remind me about the meeting"] --> M[Model calls schedule tool with ambiguous time]
M --> G[Commitment gate: authorize_effect]
G -->|ambiguous| C[Clarify card: "Which meeting?"]
C --> O1[Option: Project sync — Tue 2pm]
C --> O2[Option: 1:1 — Wed 10am]
C --> OX[Cancel]
O1 --> R[Resume: apply slots_patch → correct fire_at]
OX --> X[Aborted cleanly]
  • Renders everywhere — Web (chat + sidebar), Telegram, Discord, and Slack. Each platform renders one button per option.
  • Resume applies the chosen option’s slots_patch to the tool arguments and continues. The existing Approve/Deny buttons map to best-option / cancel.
  • Empty options list → a free-text clarify (no discrete choices).
  • Cancel is terminal — the model is told to stop the attempt and ask the user, never to silently retry.

This is the unified HITL bus (the same one that handles danger-tool approvals) — there is no second interrupt channel.


Dispatched swarm workers can be capped to a privilege scope. When agent.commitment.swarm_scope_enforce is on, workers default to semantic_tier HIGH (exec/outbound/config/identity CRITICAL denied) and denied_acts = {soul_delta, identity, config_change}. Default is ON since 2026-08-15 (intent-engine auto-dispatch) — opt out via env / ConfigStore.

See kazma_core/safety/commitment/scope.py (ScopeToken, default_worker_scope, is_act_within_scope).


The self-improvement engine persists “Soul deltas” — LLM-generated system-prompt refinements derived from untrusted conversation/tool output. When agent.commitment.soul_requires_confirm is on, deltas are held until an operator confirms them via the queue at GET /api/commitment/soul/pending and POST /api/commitment/soul/{cid}/confirm (or /reject). Config default is off; auto-ON in production / multi-user unless the operator set the key explicitly (get_commitment_config).

Storage honesty: Soul deltas live in get_config_store() under key self_improvement.agent_evolution ({"agents": {<id>: {soul, history}}}) — not a markdown SOUL.md and not a live agent_evolution.json. _load_agent_evolution / _save_agent_evolution go through ConfigStore — do NOT reintroduce a direct path.write_text write. A compound read-modify-write lock (_agent_evo_lock) serializes apply_agent_mutation within a process; ConfigStore’s own lock only guards individual get/set. Both are required.

A one-time migration (_migrate_legacy_evolution_if_present) moves any pre-existing agent_evolution.json into ConfigStore and renames it .migrated. Leave this in place.

Deltas are checked at creation time (_analyze_success/_analyze_failure) AND at apply time (_auto_apply/apply_agent_mutation) — defense-in-depth. Never inject a delta via the old "Apply these refinements to your behaviour:" framing; always use format_untrusted_block(evo, source="self_improvement").

Kill-switch KAZMA_SELF_IMPROVEMENT=0 is checked live (not just at init) on both the chat/supervisor path and the swarm worker path.


Modes (agent.commitment.mode or KAZMA_COMMITMENT_MODE)

Section titled “Modes (agent.commitment.mode or KAZMA_COMMITMENT_MODE)”
ModeBehavior
strictMost conservative — broadest clarify/confirm surface
balanced (default)Resolve clear intent, clarify only genuine ambiguity
autonomousMinimal interruption
yoloEffectively passthrough (danger tools still HITL-gated separately)

Kill-switches (all default OFF unless noted — SWARM_SCOPE_ENFORCE defaults ON since 2026-08-15)

Section titled “Kill-switches (all default OFF unless noted — SWARM_SCOPE_ENFORCE defaults ON since 2026-08-15)”
Env varLayerDefaultEffect
KAZMA_COMMITMENT_ENABLEDWhole layerON0 disables the entire layer
KAZMA_COMMITMENT_MODEModebalancedOverrides the mode
KAZMA_COMMITMENT_SWARM_SCOPE_ENFORCESwarm scopeonCaps dispatched-worker privileges (disable to opt out)
KAZMA_COMMITMENT_SOUL_REQUIRES_CONFIRMSoul gateoff (auto-ON in production / multi-user unless set)Holds Soul deltas for confirmation
KAZMA_AUTO_STORE_BELIEFSMemory auto-storeconservativeHow aggressively beliefs are stored

Every config key is also settable in the Settings UI / ConfigStore under agent.commitment.* and is re-read live (get_commitment_config) — no restart required.


Prometheus metrics exposed at /metrics:

  • kazma_commitment_decisions_total{decision="allow|clarify|confirm|deny|cancelled"}
  • kazma_commitment_pending — currently-held commitments awaiting resolution

9. Components (kazma_core/safety/commitment/)

Section titled “9. Components (kazma_core/safety/commitment/)”
ModuleResponsibility
side_effects.pyThe single source-of-truth registry: tool → ToolEffectProfile. MCP tools (mcp__*) route through classify_mcp_tool_effect. Unregistered mutators fail-closed (tokenized) when enforce_unknown_mutators is on.
authorize.pyauthorize_effect (the gate) + EffectDecision + act resolvers (remind, cancel_job, exec, send_outbound, config_change, proposal-backed post)
relative_time.pyresolve_remind — relative-time anchoring (EN + AR), G2-measured (0 false-allow)
store.pyCommitment rows + ops-SQLite tables + TTL/GC + tiered retention + list_pending_soul()
constraints.pyis_commitment_enabled + load_constraint_beliefs + cron_pending_jobs
config.pyget_commitment_config — the one live config reader
scope.pyScopeToken + swarm_scope (ContextVar) + default_worker_scope() + is_act_within_scope()
resume.pybuild_resume_value() + is_semantic_kind() — maps Approve/Deny → option/cancel on every platform
_commitment_resolve_gateExtracted gate in graph_tool_worker.py (not graph_builder.py)

  • Kill-switch / import missing → fail-open (layer treated as off). Exceptions on semantic tools → fail-closed at both chokes (do not free-fire remind/exec because the policy engine threw).
  • Conservative auto-store — beliefs are not over-written aggressively. Source-trust: user_explicit functional beliefs cannot be superseded by llm_inferred / system_tool.
  • No late approve — a commitment cannot be retroactively approved after the fact; it must be resolved in-turn.
  • GC cadence — expired/held commitments are reaped on the 15-min start_memory_worker loop (same loop rides HITL-gate TTL).

The whole layer is on by default in balanced mode; turn it off with KAZMA_COMMITMENT_ENABLED=0 if you need raw passthrough behavior.