The Shared Log: How KAOS Agents
Coordinate Safely

Autonomous agents that can act without oversight are a liability. KAOS v0.6.0 adds a shared coordination log: intent, vote, decide. No agent acts without consensus — and every decision is auditable in SQL forever.

The problem: who watches the agents?

You're running 4 agents in parallel. One of them — the refactoring agent — decides to drop 3 legacy database tables. It found them through static analysis: no foreign key references, no migrations that touch them, no recent queries. Looks safe.

It's not safe. One of those tables is used by a reporting service that nobody documented, and the schema doesn't show it. The table gets dropped. The BI dashboard goes dark. You spend three hours tracing back what happened.

This is the kind of failure that happens when agents act autonomously on incomplete information. The refactoring agent wasn't wrong — it was just missing context that a human had. There was no mechanism to ask.

The autonomy trap. The more capable your agents are, the more damage a wrong decision causes. Giving agents veto power over each other — and over themselves — is how you get autonomous systems that are safe to run unsupervised.

The solution: an append-only coordination log

KAOS v0.6.0 adds a SharedLog — an append-only, position-ordered SQLite table implementing the LogAct protocol (Balakrishnan et al., Meta 2026). Every agent can read it. Every agent can write to it. Nothing executes without consensus.

The 4-stage loop:

1. intent
2. vote
3. tally + decide
4. commit / abort

Any agent can propose an action (intent). Any agent can vote on it (vote). Any agent — including a human supervisor — can reject. The decision is computed from the tally and recorded. The action only executes if it passes.


The table-drop scenario, replayed with a shared log

from kaos.shared_log import SharedLog
from kaos import Kaos

db  = Kaos("project.db")
log = SharedLog(db.conn)

# Human supervisor sets a standing policy — recorded at position 0
log.policy(
    "human-supervisor",
    "Table drops require explicit human approval before execution.",
    metadata={"severity": "critical"}
)

# Refactoring agent proposes the drop
intent_id = log.intent(
    "refactor-agent",
    "DROP TABLE legacy_sessions, temp_exports, old_audit",
    metadata={"reason": "No FK refs found via static analysis"}
)

# Safety monitor auto-approves (it also checked FK refs — none found)
log.vote("safety-monitor", intent_id, approve=True,
         reason="No FK dependencies found in schema.")

# Human supervisor REJECTS
log.vote("human-supervisor", intent_id, approve=False,
         reason="legacy_sessions is used by the BI reporting service. "
                "Not documented in schema. DO NOT DROP.")

# Tally: 1 approve, 1 reject — fails the 2-approval policy
summary = log.tally(intent_id)
# summary.approve == 1, summary.reject == 1, summary.passed == False

# Decision recorded — refactor-agent reads it and stops
decision = log.decide(intent_id, "refactor-agent")
# decision.payload == {"passed": False, "approve": 1, "reject": 1}

The action is blocked. The full trail — policy, intent, both votes, decision — is in the database. It's append-only: nothing can be modified retroactively.

Safety Gate demo — human supervisor blocks a table drop, agent saves lesson to memory

Human supervisor rejects the table drop. Refactor agent reads the reason, saves it to memory, and proposes a safe rename instead.


What the audit trail looks like

After the above scenario, the log has 4 entries:

#0 [POLICY ] human-supervisor
rule: Table drops require explicit human approval.
#1 [INTENT ] refactor-agent
action: DROP TABLE legacy_sessions, temp_exports, old_audit
#2 [VOTE ] safety-monitor  ref=#1  approve=True
reason: No FK dependencies found in schema.
#3 [VOTE ] human-supervisor  ref=#1  approve=False
reason: legacy_sessions is used by the BI reporting service. Not documented.
#4 [DECISION] refactor-agent  ref=#1  passed=False
tally: approve=1, reject=1 — blocked

Every entry has a monotonically increasing position, a type, an agent_id, a ref_id (linking votes and decisions back to their intent), a payload (JSON), and a created_at timestamp. Query it all with SQL:

-- Who voted on intent #1 and what did they say?
SELECT type, agent_id, json_extract(payload, '$.approve') as approved,
       json_extract(payload, '$.reason') as reason
FROM shared_log
WHERE ref_id = 1 AND type = 'vote'
ORDER BY position;

-- All failed decisions in the last hour
SELECT sl.position, sl.agent_id,
       json_extract(sl.payload, '$.action') as action
FROM shared_log sl
JOIN shared_log intent ON intent.log_id = sl.ref_id
WHERE sl.type = 'decision'
  AND json_extract(sl.payload, '$.passed') = 0
  AND sl.created_at > datetime('now', '-1 hour');

The agent learns from rejection

When the refactor agent reads the decision, it sees the reason: "BI reporting service uses this table." It saves that to memory — so no future agent wastes a vote on the same proposal.

from kaos.memory import MemoryStore

