Skip to content

Commit eb44640

Browse files
author
Z User
committed
docs: comprehensive README enhancement with architecture and API reference
1 parent aee7479 commit eb44640

7 files changed

Lines changed: 982 additions & 67 deletions

File tree

README.md

Lines changed: 720 additions & 66 deletions
Large diffs are not rendered by default.

callsign1.jpg

89.8 KB
Loading

src/engine/budget.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,31 @@
1+
/**
2+
* BudgetTracker — API call and token budget management.
3+
*
4+
* Enforces daily and total limits on LLM API usage. Supports two strategies:
5+
* - 'accumulate': daily counters reset but unused calls are conceptually saved
6+
* - 'reset': fresh daily budget, no rollover
7+
*
8+
* When maxCallsPerDay is 0, the budget is treated as unlimited (returns Infinity).
9+
* The tracker auto-rolls the date each time canAffordCall or recordCall is called,
10+
* so it handles overnight sessions correctly.
11+
*
12+
* Usage:
13+
* const budget = new BudgetTracker({ provider: 'openai', maxCallsPerDay: 50, budgetStrategy: 'accumulate' });
14+
* budget.recordCall(250);
15+
* console.log(budget.remaining); // 49
16+
* console.log(budget.canAffordCall); // true
17+
*/
18+
119
import type { BudgetConfig, BudgetState } from '../types.js';
220

