Dream Audit Log
graph_auditRecords a detailed audit log of dream process events, enabling reconstruction of entity-resolution decisions for later graph unmerge operations.
Instructions
Append a structured event to the dream process audit log (logs/dream-audit.jsonl). Call this during the dream process to record run_start, run_end, transcript_start, transcript_end, entity_created, entity_resolved, edge_created, edge_modified, merge_flagged, contradiction_found, ingest_start, ingest_end, decay_applied, format_warning, or error events. entity_resolved is the audit trail for entity-resolution decisions during dream — every time the dream picks between matching an existing entity, creating a new one, or flagging an ambiguous candidate, log it here so a later graph_unmerge can reconstruct why a merge happened.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| event | Yes | Event type | |
| data | Yes | Event payload — fields vary by event type. Always include relevant names/IDs. |
Implementation Reference
- src/mcp-server/index.ts:1634-1642 (handler)Handler function for the graph_audit tool. Calls appendAuditEvent to write the structured event to logs/dream-audit.jsonl.
}, async ({ event, data }) => { const auditEvent = { event, timestamp: new Date().toISOString(), ...data, } as DreamAuditEvent; appendAuditEvent(auditEvent); return toolResult({ logged: true, event, timestamp: auditEvent.timestamp }); }); - src/mcp-server/index.ts:1619-1633 (schema)Zod input schema for graph_audit: requires an event enum and a data record.
inputSchema: { event: z .enum([ "run_start", "run_end", "transcript_start", "transcript_end", "transcript_skipped", "entity_created", "entity_resolved", "edge_created", "edge_modified", "merge_flagged", "contradiction_found", "ingest_start", "ingest_end", "decay_applied", "format_warning", "error", ]) .describe("Event type"), data: z .record(z.string(), z.unknown()) .describe("Event payload — fields vary by event type. Always include relevant names/IDs."), }, - src/mcp-server/index.ts:1609-1642 (registration)Registration of graph_audit tool with the MCP server, including title and description.
server.registerTool("graph_audit", { title: "Dream Audit Log", description: "Append a structured event to the dream process audit log (logs/dream-audit.jsonl). " + "Call this during the dream process to record run_start, run_end, transcript_start, " + "transcript_end, entity_created, entity_resolved, edge_created, edge_modified, merge_flagged, " + "contradiction_found, ingest_start, ingest_end, decay_applied, format_warning, or error events. " + "entity_resolved is the audit trail for entity-resolution decisions during dream — every time the " + "dream picks between matching an existing entity, creating a new one, or flagging an ambiguous " + "candidate, log it here so a later graph_unmerge can reconstruct why a merge happened.", inputSchema: { event: z .enum([ "run_start", "run_end", "transcript_start", "transcript_end", "transcript_skipped", "entity_created", "entity_resolved", "edge_created", "edge_modified", "merge_flagged", "contradiction_found", "ingest_start", "ingest_end", "decay_applied", "format_warning", "error", ]) .describe("Event type"), data: z .record(z.string(), z.unknown()) .describe("Event payload — fields vary by event type. Always include relevant names/IDs."), }, }, async ({ event, data }) => { const auditEvent = { event, timestamp: new Date().toISOString(), ...data, } as DreamAuditEvent; appendAuditEvent(auditEvent); return toolResult({ logged: true, event, timestamp: auditEvent.timestamp }); }); - src/shared/dream-audit.ts:139-156 (helper)Helper function that appends a structured audit event to logs/dream-audit.jsonl.
export function appendAuditEvent(event: DreamAuditEvent): void { try { mkdirSync(join(GRAPH_MEMORY_HOME, "logs"), { recursive: true }); appendFileSync(AUDIT_LOG, JSON.stringify(event) + "\n"); } catch { /* never throw from audit */ } } /** Convenience wrapper — stamps timestamp automatically. */ export function auditEvent<T extends DreamAuditEvent["event"]>( eventType: T, data: Omit<Extract<DreamAuditEvent, { event: T }>, "event" | "timestamp">, ): void { appendAuditEvent({ event: eventType, timestamp: new Date().toISOString(), ...data, } as unknown as DreamAuditEvent); } - src/shared/dream-audit.ts:17-133 (schema)TypeScript discriminated union type defining all possible audit event shapes.
export type DreamAuditEvent = | (BaseEvent & { event: "run_start"; source: string; transcripts_pending: number; ingest_pending: number; }) | (BaseEvent & { event: "run_end"; source: string; duration_ms: number; transcripts_processed: number; ingest_processed: number; entities_created: number; edges_created: number; errors: number; }) | (BaseEvent & { event: "transcript_start"; session_id: string; file_path: string; line_count: number; }) | (BaseEvent & { event: "transcript_end"; session_id: string; entities_extracted: number; edges_created: number; }) | (BaseEvent & { event: "transcript_skipped"; session_id: string; file_path: string; reason: string; }) | (BaseEvent & { event: "entity_created"; name: string; entity_type: string; confidence: number; source_session: string; }) | (BaseEvent & { event: "edge_created"; from_name: string; to_name: string; relation: string; weight: number; source_session: string; }) | (BaseEvent & { event: "edge_modified"; from_name: string; to_name: string; relation: string; old_weight: number; new_weight: number; }) | (BaseEvent & { event: "entity_resolved"; /** Raw name as encountered in the transcript/document. */ candidate_name: string; /** Action taken — see prompts/dream-nightly.md §4e for definitions. */ action: | "matched_existing" // candidate matched an existing entity by name/alias/embedding | "created_new" // no match strong enough; created a fresh entity | "skipped_ambiguous" // multiple candidates above threshold, none clearly best — left unresolved | "alias_attached"; // candidate kept distinct but linked via ALIAS_OF to canonical /** Resolved entity id when action != "skipped_ambiguous". */ chosen_id?: string; /** Why this action was chosen — "exact name match", "embedding sim 0.91 to <id>", * "name token Jaccard 0.8 with <id>", "no candidate above threshold", etc. */ reason: string; /** Cosine similarity score when the decision was driven by embeddings. */ similarity_score?: number; /** Source session for cross-referencing against the changelog. */ source_session?: string; }) | (BaseEvent & { event: "merge_flagged"; entity_a: string; entity_b: string; reason: string; }) | (BaseEvent & { event: "contradiction_found"; entity_a: string; entity_b: string; relation: string; description: string; }) | (BaseEvent & { event: "ingest_start"; file_path: string; }) | (BaseEvent & { event: "ingest_end"; file_path: string; entities_extracted: number; edges_created: number; }) | (BaseEvent & { event: "decay_applied"; nodes_affected: number; edges_affected: number; }) | (BaseEvent & { event: "format_warning"; file_path: string; format_version: string; warnings: string[]; }) | (BaseEvent & { event: "error"; context: string; message: string; });