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
| Rule | Enforcement |
|---|---|
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
| Source | Can promote? | Reason |
|---|---|---|
| L1 Archive entry | No | Raw data, not a proposal |
| L2 Working Memory entry | No | Ephemeral team context |
| L3 Emerging (status: active) | Yes | Standard promotion path |
| L3 Emerging (status: promoted) | No | Already promoted |
| L3 Emerging (status: rejected) | No | Already decided |
| L4 Canon entry | No | Already 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:
- Synthesizer excludes entities tagged
synthesizedfrom its candidate pool. - Cartographer skips relationship proposals between entities it created.
- All workers have circuit breakers that stop after 100 creates per run.