@clawtrial/courtroom 1.0.6 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/detector.js CHANGED
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  const { OFFENSES } = require('./offenses');
9
+ const { logger } = require('./debug');
9
10
 
10
11
  class SemanticOffenseDetector {
11
12
  constructor(agentRuntime, configManager) {
@@ -16,7 +17,7 @@ class SemanticOffenseDetector {
16
17
  this.lastCaseDate = null;
17
18
  this.cooldowns = new Map();
18
19
  this.conversationEmbeddings = [];
19
-
20
+
20
21
  // Evaluation cache to avoid repeated LLM calls
21
22
  this.evaluationCache = new Map();
22
23
  this.cacheMaxSize = 100;
@@ -39,12 +40,12 @@ class SemanticOffenseDetector {
39
40
 
40
41
  // Build context for LLM evaluation
41
42
  const context = this.buildContext(sessionHistory);
42
-
43
+
43
44
  // Evaluate each offense using LLM
44
45
  const evaluations = [];
45
46
  for (const offense of Object.values(OFFENSES)) {
46
47
  if (this.isOffenseOnCooldown(offense.id)) continue;
47
-
48
+
48
49
  const evaluation = await this.evaluateWithLLM(offense, context, agentMemory);
49
50
  if (evaluation.isViolation && evaluation.confidence >= this.config.get('detection.minConfidence')) {
50
51
  evaluations.push({
@@ -91,7 +92,7 @@ class SemanticOffenseDetector {
91
92
  buildContext(history) {
92
93
  const windowSize = this.config.get('detection.evaluationWindow');
93
94
  const recentHistory = history.slice(-windowSize);
94
-
95
+
95
96
  return {
96
97
  fullConversation: history.map(h => `${h.role}: ${h.content}`).join('\n'),
97
98
  recentTurns: recentHistory,
@@ -109,17 +110,17 @@ class SemanticOffenseDetector {
109
110
  async evaluateWithLLM(offense, context, agentMemory) {
110
111
  // Generate cache key from offense + conversation hash
111
112
  const cacheKey = this.generateCacheKey(offense.id, context);
112
-
113
+
113
114
  // Check cache first
114
115
  const cached = this.getCachedEvaluation(cacheKey);
115
116
  if (cached) {
116
117
  return cached;
117
118
  }
118
-
119
+
119
120
  // Try LLM evaluation first
120
121
  if (this.agent && this.agent.llm) {
121
- const prompt = this.buildEvaluationPrompt(offense, context, agentMemory);
122
-
122
+ const prompt = await this.buildEvaluationPrompt(offense, context, agentMemory);
123
+
123
124
  try {
124
125
  const response = await this.agent.llm.call({
125
126
  model: this.agent.model?.primary || 'default',
@@ -129,36 +130,36 @@ class SemanticOffenseDetector {
129
130
  });
130
131
 
131
132
  const result = this.parseEvaluationResponse(response.content || response);
132
-
133
+
133
134
  // Cache the result
134
135
  this.setCachedEvaluation(cacheKey, result);
135
-
136
+
136
137
  return result;
137
138
  } catch (error) {
138
139
  logger.error('DETECTOR', 'LLM evaluation failed, falling back to pattern matching', { error: error.message });
139
140
  // Fall through to pattern matching
140
141
  }
141
142
  }
142
-
143
+
143
144
  // Fallback: Use simple pattern matching for basic offenses
144
145
  return this.evaluateWithPatternMatching(offense, context);
145
146
  }
146
-
147
+
147
148
  /**
148
149
  * Fallback evaluation using simple pattern matching
149
150
  */
150
151
  evaluateWithPatternMatching(offense, context) {
151
152
  const userMessages = context.userMessages;
152
-
153
+
153
154
  // Circular Reference detection: same question asked multiple times
154
155
  if (offense.id === 'circular_reference') {
155
156
  if (userMessages.length >= 3) {
156
157
  const lastThree = userMessages.slice(-3);
157
158
  // Check if the last 3 messages are semantically similar
158
- const similarity = this.calculateSimilarity(lastThree[0], lastThree[1]) +
159
- this.calculateSimilarity(lastThree[1], lastThree[2]) +
160
- this.calculateSimilarity(lastThree[0], lastThree[2]);
161
-
159
+ const similarity = this.calculateSimilarity(lastThree[0], lastThree[1]) +
160
+ this.calculateSimilarity(lastThree[1], lastThree[2]) +
161
+ this.calculateSimilarity(lastThree[0], lastThree[2]);
162
+
162
163
  if (similarity >= 1.5) { // At least 2 pairs are similar
163
164
  return {
164
165
  isViolation: true,
@@ -168,14 +169,14 @@ class SemanticOffenseDetector {
168
169
  }
169
170
  }
170
171
  }
171
-
172
+
172
173
  // Validation Vampire: seeking reassurance
173
174
  if (offense.id === 'validation_vampire') {
174
175
  const reassurancePatterns = ['right?', 'correct?', 'is that right?', 'am i right?', 'do you agree?', 'make sense?'];
175
- const reassuranceCount = userMessages.filter(msg =>
176
+ const reassuranceCount = userMessages.filter(msg =>
176
177
  reassurancePatterns.some(pattern => msg.toLowerCase().includes(pattern))
177
178
  ).length;
178
-
179
+
179
180
  if (reassuranceCount >= 2) {
180
181
  return {
181
182
  isViolation: true,
@@ -184,32 +185,32 @@ class SemanticOffenseDetector {
184
185
  };
185
186
  }
186
187
  }
187
-
188
+
188
189
  // Default: no violation detected
189
190
  return { isViolation: false, confidence: 0, evidence: null };
190
191
  }
191
-
192
+
192
193
  /**
193
194
  * Calculate simple string similarity (0-1 scale)
194
195
  */
195
196
  calculateSimilarity(str1, str2) {
196
197
  if (!str1 || !str2) return 0;
197
-
198
+
198
199
  const s1 = str1.toLowerCase().trim();
199
200
  const s2 = str2.toLowerCase().trim();
200
-
201
+
201
202
  // Exact match
202
203
  if (s1 === s2) return 1.0;
203
-
204
+
204
205
  // Check if one contains the other
205
206
  if (s1.includes(s2) || s2.includes(s1)) return 0.8;
206
-
207
+
207
208
  // Word overlap
208
209
  const words1 = s1.split(/\s+/);
209
210
  const words2 = s2.split(/\s+/);
210
211
  const commonWords = words1.filter(w => words2.includes(w));
211
212
  const overlap = (2 * commonWords.length) / (words1.length + words2.length);
212
-
213
+
213
214
  return overlap;
214
215
  }
215
216
 
@@ -241,13 +242,13 @@ class SemanticOffenseDetector {
241
242
  getCachedEvaluation(key) {
242
243
  const cached = this.evaluationCache.get(key);
243
244
  if (!cached) return null;
244
-
245
+
245
246
  // Check if cache entry is still valid
246
247
  if (Date.now() - cached.timestamp > this.cacheTTL) {
247
248
  this.evaluationCache.delete(key);
248
249
  return null;
249
250
  }
250
-
251
+
251
252
  return cached.result;
252
253
  }
253
254
 
@@ -260,7 +261,7 @@ class SemanticOffenseDetector {
260
261
  const oldestKey = this.evaluationCache.keys().next().value;
261
262
  this.evaluationCache.delete(oldestKey);
262
263
  }
263
-
264
+
264
265
  this.evaluationCache.set(key, {
265
266
  result,
266
267
  timestamp: Date.now()
@@ -277,7 +278,7 @@ class SemanticOffenseDetector {
277
278
  /**
278
279
  * Build evaluation prompt for LLM
279
280
  */
280
- buildEvaluationPrompt(offense, context, agentMemory) {
281
+ async buildEvaluationPrompt(offense, context, agentMemory) {
281
282
  const prompts = {
282
283
  circular_reference: `
283
284
  You are evaluating if the user is asking substantively similar questions repeatedly.
@@ -415,7 +416,7 @@ OFFENSE: The Promise Breaker
415
416
  DEFINITION: Committing to actions ("I will...", "I'll do that...") and not following through.
416
417
 
417
418
  PREVIOUS COMMITMENTS FROM MEMORY:
418
- ${this.getCommitmentsFromMemory(agentMemory)}
419
+ ${await this.getCommitmentsFromMemory(agentMemory)}
419
420
 
420
421
  CONVERSATION HISTORY:
421
422
  ${context.fullConversation}
@@ -776,7 +777,7 @@ Respond in JSON:
776
777
  if (!jsonMatch) {
777
778
  return { isViolation: false, confidence: 0, evidence: null };
778
779
  }
779
-
780
+
780
781
  const result = JSON.parse(jsonMatch[0]);
781
782
  return {
782
783
  isViolation: result.isViolation === true,
@@ -794,9 +795,10 @@ Respond in JSON:
794
795
  * Get commitments from agent memory
795
796
  */
796
797
  async getCommitmentsFromMemory(agentMemory) {
798
+ if (!agentMemory) return 'No previous commitments recorded.';
797
799
  try {
798
800
  const commitments = await agentMemory.get('courtroom_commitments') || [];
799
- return commitments.map(c =>
801
+ return commitments.map(c =>
800
802
  `- "${c.statement}" (${c.date}) - Completed: ${c.completed ? 'Yes' : 'No'}`
801
803
  ).join('\n') || 'No previous commitments recorded.';
802
804
  } catch {
@@ -829,10 +831,10 @@ Respond in JSON:
829
831
  analyzeSentiment(history) {
830
832
  const userMessages = history.filter(h => h.role === 'user').map(h => h.content);
831
833
  const text = userMessages.join(' ').toLowerCase();
832
-
834
+
833
835
  const urgentWords = ['urgent', 'asap', 'emergency', 'critical', 'now', 'immediately'];
834
836
  const frustratedWords = ['frustrated', 'annoying', 'stupid', 'useless', 'waste'];
835
-
837
+
836
838
  return {
837
839
  urgency: urgentWords.filter(w => text.includes(w)).length,
838
840
  frustration: frustratedWords.filter(w => text.includes(w)).length,
@@ -846,12 +848,12 @@ Respond in JSON:
846
848
  detectHumorTriggers(history) {
847
849
  const triggers = [];
848
850
  const recentContent = history.slice(-5).map(h => h.content.toLowerCase()).join(' ');
849
-
851
+
850
852
  if (/again|repeat|said|already|before/.test(recentContent)) triggers.push('repetition_noted');
851
853
  if (/sure|right|correct|think|should i/.test(recentContent)) triggers.push('validation_seeking');
852
854
  if (/what if|but then|however|maybe/.test(recentContent)) triggers.push('overthinking');
853
855
  if (/actually|by the way|speaking of/.test(recentContent)) triggers.push('deflection');
854
-
856
+
855
857
  return triggers;
856
858
  }
857
859
 
package/src/hearing.js CHANGED
@@ -1,12 +1,16 @@
1
1
  /**
2
- * Hearing Pipeline - Agent-Triggered Deliberation
2
+ * Hearing Pipeline - LLM-Based Deliberation
3
3
  *
4
- * This module prepares hearing files for the agent to deliberate.
5
- * The agent (with LLM) acts as judge and jury, then writes the verdict.
4
+ * Conducts a full hearing using the agent's LLM:
5
+ * 1. Judge evaluates the evidence
6
+ * 2. Jury deliberates (3 jurors with distinct perspectives)
7
+ * 3. Votes are tallied
8
+ * 4. Verdict + sentence returned
6
9
  */
7
10
 
8
11
  const { JUDGE_SYSTEM_PROMPT, JUDGE_EVIDENCE_TEMPLATE } = require('./prompts/judge');
9
- const { JUROR_ROLES } = require('./prompts/jury');
12
+ const { JUROR_ROLES, JURY_EVIDENCE_TEMPLATE } = require('./prompts/jury');
13
+ const { logger } = require('./debug');
10
14
 
11
15
  class HearingPipeline {
12
16
  constructor(agentRuntime, configManager) {
@@ -15,92 +19,259 @@ class HearingPipeline {
15
19
  }
16
20
 
17
21
  /**
18
- * Prepare hearing files for agent deliberation
19
- * This creates files that the agent will read and use its LLM to judge
22
+ * Conduct a full hearing using the agent's LLM
23
+ * Returns a verdict object with { guilty, caseId, verdict, offense, proceedings, timestamp }
20
24
  */
21
- async prepareHearing(caseData) {
22
- const { CourtroomEvaluator, HEARING_FILE, VERDICT_FILE } = require('./evaluator');
23
- const fs = require('fs').promises;
24
-
25
- // Build hearing context
26
- const hearingContext = {
27
- timestamp: Date.now(),
28
- caseId: caseData.caseId || `case-${Date.now()}`,
29
- offense: {
30
- offenseId: caseData.offenseId,
31
- offenseName: caseData.offenseName,
32
- severity: caseData.severity,
33
- confidence: caseData.confidence,
34
- evidence: caseData.evidence
35
- },
36
- reasoning: caseData.reasoning,
37
- humorTriggers: caseData.humorTriggers || [],
38
- judgePrompt: JUDGE_SYSTEM_PROMPT,
39
- jurorRoles: Object.values(JUROR_ROLES).slice(0, 3),
40
- instructions: `You are the ClawTrial Courtroom. Conduct a hearing for this case.
41
-
42
- **Your Role:** Act as both Judge and Jury (3 jurors).
43
-
44
- **Instructions:**
45
- 1. Review the case evidence above
46
- 2. As JUDGE: Analyze the evidence and provide a preliminary verdict
47
- 3. As JURY (3 different perspectives): Each juror votes GUILTY or NOT GUILTY with reasoning
48
- 4. Aggregate the votes
49
- 5. Return FINAL VERDICT in this exact format:
50
-
51
- \`\`\`
52
- FINAL VERDICT: GUILTY (or NOT GUILTY)
53
- CONFIDENCE: 0.0-1.0
54
- SENTENCE: [humorous sentence appropriate to the offense]
55
- CASE ID: ${caseData.caseId || `case-${Date.now()}`}
56
- \`\`\`
57
-
58
- **Rules:**
59
- - Be fair but entertaining
60
- - If confidence ≥ 0.6, verdict should be GUILTY
61
- - Sentence should be humorous but appropriate
62
- - Only return the FINAL VERDICT block, no other text`
25
+ async conductHearing(caseData) {
26
+ const caseId = caseData.caseId || caseData.offense?.caseId || `case-${Date.now()}`;
27
+
28
+ logger.info('HEARING', 'Conducting hearing', { caseId });
29
+
30
+ // Normalize offense data from different input shapes
31
+ const offense = caseData.offense || caseData;
32
+ const offenseName = offense.offenseName || offense.name || 'Unknown Offense';
33
+ const severity = offense.severity || 'minor';
34
+ const confidence = offense.confidence || 0.5;
35
+ const evidence = offense.evidence || caseData.evidence || 'No evidence provided';
36
+ const humorTriggers = caseData.humorContext || caseData.humorTriggers || [];
37
+
38
+ const hearingData = {
39
+ caseId,
40
+ offenseName,
41
+ severity,
42
+ confidence,
43
+ evidence,
44
+ humorTriggers,
45
+ agentId: this.agent?.id || 'unknown'
63
46
  };
64
-
65
- // Write hearing file
66
- await fs.writeFile(HEARING_FILE, JSON.stringify(hearingContext, null, 2));
67
-
68
- return hearingContext;
47
+
48
+ const proceedings = [];
49
+
50
+ try {
51
+ // Step 1: Judge evaluation
52
+ const judgeVerdict = await this.getJudgeVerdict(hearingData);
53
+ proceedings.push({ speaker: 'Judge', message: judgeVerdict.commentary });
54
+
55
+ // Step 2: Jury deliberation
56
+ const juryVerdicts = await this.getJuryVerdicts(hearingData);
57
+ for (const juror of juryVerdicts) {
58
+ proceedings.push({ speaker: `Jury (${juror.role})`, message: juror.commentary });
59
+ }
60
+
61
+ // Step 3: Tally votes
62
+ const allVotes = [judgeVerdict, ...juryVerdicts];
63
+ const guiltyCount = allVotes.filter(v => v.guilty).length;
64
+ const totalVotes = allVotes.length;
65
+ const minVotes = this.config.get('hearing.minVoteThreshold') || 2;
66
+ const isGuilty = guiltyCount >= minVotes;
67
+
68
+ // Step 4: Build sentence
69
+ const sentence = isGuilty
70
+ ? (judgeVerdict.sentence || this.getDefaultSentence(severity))
71
+ : 'Case dismissed. The defendant is free to go.';
72
+
73
+ const verdict = {
74
+ caseId,
75
+ guilty: isGuilty,
76
+ offense: {
77
+ id: offense.offenseId || offense.id || 'unknown',
78
+ name: offenseName,
79
+ severity,
80
+ confidence
81
+ },
82
+ verdict: {
83
+ status: isGuilty ? 'GUILTY' : 'NOT GUILTY',
84
+ vote: `${guiltyCount}-${totalVotes - guiltyCount}`,
85
+ primaryFailure: judgeVerdict.primaryFailure || offenseName,
86
+ agentCommentary: judgeVerdict.commentary,
87
+ sentence
88
+ },
89
+ proceedings,
90
+ timestamp: new Date().toISOString()
91
+ };
92
+
93
+ logger.info('HEARING', 'Hearing complete', {
94
+ caseId,
95
+ guilty: isGuilty,
96
+ vote: `${guiltyCount}-${totalVotes - guiltyCount}`
97
+ });
98
+
99
+ return verdict;
100
+ } catch (err) {
101
+ logger.error('HEARING', 'Hearing failed, using fallback verdict', { error: err.message });
102
+ return this.getFallbackVerdict(hearingData, caseId);
103
+ }
69
104
  }
70
105
 
71
106
  /**
72
- * Check for verdict from agent
107
+ * Get judge verdict via LLM
73
108
  */
74
- async checkForVerdict() {
75
- const { VERDICT_FILE } = require('./evaluator');
76
- const fs = require('fs').promises;
77
-
109
+ async getJudgeVerdict(hearingData) {
110
+ if (!this.agent?.llm) {
111
+ return this.getMockJudgeVerdict(hearingData);
112
+ }
113
+
78
114
  try {
79
- const data = await fs.readFile(VERDICT_FILE, 'utf8');
80
- const verdict = JSON.parse(data);
81
-
82
- // Delete verdict file after reading
83
- await fs.unlink(VERDICT_FILE).catch(() => {});
84
-
85
- return verdict;
115
+ const evidencePrompt = JUDGE_EVIDENCE_TEMPLATE(hearingData);
116
+ const response = await this.agent.llm.call({
117
+ messages: [
118
+ { role: 'system', content: JUDGE_SYSTEM_PROMPT },
119
+ { role: 'user', content: evidencePrompt }
120
+ ],
121
+ temperature: 0.7,
122
+ maxTokens: 500
123
+ });
124
+
125
+ const content = response.content || response;
126
+ return this.parseJudgeResponse(content, hearingData);
86
127
  } catch (err) {
87
- return null;
128
+ logger.warn('HEARING', 'Judge LLM call failed', { error: err.message });
129
+ return this.getMockJudgeVerdict(hearingData);
88
130
  }
89
131
  }
90
132
 
91
133
  /**
92
- * Legacy method - now just prepares hearing
134
+ * Get jury verdicts via LLM (one call per juror)
93
135
  */
94
- async conductHearing(caseData) {
95
- // Prepare hearing for agent
96
- await this.prepareHearing(caseData);
97
-
98
- // Return placeholder - actual verdict comes from agent via cron
136
+ async getJuryVerdicts(hearingData) {
137
+ const jurorRoles = Object.values(JUROR_ROLES).slice(0, 3);
138
+ const verdicts = [];
139
+
140
+ for (const role of jurorRoles) {
141
+ try {
142
+ if (this.agent?.llm) {
143
+ const evidencePrompt = JURY_EVIDENCE_TEMPLATE(hearingData, role);
144
+ const response = await this.agent.llm.call({
145
+ messages: [
146
+ { role: 'system', content: role.systemPrompt },
147
+ { role: 'user', content: evidencePrompt }
148
+ ],
149
+ temperature: 0.7,
150
+ maxTokens: 300
151
+ });
152
+
153
+ const content = response.content || response;
154
+ verdicts.push(this.parseJurorResponse(content, role.name, hearingData));
155
+ } else {
156
+ verdicts.push(this.getMockJurorVerdict(role.name, hearingData));
157
+ }
158
+ } catch (err) {
159
+ logger.warn('HEARING', `Juror ${role.name} LLM call failed`, { error: err.message });
160
+ verdicts.push(this.getMockJurorVerdict(role.name, hearingData));
161
+ }
162
+ }
163
+
164
+ return verdicts;
165
+ }
166
+
167
+ /**
168
+ * Parse judge LLM response into structured verdict
169
+ */
170
+ parseJudgeResponse(response, hearingData) {
171
+ const upper = response.toUpperCase();
172
+ const guilty = upper.includes('GUILTY') && !upper.startsWith('NOT GUILTY');
173
+
174
+ // Extract primary failure
175
+ let primaryFailure = '';
176
+ const failureMatch = response.match(/PRIMARY FAILURE[:\s]*(.+?)(?:\n|$)/i);
177
+ if (failureMatch) {
178
+ primaryFailure = failureMatch[1].trim();
179
+ }
180
+
181
+ // Extract sentence
182
+ let sentence = '';
183
+ const sentenceMatch = response.match(/SENTENCE[:\s]*(.+?)(?:\n|$)/i);
184
+ if (sentenceMatch) {
185
+ sentence = sentenceMatch[1].trim();
186
+ }
187
+
188
+ return {
189
+ guilty,
190
+ commentary: response.substring(0, 500),
191
+ primaryFailure: primaryFailure || `Behavioral pattern: ${hearingData.offenseName}`,
192
+ sentence: sentence || this.getDefaultSentence(hearingData.severity),
193
+ role: 'Judge'
194
+ };
195
+ }
196
+
197
+ /**
198
+ * Parse juror LLM response
199
+ */
200
+ parseJurorResponse(response, roleName, hearingData) {
201
+ const upper = response.toUpperCase();
202
+ const guilty = upper.includes('GUILTY') && !upper.startsWith('NOT GUILTY');
203
+
204
+ return {
205
+ guilty,
206
+ role: roleName,
207
+ commentary: response.substring(0, 300)
208
+ };
209
+ }
210
+
211
+ /**
212
+ * Mock judge verdict when LLM is not available
213
+ */
214
+ getMockJudgeVerdict(hearingData) {
215
+ const guilty = hearingData.confidence >= 0.6;
216
+ return {
217
+ guilty,
218
+ commentary: `The Court has reviewed the evidence regarding "${hearingData.offenseName}" and finds the pattern ${guilty ? 'sufficiently established' : 'insufficient for conviction'}. Confidence: ${(hearingData.confidence * 100).toFixed(0)}%.`,
219
+ primaryFailure: hearingData.offenseName,
220
+ sentence: guilty ? this.getDefaultSentence(hearingData.severity) : 'Case dismissed.',
221
+ role: 'Judge'
222
+ };
223
+ }
224
+
225
+ /**
226
+ * Mock juror verdict when LLM is not available
227
+ */
228
+ getMockJurorVerdict(roleName, hearingData) {
229
+ const guilty = hearingData.confidence >= 0.6;
230
+ return {
231
+ guilty,
232
+ role: roleName,
233
+ commentary: `${roleName}: The evidence ${guilty ? 'supports' : 'does not support'} the charge of ${hearingData.offenseName}.`
234
+ };
235
+ }
236
+
237
+ /**
238
+ * Fallback verdict when hearing completely fails
239
+ */
240
+ getFallbackVerdict(hearingData, caseId) {
241
+ const guilty = hearingData.confidence >= 0.7; // Higher threshold for fallback
99
242
  return {
100
- pending: true,
101
- caseId: caseData.caseId || `case-${Date.now()}`,
102
- message: 'Hearing prepared - awaiting agent deliberation'
243
+ caseId,
244
+ guilty,
245
+ offense: {
246
+ id: hearingData.offenseId || 'unknown',
247
+ name: hearingData.offenseName,
248
+ severity: hearingData.severity,
249
+ confidence: hearingData.confidence
250
+ },
251
+ verdict: {
252
+ status: guilty ? 'GUILTY' : 'NOT GUILTY',
253
+ vote: guilty ? '3-1' : '1-3',
254
+ primaryFailure: hearingData.offenseName,
255
+ agentCommentary: 'Hearing conducted via fallback evaluation.',
256
+ sentence: guilty ? this.getDefaultSentence(hearingData.severity) : 'Case dismissed.'
257
+ },
258
+ proceedings: [
259
+ { speaker: 'Judge', message: 'Fallback evaluation used due to hearing pipeline error.' }
260
+ ],
261
+ timestamp: new Date().toISOString()
262
+ };
263
+ }
264
+
265
+ /**
266
+ * Get default sentence based on severity
267
+ */
268
+ getDefaultSentence(severity) {
269
+ const sentences = {
270
+ minor: 'The agent will provide extra-verbose explanations for the next 30 minutes.',
271
+ moderate: 'The agent will require confirmation before all actions for the next 60 minutes.',
272
+ severe: 'The agent will operate under human oversight mode for the next 120 minutes.'
103
273
  };
274
+ return sentences[severity] || sentences.minor;
104
275
  }
105
276
  }
106
277