Skip to content

CausalityService

Layer 1 service that tracks decision causality and enables reasoning reconstruction.

Overview

CausalityService is the implementation of Layer 1 (Causality Engine). It tracks WHY contexts were created by maintaining causal relationships and enabling backwards traversal through decision history.

Location: src/domain/services/CausalityService.ts

Layer: Layer 1 - Past (WHY)

Purpose: Understand historical causality and decision chains


Constructor

typescript
constructor(private readonly repository: IContextRepository)

Parameters:

  • repository: Database abstraction for context storage

Dependency Injection: Repository is injected, not created internally (testable, swappable)


Methods

buildCausalChain(snapshotId: string): Promise<CausalChainNode[]>

Trace decision history backwards from a context to its root cause.

Parameters:

  • snapshotId: Starting context ID to trace from

Returns: Array of CausalChainNode with root at index 0

Algorithm:

typescript
1. Start at target snapshot
2. Look up snapshot in repository
3. Check if it has causedBy parent
4. If yes, fetch parent and add to chain
5. Repeat until causedBy is null (root found)
6. Return chain with root first

Example:

typescript
const chain = await causalityService.buildCausalChain('decision-id');

// Returns:
// [
//   { snapshot: {...}, depth: 0 },  // Root (conversation)
//   { snapshot: {...}, depth: 1 },  // Research
//   { snapshot: {...}, depth: 2 }   // Decision
// ]

Edge Cases:

  • Circular references: Prevented by visited set
  • Broken chains: Stops at missing parent
  • Single node: Returns array with one element

Performance: O(n) where n = chain length (typically 3-10)


reconstructReasoning(snapshotId: string): Promise<string>

Generate human-readable explanation of why a context was created.

Parameters:

  • snapshotId: Context to explain

Returns: Formatted markdown string

Example:

typescript
const reasoning = await causalityService.reconstructReasoning('abc-123');

// Returns:
// **Action Type**: decision
//
// **Rationale**: OAuth2 with PKCE is more secure than JWT
//
// **Context Summary**: Decision to use OAuth2 for mobile app

Format:

**Action Type**: {actionType}

**Rationale**: {rationale}

**Context Summary**: {summary}

Edge Cases:

  • No causality metadata: Returns summary only
  • Missing rationale: Shows "No rationale provided"

getCausalityStats(project: string): Promise<CausalityStats>

Get aggregate statistics on causal relationships for a project.

Parameters:

  • project: Project to analyze

Returns: Statistics object

Return Type:

typescript
interface CausalityStats {
  totalWithCausality: number;
  averageChainLength: number;
  actionTypeCounts: Record<ActionType, number>;
  rootCauses: number;
}

Example:

typescript
const stats = await causalityService.getCausalityStats('my-project');

// Returns:
// {
//   totalWithCausality: 42,
//   averageChainLength: 3.5,
//   actionTypeCounts: {
//     conversation: 10,
//     research: 15,
//     decision: 12,
//     file_edit: 5,
//     tool_use: 0
//   },
//   rootCauses: 10
// }

Performance:

  • Queries all contexts for project
  • Samples 10 chains for average length calculation
  • O(n) where n = context count

Types

CausalChainNode

typescript
interface CausalChainNode {
  snapshot: IContextSnapshot;
  causedBy: CausalChainNode | null;
  children: CausalChainNode[];
  depth: number;  // Distance from root (0 = root)
}

ActionType

typescript
type ActionType =
  | 'conversation'  // User dialogue
  | 'decision'      // Strategic choice
  | 'file_edit'     // Code modification
  | 'tool_use'      // External tool
  | 'research';     // Information gathering

Usage Examples

Build and Display Chain

typescript
const chain = await causalityService.buildCausalChain('decision-id');

console.log(`Chain has ${chain.length} steps:`);
for (const node of chain) {
  const indent = '  '.repeat(node.depth);
  console.log(`${indent}- ${node.snapshot.causality?.actionType}: ${node.snapshot.summary}`);
}

// Output:
// Chain has 3 steps:
// - conversation: User asks about authentication
//   - research: OAuth2 best practices research
//     - decision: Use OAuth2 with PKCE

Find Root Causes

typescript
const stats = await causalityService.getCausalityStats('my-project');
console.log(`Project has ${stats.rootCauses} root causes`);

// Root causes are entry points - user questions, external events

Explain Decision

typescript
const reasoning = await causalityService.reconstructReasoning('decision-id');
console.log(reasoning);

// Shows:
// - What type of action created it
// - Why it was created (rationale)
// - What the context is about (summary)

Best Practices

1. Always Set causedBy When Relevant

typescript
// ✅ Good: Links to parent
save_context({
  content: 'Implementation...',
  metadata: {
    causedBy: decisionId,
    actionType: 'file_edit'
  }
});

// ❌ Bad: Orphaned context
save_context({
  content: 'Implementation...'
});

2. Use Appropriate Action Types

Choose the type that best describes what happened:

  • User asked question → conversation
  • You researched → research
  • You made choice → decision
  • You wrote code → file_edit
  • You ran tool → tool_use

3. Provide Clear Rationale

typescript
// ✅ Good: Clear reasoning
metadata: {
  rationale: 'OAuth2 with PKCE prevents auth code interception on mobile'
}

// ❌ Bad: Vague
metadata: {
  rationale: 'Better security'
}

Testing

Unit Test Example

typescript
describe('CausalityService', () => {
  it('builds 3-level causal chain', async () => {
    const mockRepo = {
      findById: jest.fn()
        .mockResolvedValueOnce({
          id: '3',
          causality: { causedBy: '2' }
        })
        .mockResolvedValueOnce({
          id: '2',
          causality: { causedBy: '1' }
        })
        .mockResolvedValueOnce({
          id: '1',
          causality: { causedBy: null }  // Root
        })
    };

    const service = new CausalityService(mockRepo);
    const chain = await service.buildCausalChain('3');

    expect(chain).toHaveLength(3);
    expect(chain[0].snapshot.id).toBe('1');  // Root first
    expect(chain[2].snapshot.id).toBe('3');  // Target last
  });
});

Error Handling

Context Not Found

typescript
try {
  const chain = await causalityService.buildCausalChain('invalid-id');
} catch (error) {
  // Handle: Context doesn't exist or was pruned
}

Circular Chain Detection

The algorithm prevents infinite loops:

typescript
const visited = new Set<string>();

while (currentId && !visited.has(currentId)) {
  visited.add(currentId);
  // Process...
}

If a circular reference exists, the chain stops at the cycle point.


Integration with Other Layers

With Layer 2 (Memory)

Causal position affects memory importance:

  • Root causes often stay in ACTIVE/RECENT tiers longer
  • Leaf nodes may be archived faster

With Layer 3 (Propagation)

Causal position affects prediction score:

  • Root causes get +0.3 score boost
  • Decision nodes get +0.2 boost
  • Branch points get +0.1 boost

See Also