How the engine works
From the prescriptive architecture to the actual dispatch loop: the 4-stage pipeline as designed, then the code that runs engagements.
src/orchestrator.ts252 lines · runTheShem L49–251
Outline 3 symbols
- logger const
- SchemOptions interface export
- runTheShem function export
1/**
2 * The Shem Orchestrator — Main entry point for the multi-agent system.
3 *
4 * v3: Refactored for session-based state isolation.
5 * All state lives in SessionState. Tools, hooks, and MCP server
6 * are created per-session via factory functions.
7 *
8 * Supports both CLI mode (ReadlineGateResolver) and API mode
9 * (AsyncGateResolver) — the session's gateResolver determines behavior.
10 */
11
12import { query } from '@anthropic-ai/claude-agent-sdk';
13import { retryQuery } from './utils/retry-query.js';
14import { orchestratorPrompt } from './agents/prompts/orchestrator.js';
15import { agentDefinitions } from './agents/definitions.js';
16import { createShemMcpServer } from './mcp/server.js';
17import { createAuditHooks, initAuditLog } from './hooks/audit-logger.js';
18import { createCostHooks } from './hooks/cost-tracker.js';
19import { createGateHooks } from './hooks/human-gate.js';
20import { createDynamicPermissions } from './permissions/dynamic-permissions.js';
21import { SessionState } from './session/session-state.js';
22import { eventTimestamp } from './events/event-bus.js';
23import { streamMessages } from './utils/stream-messages.js';
24import { handleSessionError } from './utils/error-recovery.js';
25import { config } from './config.js';
26import type { DocumentContext } from './types/index.js';
27import type { GateResolver } from './gates/gate-resolver.js';
28import type { EffortLevel } from './types/engagement.js';
29import { createLogger } from './utils/logger.js';
30
31const logger = createLogger('ORCHESTRATOR');
32
33export interface SchemOptions {
34 maxBudgetUsd?: number;
35 model?: string;
36 maxTurns?: number;
37 /** Claude API effort level — controls thinking depth and token spend. */
38 effort?: EffortLevel;
39 logLevel?: 'debug' | 'info' | 'warn' | 'error';
40 cwd?: string;
41 /** Optional pre-created session (for API mode). If not provided, a new one is created. */
42 session?: SessionState;
43 /** Optional gate resolver override (for API/testing). */
44 gateResolver?: GateResolver;
45 /** v18: LLM provider override for this session. Overrides global LAVERN_PROVIDER. */
46 provider?: 'anthropic' | 'mistral' | 'managed';
47}
48
49export async function runTheShem(
50 documentPath: string,
51 context: DocumentContext,
52 options: SchemOptions = {}
53): Promise<SessionState> {
54 const {
55 maxBudgetUsd = config.defaultBudgetUsd,
56 model = config.defaultModel,
57 maxTurns = config.defaultMaxTurns,
58 effort,
59 logLevel = config.logLevel,
60 } = options;
61
62 // Use provided session or create a new one
63 const session = options.session ?? new SessionState(undefined, {
64 gateResolver: options.gateResolver,
65 budgetUsd: maxBudgetUsd,
66 });
67 session.budgetUsd = maxBudgetUsd;
68
69 // Initialize audit log
70 initAuditLog(session);
71
72 // Note: debug logging is controlled via the SHEM_LOG_LEVEL env var at startup,
73 // not mutated per-session. The logLevel option is passed through to streamMessages.
74
75 // Emit session start event
76 session.events.emitEvent({
77 type: 'session_start',
78 sessionId: session.id,
79 document: documentPath,
80 timestamp: eventTimestamp(),
81 });
82
83 logger.info('Starting session', {
84 sessionId: session.id,
85 document: documentPath,
86 context: `${context.moment} | ${context.audience} | ${context.jurisdiction}`,
87 budget: maxBudgetUsd.toFixed(2),
88 model,
89 });
90
91 // Create session-bound factories
92 const shemMcpServer = createShemMcpServer(session);
93 const { auditLoggerHook, subagentStartHook, subagentStopHook } = createAuditHooks(session);
94 const { haltCheckHook, costTrackerHook } = createCostHooks(session);
95 const { humanGateEnforcerHook } = createGateHooks(session);
96
97 const prompt = `
98Review and redesign the legal document at: ${documentPath}
99
100Context:
101- **Moment**: ${context.moment} (when the user encounters this document)
102- **Audience**: ${context.audience}
103- **Jurisdiction**: ${context.jurisdiction}
104${context.documentType ? `- **Document Type**: ${context.documentType}` : ''}
105${context.focus ? `- **Focus Area**: ${context.focus}` : ''}
106
107Follow the full orchestration workflow. Start by calling \`get_current_step\` to
108see where you are in the workflow, then advance step by step.
109
110The 11-step workflow:
1111. INTAKE — Read document, gather context, query memory
1122. PARALLEL ANALYSIS — Dispatch ALL 5 analysis agents simultaneously
113 (design-reviewer, ethics-auditor, service-designer, plain-language-specialist, client-proxy)
1143. DEBATE ROUND 1 — Resolve conflicts, call resolve_debate for each topic
1154. HUMAN GATE — If RED ethics findings, request approval (confidence-routed)
1165. TRANSFORMATION — Dispatch transformation-specialist with findings + precedents
1176. PARALLEL VERIFICATION — Run self/cross/score verification + meaning-guardian + ethics re-check
1187. DEBATE ROUND 2 — Resolve transformation challenges
1198. HUMAN GATE — If CRITICAL meaning changes, request approval
1209. SYNTHESIS — Dispatch synthesis-editor for dual artifacts, save precedents
12110. HUMAN GATE — Request final delivery approval
12211. DELIVERED — Output delivered
123
124IMPORTANT:
125- Use advance_step after completing each step
126- Use resolve_debate to formally close EVERY debate topic
127- Use get_unresolved_debates before advancing past debate rounds
128- Run ALL three verification types after transformation
129- Query memory at start, save lessons at end
130
131Produce the complete dual-artifact output with full audit trail.
132 `.trim();
133
134 let result;
135 try {
136 result = retryQuery({
137 prompt,
138 options: {
139 systemPrompt: {
140 type: 'preset',
141 preset: 'claude_code',
142 append: orchestratorPrompt,
143 },
144 allowedTools: [
145 'Read', 'Grep', 'Glob', 'Task', 'TodoWrite',
146 // Workflow engine
147 'mcp__shem__get_current_step',
148 'mcp__shem__advance_step',
149 'mcp__shem__get_workflow_history',
150 // Debate board
151 'mcp__shem__post_finding',
152 'mcp__shem__post_challenge',
153 'mcp__shem__post_response',
154 'mcp__shem__resolve_debate',
155 'mcp__shem__get_findings',
156 'mcp__shem__get_challenges',
157 'mcp__shem__get_unresolved_debates',
158 'mcp__shem__get_debate_summary',
159 // Scoring engine
160 'mcp__shem__calculate_complexity_tax',
161 'mcp__shem__calculate_readability_score',
162 'mcp__shem__calculate_findability_score',
163 'mcp__shem__compare_before_after',
164 // Verification engine
165 'mcp__shem__run_self_verification',
166 'mcp__shem__run_cross_verification',
167 'mcp__shem__run_score_verification',
168 'mcp__shem__get_verification_summary',
169 // Memory system
170 'mcp__shem__add_institutional_memory',
171 'mcp__shem__query_institutional_memory',
172 'mcp__shem__save_matter_memory',
173 'mcp__shem__load_matter_memory',
174 'mcp__shem__save_precedent',
175 'mcp__shem__query_precedents',
176 // Approval gate
177 'mcp__shem__request_approval',
178 // v4: Report Card
179 'mcp__shem__compile_report_card',
180 'mcp__shem__get_report_card',
181 // v4: Feedback Loop
182 'mcp__shem__run_feedback_loop',
183 'mcp__shem__update_precedent_effectiveness',
184 'mcp__shem__record_anti_pattern',
185 'mcp__shem__query_anti_patterns',
186 // v4: Baselines
187 'mcp__shem__update_baselines',
188 'mcp__shem__check_against_baseline',
189 'mcp__shem__get_baseline',
190 'mcp__shem__get_quality_trend',
191 // v4: LEGAL.md
192 'mcp__shem__compile_legal_md',
193 'mcp__shem__get_legal_md',
194 // v4: Session Replay Testing
195 'mcp__shem__run_regression_test',
196 'mcp__shem__run_batch_regression',
197 'mcp__shem__compare_sessions',
198 // v5: Evaluator Gate (available but not used in legal-design flow)
199 'mcp__shem__run_evaluator_gate',
200 'mcp__shem__record_evaluation_result',
201 // v6: Risk Pricing
202 'mcp__shem__request_risk_assessment',
203 'mcp__shem__record_risk_assessment',
204 ],
205 agents: agentDefinitions,
206 canUseTool: createDynamicPermissions(session),
207 mcpServers: {
208 shem: shemMcpServer,
209 },
210 hooks: {
211 PostToolUse: [
212 { hooks: [auditLoggerHook] },
213 ],
214 PreToolUse: [
215 { hooks: [haltCheckHook, humanGateEnforcerHook, costTrackerHook] },
216 ],
217 SubagentStart: [
218 { hooks: [subagentStartHook] },
219 ],
220 SubagentStop: [
221 { hooks: [subagentStopHook] },
222 ],
223 },
224 maxBudgetUsd,
225 maxTurns,
226 model,
227 effort,
228 cwd: options.cwd,
229 },
230 }, session);
231 } catch (queryError) {
232 const sessionError = handleSessionError(session, queryError);
233 logger.error('Failed to initialize query', { step: sessionError.step, error: sessionError.cause });
234 throw queryError;
235 }
236
237 // Stream messages to the console
238 try {
239 await streamMessages(result, {
240 session,
241 documentLabel: documentPath,
242 logLevel,
243 });
244 } catch (error) {
245 const sessionError = handleSessionError(session, error);
246 logger.error('Session error', { step: sessionError.step, error: sessionError.cause });
247 throw error;
248 }
249
250 return session;
251}
252