Skip to main content

Promotion Pipeline

Problem

When a Synthesizer detects a cross-agent pattern — say "agents using the fetch-data tool fail 40% of the time on large payloads" — it creates a proposal: "limit payload size to 5MB." If that proposal auto-promotes to enforceable policy, every agent in the system is immediately constrained. The blast radius is total.

Auto-generated policies without human review create three failure modes:

  • False positives. A pattern observed across 5 agents may reflect a shared misconfiguration, not an organizational truth.
  • Context blindness. The Synthesizer sees statistical patterns but not business context. Retries might be essential for financial transactions even if they look wasteful in trace data.
  • Irreversibility. Once a policy is enforced via the Policy Bridge, agents treat it as mandatory. Rolling back requires someone to notice the damage first.

Solution

SOMA enforces a strict promotion pipeline with an explicit governance gate between machine proposals (L3) and organizational canon (L4). No shortcut exists. The pipeline is:

Traces → Harvester → L1 → Synthesizer → L3 → Human Review → L4 → Policy Bridge → Agents

The Governance API provides list_pending(), promote(), and reject() operations. Only pending L3 entries are eligible. The human reviewer sees the confidence score, evidence chain, and linked traces before deciding.

Architecture

Code Examples

The Governance API

import { createGovernanceAPI } from './governance';
import { Vault } from './vault';

const vault = new Vault({ dir: '.soma/vault' });
const governance = createGovernanceAPI(vault);

Listing pending proposals

// Returns L3 entries with status 'pending', sorted by confidence descending
const pending = await governance.list_pending();

// Example output:
// [
// {
// id: 'proposal-retry-limit',
// name: 'Agents should cap retries at 3',
// confidence: 0.82,
// evidence_links: ['exec-invoice-agent-001', 'exec-email-router-042', ...],
// layer: 'emerging',
// status: 'active',
// created: '2026-03-20T10:00:00.000Z'
// },
// {
// id: 'proposal-payload-limit',
// name: 'Limit fetch-data payload to 5MB',
// confidence: 0.65,
// evidence_links: ['exec-order-agent-007', 'exec-support-agent-003'],
// ...
// }
// ]

Reviewing evidence before promoting

// Get the full evidence chain for a proposal
const evidence = await governance.get_evidence('proposal-retry-limit');

// Returns:
// {
// proposal: { id: 'proposal-retry-limit', confidence: 0.82, ... },
// evidence: [
// { id: 'exec-invoice-agent-001', type: 'execution', layer: 'archive', ... },
// { id: 'exec-email-router-042', type: 'execution', layer: 'archive', ... },
// { id: 'exec-invoice-agent-019', type: 'decision', layer: 'archive', ... }
// ]
// }

Promoting to canon

// Creates a new L4 entry, marks the L3 entry as 'promoted'
await governance.promote('proposal-retry-limit', 'reviewer-jane');

// The new L4 entry contains:
// {
// id: 'canon-proposal-retry-limit',
// layer: 'canon',
// status: 'enforcing',
// origin_l3_id: 'proposal-retry-limit',
// ratified_by: 'reviewer-jane',
// ratified_at: '2026-03-21T15:00:00.000Z',
// source_worker: 'governance'
// }

Rejecting a proposal

// Marks the L3 entry as 'rejected' with reason — it stays in L3, never decays
await governance.reject(
'proposal-payload-limit',
'reviewer-jane',
'Payload limits would break the batch import pipeline. Revisit after Q2 migration.'
);

CLI workflow

# List pending proposals
soma governance list

# Show a proposal with its evidence chain
soma governance show --id proposal-retry-limit

# Promote after review
soma governance promote --id proposal-retry-limit

# Reject with reason
soma governance reject --id proposal-payload-limit --reason "Breaks batch import pipeline"

Dashboard workflow

The AgentFlow Dashboard provides a governance page with the same operations:

GET  /api/soma/governance           — Layer counts + pending proposals + canon
POST /api/soma/governance/promote — { entryId, reviewerId }
POST /api/soma/governance/reject — { entryId, reviewerId, reason }
GET /api/soma/governance/evidence/:id — Evidence chain for a proposal

The dashboard server calls the SOMA CLI via child_process.execSync with sanitized arguments. The browser never touches the vault directly.

Rules and Invariants

Promotion rules

RuleEnforcement
No auto-promotion. L3 to L4 requires explicit human action via promote().writeToLayer('canon', ...) only accepts source_worker: 'governance'
L2 cannot be promoted. Working memory is ephemeral team context, not a governance candidate.promote() throws if the entry's layer is working
Already-decided entries are final. Promoted or rejected entries cannot be re-promoted or re-rejected.promote() and reject() throw if status is promoted or rejected
Only pending L3 entries are eligible.promote() checks layer === 'emerging' and status === 'active'
Evidence chain required. Every L3 proposal must have evidence_links pointing to L1 traces.Schema validation on writeToLayer('emerging', ...)
L4 must reference its origin. Every canon entry carries origin_l3_id, ratified_by, and ratified_at.Schema validation on writeToLayer('canon', ...)

What cannot be promoted

SourceCan promote?Reason
L1 Archive entryNoRaw data, not a proposal
L2 Working Memory entryNoEphemeral team context
L3 Emerging (status: active)YesStandard promotion path
L3 Emerging (status: promoted)NoAlready promoted
L3 Emerging (status: rejected)NoAlready decided
L4 Canon entryNoAlready canon

Confidence scoring (Synthesizer output)

The confidence score on L3 proposals helps reviewers prioritize:

  • Cross-agent corroboration (5+ agents): confidence >= 0.8
  • Single-agent patterns: capped at 0.5
  • Evidence count: +0.02 per trace, +0.15 per agent (incremental)

list_pending() sorts by confidence descending so reviewers see the strongest proposals first.

Feedback loop guards

The promotion pipeline includes guards that prevent workers from processing their own output:

  1. Synthesizer excludes entities tagged synthesized from its candidate pool.
  2. Cartographer skips relationship proposals between entities it created.
  3. All workers have circuit breakers that stop after 100 creates per run.