Skip to content

Commit 8b07d69

Browse files
Claudeclaude
authored andcommitted
feat: add reflect_scars tool and default execution_successful at session close
Close the scar enforcement gap where agents confirm scars (APPLYING) but never report outcomes, causing 80% null execution_successful in scar_usage. Option A: session_close defaults execution_successful based on confirm_scars decision (APPLYING→true, unmentioned→false). Option B: new reflect_scars tool (gitmem-rf/gm-reflect) for end-of-session OBEYED/REFUTED evidence, bridged into scar_usage with priority over defaults. Refs: OD-772 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d33dd8e commit 8b07d69

File tree

7 files changed

+450
-7
lines changed

7 files changed

+450
-7
lines changed

src/server.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { recordScarUsage } from "./tools/record-scar-usage.js";
2525
import { recordScarUsageBatch } from "./tools/record-scar-usage-batch.js";
2626
import { recall } from "./tools/recall.js";
2727
import { confirmScars } from "./tools/confirm-scars.js";
28+
import { reflectScars } from "./tools/reflect-scars.js";
2829
import { saveTranscript } from "./tools/save-transcript.js";
2930
import { getTranscript } from "./tools/get-transcript.js";
3031
import { searchTranscripts } from "./tools/search-transcripts.js";
@@ -82,6 +83,7 @@ import type {
8283
SaveTranscriptParams,
8384
GetTranscriptParams,
8485
ConfirmScarsParams,
86+
ReflectScarsParams,
8587
} from "./types/index.js";
8688
import type { RecallParams } from "./tools/recall.js";
8789
import type { SearchParams } from "./tools/search.js";
@@ -159,6 +161,11 @@ export function createServer(): Server {
159161
case "gm-confirm":
160162
result = await confirmScars(toolArgs as unknown as ConfirmScarsParams);
161163
break;
164+
case "reflect_scars":
165+
case "gitmem-rf":
166+
case "gm-reflect":
167+
result = await reflectScars(toolArgs as unknown as ReflectScarsParams);
168+
break;
162169
case "session_start":
163170
case "gitmem-ss":
164171
case "gm-open":
@@ -269,6 +276,7 @@ export function createServer(): Server {
269276
const commands = [
270277
{ alias: "gitmem-r", full: "recall", description: "Check scars before taking action" },
271278
{ alias: "gitmem-cs", full: "confirm_scars", description: "Confirm recalled scars (APPLYING/N_A/REFUTED)" },
279+
{ alias: "gitmem-rf", full: "reflect_scars", description: "End-of-session scar reflection (OBEYED/REFUTED)" },
272280
{ alias: "gitmem-ss", full: "session_start", description: "Initialize session with context" },
273281
{ alias: "gitmem-sr", full: "session_refresh", description: "Refresh context for active session" },
274282
{ alias: "gitmem-sc", full: "session_close", description: "Close session with compliance validation" },

src/services/metrics.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export type ToolName =
3535
| "resolve_thread"
3636
| "create_thread"
3737
| "confirm_scars"
38+
| "reflect_scars"
3839
| "cleanup_threads"
3940
| "health";
4041

@@ -106,6 +107,7 @@ export const PERFORMANCE_TARGETS: Record<ToolName, number> = {
106107
resolve_thread: 100, // In-memory mutation + file write
107108
create_thread: 100, // In-memory mutation + file write
108109
confirm_scars: 500, // In-memory validation + file write
110+
reflect_scars: 500, // In-memory validation + file write
109111
cleanup_threads: 2000, // Fetch all threads + lifecycle computation
110112
health: 100, // In-memory read from EffectTracker
111113
};

src/services/session-state.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
* This allows recall() to always assign variants even without explicit parameters.
1313
*/
1414

15-
import type { SurfacedScar, ScarConfirmation, Observation, SessionChild, ThreadObject } from "../types/index.js";
15+
import type { SurfacedScar, ScarConfirmation, ScarReflection, Observation, SessionChild, ThreadObject } from "../types/index.js";
1616

1717
interface SessionContext {
1818
sessionId: string;
@@ -22,6 +22,7 @@ interface SessionContext {
2222
startedAt: Date;
2323
surfacedScars: SurfacedScar[]; // Track all scars surfaced during session
2424
confirmations: ScarConfirmation[]; // Refute-or-obey confirmations for recall-surfaced scars
25+
reflections: ScarReflection[]; // End-of-session scar reflections (OBEYED/REFUTED)
2526
observations: Observation[]; // v2 Phase 2: Sub-agent/teammate observations
2627
children: SessionChild[]; // v2 Phase 2: Child agent records
2728
threads: ThreadObject[]; // : Working thread state
@@ -34,11 +35,12 @@ let currentSession: SessionContext | null = null;
3435
* Set the current active session
3536
* Called by session_start
3637
*/
37-
export function setCurrentSession(context: Omit<SessionContext, 'surfacedScars' | 'confirmations' | 'observations' | 'children' | 'threads'> & { surfacedScars?: SurfacedScar[]; observations?: Observation[]; children?: SessionChild[]; threads?: ThreadObject[] }): void {
38+
export function setCurrentSession(context: Omit<SessionContext, 'surfacedScars' | 'confirmations' | 'reflections' | 'observations' | 'children' | 'threads'> & { surfacedScars?: SurfacedScar[]; observations?: Observation[]; children?: SessionChild[]; threads?: ThreadObject[] }): void {
3839
currentSession = {
3940
...context,
4041
surfacedScars: context.surfacedScars || [],
4142
confirmations: [],
43+
reflections: [],
4244
observations: context.observations || [],
4345
children: context.children || [],
4446
threads: context.threads || [],
@@ -137,6 +139,36 @@ export function getConfirmations(): ScarConfirmation[] {
137139
return currentSession?.confirmations || [];
138140
}
139141

142+
/**
143+
* Add end-of-session scar reflections (OBEYED/REFUTED) to the current session.
144+
* Called by reflect_scars tool after validation.
145+
*/
146+
export function addReflections(reflections: ScarReflection[]): void {
147+
if (!currentSession) {
148+
console.warn("[session-state] Cannot add reflections: no active session");
149+
return;
150+
}
151+
152+
for (const ref of reflections) {
153+
// Replace existing reflection for same scar_id (allow re-reflection)
154+
const idx = currentSession.reflections.findIndex(r => r.scar_id === ref.scar_id);
155+
if (idx >= 0) {
156+
currentSession.reflections[idx] = ref;
157+
} else {
158+
currentSession.reflections.push(ref);
159+
}
160+
}
161+
162+
console.error(`[session-state] Reflections tracked: ${currentSession.reflections.length} total`);
163+
}
164+
165+
/**
166+
* Get all end-of-session scar reflections for the current session.
167+
*/
168+
export function getReflections(): ScarReflection[] {
169+
return currentSession?.reflections || [];
170+
}
171+
140172
/**
141173
* Check if there are recall-surfaced scars that haven't been confirmed.
142174
* Only checks scars with source "recall" — session_start scars don't require confirmation.

src/tools/definitions.ts

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,39 @@ export const TOOLS = [
8787
required: ["confirmations"],
8888
},
8989
},
90+
{
91+
name: "reflect_scars",
92+
description: "End-of-session scar reflection — the closing counterpart to confirm_scars. Mirrors CODA-1's [Scar Reflection] protocol. Call BEFORE session_close to provide evidence of how each surfaced scar was handled. OBEYED: concrete evidence of compliance (min 15 chars). REFUTED: why it didn't apply + what was done instead (min 30 chars). Session close uses reflections to set execution_successful accurately.",
93+
inputSchema: {
94+
type: "object" as const,
95+
properties: {
96+
reflections: {
97+
type: "array",
98+
items: {
99+
type: "object",
100+
properties: {
101+
scar_id: {
102+
type: "string",
103+
description: "UUID of the surfaced scar (from recall or session_start)",
104+
},
105+
outcome: {
106+
type: "string",
107+
enum: ["OBEYED", "REFUTED"],
108+
description: "OBEYED: followed the scar with evidence. REFUTED: scar didn't apply, explain why.",
109+
},
110+
evidence: {
111+
type: "string",
112+
description: "Concrete evidence of compliance (OBEYED, min 15 chars) or explanation of why scar didn't apply (REFUTED, min 30 chars).",
113+
},
114+
},
115+
required: ["scar_id", "outcome", "evidence"],
116+
},
117+
description: "One reflection per surfaced scar.",
118+
},
119+
},
120+
required: ["reflections"],
121+
},
122+
},
90123
{
91124
name: "session_start",
92125
description: "Initialize session, detect agent, load institutional context (last session, recent decisions, open threads). Scars surface on-demand via recall(). DISPLAY: The result includes a pre-formatted 'display' field visible in the tool result. Output the display field verbatim as your response — tool results are collapsed in the CLI.",
@@ -812,6 +845,29 @@ export const TOOLS = [
812845
required: ["confirmations"],
813846
},
814847
},
848+
{
849+
name: "gitmem-rf",
850+
description: "gitmem-rf (reflect_scars) - End-of-session scar reflection (OBEYED/REFUTED with evidence)",
851+
inputSchema: {
852+
type: "object" as const,
853+
properties: {
854+
reflections: {
855+
type: "array",
856+
items: {
857+
type: "object",
858+
properties: {
859+
scar_id: { type: "string", description: "UUID of the surfaced scar" },
860+
outcome: { type: "string", enum: ["OBEYED", "REFUTED"], description: "Reflection outcome" },
861+
evidence: { type: "string", description: "Evidence (OBEYED min 15 chars, REFUTED min 30 chars)" },
862+
},
863+
required: ["scar_id", "outcome", "evidence"],
864+
},
865+
description: "One reflection per surfaced scar",
866+
},
867+
},
868+
required: ["reflections"],
869+
},
870+
},
815871
{
816872
name: "gitmem-ss",
817873
description: "gitmem-ss (session_start) - Initialize session with institutional context. DISPLAY: The result includes a pre-formatted 'display' field. Output the display field verbatim as your response — tool results are collapsed in the CLI.",
@@ -1510,6 +1566,29 @@ export const TOOLS = [
15101566
required: ["confirmations"],
15111567
},
15121568
},
1569+
{
1570+
name: "gm-reflect",
1571+
description: "gm-reflect (reflect_scars) - End-of-session scar reflection",
1572+
inputSchema: {
1573+
type: "object" as const,
1574+
properties: {
1575+
reflections: {
1576+
type: "array",
1577+
items: {
1578+
type: "object",
1579+
properties: {
1580+
scar_id: { type: "string", description: "UUID of the surfaced scar" },
1581+
outcome: { type: "string", enum: ["OBEYED", "REFUTED"] },
1582+
evidence: { type: "string", description: "Evidence of compliance or refutation" },
1583+
},
1584+
required: ["scar_id", "outcome", "evidence"],
1585+
},
1586+
description: "One reflection per surfaced scar",
1587+
},
1588+
},
1589+
required: ["reflections"],
1590+
},
1591+
},
15131592
{
15141593
name: "gm-refresh",
15151594
description: "gm-refresh (session_refresh) - Refresh context for the active session without creating a new one. DISPLAY: The result includes a pre-formatted 'display' field. Output the display field verbatim as your response — tool results are collapsed in the CLI.",
@@ -2196,15 +2275,15 @@ export const TOOLS = [
21962275
*/
21972276
export const ALIAS_TOOL_NAMES = new Set([
21982277
// gitmem-* aliases
2199-
"gitmem-r", "gitmem-cs", "gitmem-ss", "gitmem-sr", "gitmem-sc",
2278+
"gitmem-r", "gitmem-cs", "gitmem-rf", "gitmem-ss", "gitmem-sr", "gitmem-sc",
22002279
"gitmem-cl", "gitmem-cd", "gitmem-rs", "gitmem-rsb",
22012280
"gitmem-st", "gitmem-gt", "gitmem-stx",
22022281
"gitmem-search", "gitmem-log", "gitmem-analyze",
22032282
"gitmem-pc", "gitmem-ao",
22042283
"gitmem-lt", "gitmem-rt", "gitmem-ct", "gitmem-ps", "gitmem-ds",
22052284
"gitmem-cleanup", "gitmem-health", "gitmem-al", "gitmem-graph",
22062285
// gm-* aliases
2207-
"gm-open", "gm-confirm", "gm-refresh", "gm-close",
2286+
"gm-open", "gm-confirm", "gm-reflect", "gm-refresh", "gm-close",
22082287
"gm-scar", "gm-search", "gm-log", "gm-analyze",
22092288
"gm-pc", "gm-absorb",
22102289
"gm-threads", "gm-resolve", "gm-thread-new", "gm-promote", "gm-dismiss",

0 commit comments

Comments
 (0)