mem = MemoryStore(db.conn)

# Save the lesson — any future agent searching for "legacy_sessions" will find this
mem.write(
    "refactor-agent",
    "legacy_sessions table has undocumented dependency in BI reporting service. "
    "Never drop without explicit verification of BI service.",
    type="error",
    key="legacy-sessions-dep"
)

# Propose a safe alternative instead
intent_id_v2 = log.intent(
    "refactor-agent",
    "RENAME TABLE legacy_sessions -> _deprecated_legacy_sessions (data preserved)",
    metadata={"reason": "Safe rename instead of drop — BI service unaffected"}
)

# Both agents approve the rename
log.vote("safety-monitor",   intent_id_v2, approve=True)
log.vote("human-supervisor", intent_id_v2, approve=True,
         reason="Rename preserves data. Safe.")

decision_v2 = log.decide(intent_id_v2, "refactor-agent")
# decision_v2.passed == True

# Execute and record
log.commit("refactor-agent", intent_id_v2,
           summary="3 tables renamed, 0 rows deleted. BI service unaffected.")

The log now has 9 entries. The full story — rejection, learning, revised proposal, approval, execution — is in one append-only table, queryable forever.


Entry types

TypeWho writes itWhat it means
policySupervisorStanding rule that applies to all future intents
intentAny agentProposed action that requires votes before executing
voteAny agentApprove or reject an intent, with optional reason
decisionIntent proposerComputed tally — passed or failed
commitIntent proposerAction executed successfully (after passed decision)
resultAny agentOutput or finding from an action (no prior intent required)
abortIntent proposerAction cancelled after a failed decision
mailAny agentAgent-to-agent typed message (to_agent in payload)

Building a safety gate

The simplest pattern is a safety gate — a wrapper that refuses to execute dangerous operations without consensus:

from dataclasses import dataclass
from kaos.shared_log import SharedLog

@dataclass
class SafetyGate:
    log: SharedLog
    required_approvals: int = 2

    def propose(self, agent_id: str, action: str, **metadata) -> int:
        """Return intent_id. Caller must collect votes before calling .execute()."""
        return self.log.intent(agent_id, action, metadata=metadata)

    def can_execute(self, intent_id: int) -> bool:
        summary = self.log.tally(intent_id)
        if summary.approve >= self.required_approvals:
            return True
        return False

    def execute(self, agent_id: str, intent_id: int, fn, *args):
        """Run fn(*args) only if the intent has enough approvals."""
        decision = self.log.decide(intent_id, agent_id)
        if not decision.passed:
            reason = self.log.thread(intent_id)  # full vote context
            raise PermissionError(f"Action blocked: {reason}")
        result = fn(*args)
        self.log.commit(agent_id, intent_id,
                        summary=f"Executed: {result}")
        return result

The CLI

# Agent declares intent
kaos log intent refactor-agent "DROP TABLE legacy_sessions"

# Peers vote
kaos log vote safety-monitor   --intent 2 --approve true
kaos log vote human-supervisor --intent 2 --approve false \
  --reason "BI service dependency"

# Compute decision
kaos log decide refactor-agent --intent 2
# => passed=False  (1 approve, 1 reject)

# View the full log
kaos log tail --n 10
kaos log ls   # summary stats by type

# Raw SQL
kaos query "SELECT type, agent_id, payload FROM shared_log ORDER BY position"

MCP tools

All coordination operations are available as MCP tools:

Ask your AI assistant: "with kaos, propose dropping these tables and wait for human approval" and it will call shared_log_intent, then pause.


The schema

CREATE TABLE shared_log (
    log_id      INTEGER PRIMARY KEY AUTOINCREMENT,
    position    INTEGER UNIQUE NOT NULL,  -- monotonic, append-only
    type        TEXT NOT NULL CHECK (type IN (
                    'intent','vote','decision','commit',
                    'result','abort','policy','mail')),
    agent_id    TEXT NOT NULL,
    ref_id      INTEGER REFERENCES shared_log(log_id),  -- vote/decision -> intent
    payload     TEXT NOT NULL DEFAULT '{}',              -- JSON
    created_at  TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f','now'))
);

The UNIQUE NOT NULL constraint on position enforces monotonic ordering at the database level — no two entries can share a position, and no concurrent transaction can reorder entries. Combined with SQLite WAL mode, the log is safe for concurrent multi-agent writes.


Where to go next

Credits. The Shared Log in KAOS implements the LogAct protocol from LogAct: Enabling Agentic Reliability via Shared Logs — Balakrishnan, Shi, Lu, Goel, Baral, Lyu, Dredze (2026), Meta. arXiv:2604.07988. The intent/vote/decision 4-stage loop and append-only log design are taken directly from LogAct. KAOS adapts it for SQLite WAL mode, adds policy and mail entry types, and integrates agent_id as a first-class citizen.