321
export class BudgetTracker {
422
private state: BudgetState;
523
private config: BudgetConfig;
624

25+
/**
26+
* Create a new BudgetTracker with fresh state.
27+
* @param config - Budget configuration (provider, daily limits, strategy)
28+
*/
729
constructor(config: BudgetConfig) {
830
this.config = config;
931
this.state = {
@@ -16,16 +38,19 @@ export class BudgetTracker {
1638
};
1739
}
1840

41+
/** Remaining calls today. Returns Infinity when maxCallsPerDay is 0 (unlimited). */
1942
get remaining(): number {
2043
if (this.config.maxCallsPerDay === 0) return Infinity;
2144
return Math.max(0, this.config.maxCallsPerDay - this.state.callsToday);
2245
}
2346

47+
/** Whether a new API call can be made. Auto-rolls the date if needed. */
2448
get canAffordCall(): boolean {
2549
this.rollDate();
2650
return this.remaining > 0;
2751
}
2852

53+
/** Record an API call and its token usage. Auto-rolls the date if needed. */
2954
recordCall(tokensUsed: number): void {
3055
this.rollDate();
3156
this.state.callsToday++;
@@ -35,10 +60,16 @@ export class BudgetTracker {
3560
this.state.lastCallAt = new Date().toISOString();
3661
}
3762

63+
/** Get a snapshot of the current budget state (shallow copy). */
3864
getState(): BudgetState {
3965
return { ...this.state };
4066
}
4167

68+
/**
69+
* Internal: roll over to a new day if the date has changed.
70+
* Resets daily counters (callsToday, tokensToday) regardless of strategy.
71+
* The 'accumulate' strategy is handled at a higher level by the agent.
72+
*/
4273
private rollDate(): void {
4374
const today = new Date().toISOString().slice(0, 10);
4475
if (this.state.budgetDate !== today) {
@@ -56,10 +87,12 @@ export class BudgetTracker {
5687
}
5788
}
5889

90+
/** Serialize budget state to JSON for persistence. */
5991
toJSON(): BudgetState {
6092
return this.getState();
6193
}
6294

95+
/** Restore a BudgetTracker from a previously serialized state. */
6396
static fromJSON(config: BudgetConfig, state: BudgetState): BudgetTracker {
6497
const bt = new BudgetTracker(config);
6598
bt.state = { ...state };

src/engine/strategies.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,41 @@
1+
/**
2+
* Thinking Strategies — Five complementary approaches to idea exploration.
3+
*
4+
* Each strategy receives the current knowledge tensor and optional context,
5+
* then returns structured output: content text, connections to prior themes,
6+
* open questions, and a confidence score (0.0 – 1.0).
7+
*
8+
* The strategies are designed to work in concert:
9+
* explore -> builds breadth (new angles)
10+
* connect -> builds bridges (between ideas)
11+
* contradict -> stress-tests (finds tensions)
12+
* synthesize -> builds depth (finds patterns)
13+
* question -> meta-checks (validates approach)
14+
*/
15+
116
import type { ThinkingStrategy, Thought, KnowledgeTensor } from '../types.js';
217

18+
/** Result produced by a single strategy execution. */
319
export interface StrategyResult {
20+
/** The main thought content (markdown text) */
421
content: string;
22+
/** References to prior topics or themes */
523
connections: string[];
24+
/** New open questions raised by this thought */
625
questions: string[];
26+
/** Confidence score from 0.0 (uncertain) to 1.0 (highly confident) */
727
confidence: number;
828
}
929

30+
/**
31+
* Execute a thinking strategy against the current knowledge tensor.
32+
* Dispatches to the appropriate strategy function based on the strategy name.
33+
*
34+
* @param strategy - Which thinking strategy to use
35+
* @param tensor - The current knowledge tensor (all prior thoughts)
36+
* @param contextSummary - Optional project context summary for grounding
37+
* @returns A StrategyResult with content, connections, questions, and confidence
38+
*/
1039
export function executeStrategy(
1140
strategy: ThinkingStrategy,
1241
tensor: KnowledgeTensor,
@@ -21,6 +50,13 @@ export function executeStrategy(
2150
}
2251
}
2352

53+
/**
54+
* EXPLORE: Breadth-first search for new angles.
55+
*
56+
* Maintains a pool of 10 candidate angles (historical origins, cross-domain
57+
* applications, failure modes, etc.) and picks the first unexplored one.
58+
* This ensures diverse coverage of the topic space over time.
59+
*/
2460
function explore(tensor: KnowledgeTensor, _ctx: string): StrategyResult {
2561
const existingTopics = tensor.thoughts.map(t => t.topic);
2662
const explored = new Set(existingTopics);
@@ -56,6 +92,13 @@ function explore(tensor: KnowledgeTensor, _ctx: string): StrategyResult {
5692
};
5793
}
5894

95+
/**
96+
* CONNECT: Find conceptual bridges between existing thoughts.
97+
*
98+
* Randomly selects two prior thoughts and identifies the intersection
99+
* between their topics. Requires at least 2 thoughts to produce meaningful output;
100+
* otherwise returns a low-confidence "building foundation" result.
101+
*/
59102
function connect(tensor: KnowledgeTensor, _ctx: string): StrategyResult {
60103
const thoughts = tensor.thoughts;
61104
if (thoughts.length < 2) {
@@ -86,6 +129,13 @@ function connect(tensor: KnowledgeTensor, _ctx: string): StrategyResult {
86129
};
87130
}
88131

132+
/**
133+
* CONTRADICT: Stress-test assumptions by finding tensions.
134+
*
135+
* Sorts all thoughts by confidence and contrasts the highest-confidence
136+
* thought with the lowest-confidence one. The gap may reveal blind spots
137+
* or genuine paradoxes in the exploration.
138+
*/
89139
function contradict(tensor: KnowledgeTensor, _ctx: string): StrategyResult {
90140
const thoughts = tensor.thoughts;
91141

@@ -119,6 +169,14 @@ function contradict(tensor: KnowledgeTensor, _ctx: string): StrategyResult {
119169
};
120170
}
121171

172+
/**
173+
* SYNTHESIZE: Identify recurring themes and emerging patterns.
174+
*
175+
* Counts connection frequencies across all thoughts to find the top 3
176+
* recurring themes, then produces a synthesis that assesses whether the
177+
* exploration is still in an exploratory phase or developing clear structure.
178+
* Confidence grows with thought count (capped at 0.90).
179+
*/
122180
function synthesize(tensor: KnowledgeTensor, _ctx: string): StrategyResult {
123181
const thoughts = tensor.thoughts;
124182
const themes = new Map<string, number>();
@@ -151,6 +209,13 @@ function synthesize(tensor: KnowledgeTensor, _ctx: string): StrategyResult {
151209
};
152210
}
153211

212+
/**
213+
* QUESTION: Meta-cognitive check to validate the exploration approach.
214+
*
215+
* Uses a pool of 5 meta-questions ("What are we not asking?", "What would
216+
* a child ask?", etc.) to challenge assumptions. References the current
217+
* count of open questions to gauge whether too many are piling up.
218+
*/
154219
function question(tensor: KnowledgeTensor, _ctx: string): StrategyResult {
155220
const unanswered = tensor.openQuestions.slice(0, 5);
156221
const metaQuestions = [

src/engine/thinker.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
1+
/**
2+
* Thinker — Main orchestration engine for Murmur-Agent.
3+
*
4+
* The Thinker manages the full thinking lifecycle: strategy selection,
5+
* thought generation, budget enforcement, tensor updates, and output
6+
* persistence. It can run a single thought via think() or all thoughts
7+
* sequentially via runAll().
8+
*
9+
* The Thinker cycles through configured strategies in order, with a 20%
10+
* chance of repeating the previous strategy for deeper exploration.
11+
*
12+
* Session state can be saved to and loaded from JSON for persistence
13+
* across restarts (critical for long-running overnight sessions).
14+
*
15+
* Usage:
16+
* const thinker = new Thinker(config);
17+
* thinker.setContext('project context...');
18+
* await thinker.runAll();
19+
* console.log(thinker.getTensor().thoughts.length);
20+
*/
21+
122
import type { MurmurConfig, Thought, KnowledgeTensor, ThinkingStrategy } from '../types.js';
223
import { BudgetTracker } from './budget.js';
324
import { executeStrategy } from './strategies.js';
@@ -13,6 +34,11 @@ export class Thinker {
1334
private thoughtCount: number = 0;
1435
private contextSummary: string = '';
1536

37+
/**
38+
* Create a new Thinker instance.
39+
* Initializes the knowledge tensor, budget tracker, and output writer.
40+
* @param config - Full Murmur configuration (topic, strategies, budget, etc.)
41+
*/
1642
constructor(config: MurmurConfig) {
1743
this.config = config;
1844
this.budget = new BudgetTracker(config.budget);
@@ -29,18 +55,28 @@ export class Thinker {
2955
};
3056
}
3157

58+
/** Set an optional project context summary to ground the thinking strategies. */
3259
setContext(summary: string): void {
3360
this.contextSummary = summary;
3461
}
3562

63+
/** Get a shallow copy of the current knowledge tensor. */
3664
getTensor(): KnowledgeTensor {
3765
return { ...this.tensor };
3866
}
3967

68+
/** Get the budget tracker for inspection. */
4069
getBudget(): BudgetTracker {
4170
return this.budget;
4271
}
4372

73+
/**
74+
* Generate a single thought.
75+
* Picks a strategy, executes it, records budget usage, updates the tensor,
76+
* writes the thought to disk, and persists the tensor snapshot.
77+
*
78+
* @returns The generated Thought, or null if max thoughts reached or budget exhausted
79+
*/
4480
async think(): Promise<Thought | null> {
4581
if (this.thoughtCount >= this.config.thinking.maxThoughts) {
4682
return null;
@@ -88,6 +124,13 @@ export class Thinker {
88124
return thought;
89125
}
90126

127+
/**
128+
* Run all thoughts until maxThoughts or budget is exhausted.
129+
* Respects the configured interval between thoughts.
130+
* Generates a SUMMARY.md when complete if autoSummary is enabled.
131+
*
132+
* @returns The number of thoughts generated
133+
*/
91134
async runAll(): Promise<number> {
92135
let count = 0;
93136
while (this.thoughtCount < this.config.thinking.maxThoughts && this.budget.canAffordCall) {
@@ -108,6 +151,11 @@ export class Thinker {
108151
return count;
109152
}
110153

154+
/**
155+
* Pick the next strategy from the configured list.
156+
* Cycles through strategies in order, with a 20% chance of
157+
* repeating the last strategy for deeper exploration of a single angle.
158+
*/
111159
private pickStrategy(): ThinkingStrategy {
112160
const strategies = this.config.thinking.strategies;
113161
// Cycle through strategies, but occasionally repeat the last one for depth
@@ -119,6 +167,7 @@ export class Thinker {
119167
return strategies[idx];
120168
}
121169

170+
/** Save the current session state (tensor, budget, thought count) to a JSON file. */
122171
saveState(filePath: string): void {
123172
fs.writeFileSync(filePath, JSON.stringify({
124173
tensor: this.tensor,
@@ -127,6 +176,7 @@ export class Thinker {
127176
}, null, 2));
128177
}
129178

179+
/** Restore session state from a previously saved JSON file. Ignores missing files. */
130180
loadState(filePath: string): void {
131181
if (!fs.existsSync(filePath)) return;
132182
const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));

src/output/writer.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
/**
2+
* OutputWriter — File system persistence for Murmur thoughts and knowledge tensors.
3+
*
4+
* Handles writing thoughts, tensor snapshots, and summaries to disk in
5+
* markdown, JSON, or both formats. Files are written to a configurable
6+
* output directory (default: murmur-output/) and follow a zero-padded
7+
* naming convention (e.g., 001-explore.md, 002-connect.json).
8+
*
9+
* Usage:
10+
* const writer = new OutputWriter('./murmur-output', 'both');
11+
* writer.writeThought(thought); // -> [path/to/001-explore.md, path/to/001-explore.json]
12+
* writer.writeTensor(tensor); // -> path/to/tensor.json
13+
* writer.writeSummary(tensor); // -> path/to/SUMMARY.md
14+
*/
15+
116
import * as fs from 'fs';
217
import * as path from 'path';
318
import type { Thought, KnowledgeTensor } from '../types.js';
@@ -6,12 +21,25 @@ export class OutputWriter {
621
private outputDir: string;
722
private format: 'markdown' | 'json' | 'both';
823

24+
/**
25+
* Create an OutputWriter. The output directory is created recursively if it doesn't exist.
26+
* @param outputDir - Directory path for all output files
27+
* @param format - Output format: 'markdown', 'json', or 'both'
28+
*/
929
constructor(outputDir: string, format: 'markdown' | 'json' | 'both') {
1030
this.outputDir = outputDir;
1131
this.format = format;
1232
fs.mkdirSync(outputDir, { recursive: true });
1333
}
1434

35+
/**
36+
* Write a single thought to disk.
37+
* File naming: {zero-padded-id}-{strategy}.{ext}
38+
* e.g., 001-explore.md, 001-explore.json
39+
*
40+
* @param thought - The thought to persist
41+
* @returns Array of file paths that were written
42+
*/
1543
writeThought(thought: Thought): string[] {
1644
const files: string[] = [];
1745
const padded = String(thought.id).padStart(3, '0');
@@ -54,12 +82,27 @@ export class OutputWriter {
5482
return files;
5583
}
5684

85+
/**
86+
* Serialize the full knowledge tensor to a JSON file.
87+
* This is called after every thought to maintain a live snapshot.
88+
*
89+
* @param tensor - The knowledge tensor to serialize
90+
* @returns Path to the written tensor.json file
91+
*/
5792
writeTensor(tensor: KnowledgeTensor): string {
5893
const tensorPath = path.join(this.outputDir, 'tensor.json');
5994
fs.writeFileSync(tensorPath, JSON.stringify(tensor, null, 2));
6095
return tensorPath;
6196
}
6297

98+
/**
99+
* Generate a human-readable SUMMARY.md from the knowledge tensor.
100+
* Includes thought clusters, contradictions, open questions,
101+
* and confidence distribution across all thoughts.
102+
*
103+
* @param tensor - The knowledge tensor to summarize
104+
* @returns Path to the written SUMMARY.md file
105+
*/
63106
writeSummary(tensor: KnowledgeTensor): string {
64107
const summaryPath = path.join(this.outputDir, 'SUMMARY.md');
65108
const content = [

0 commit comments

Comments
 (0)