@ouro.bot/cli 0.1.0-alpha.137 → 0.1.0-alpha.138

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/changelog.json CHANGED
@@ -1,6 +1,19 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.138",
6
+ "changes": [
7
+ "Memory renamed to diary: memory.ts → diary.ts, MemoryFact → DiaryEntry, memory_save → diary_write, memory_search → recall. All types, functions, events, and variables renamed throughout.",
8
+ "Diary path: diary/ (top-level) replaces psyche/memory/. Fallback reads from legacy path for existing bundles.",
9
+ "Journal workspace: journal/ directory for freeform thinking-in-progress. Agent writes with write_file, system reads for heartbeat context.",
10
+ "Unified recall tool: searches both diary entries and journal files. Results tagged [diary] or [journal].",
11
+ "Journal embeddings: file-level embeddings indexed during heartbeat via journal/.index.json sidecar.",
12
+ "Journal section in inner dialog system prompt: index of up to 10 most recently modified journal files with name, recency, and first-line preview.",
13
+ "Metacognitive framing updated: diary (record), journal (workspace), ponder/rest vocabulary, morning briefing encouragement.",
14
+ "Session migration: memory_save → diary_write, memory_search → recall added to migrateToolNames()."
15
+ ]
16
+ },
4
17
  {
5
18
  "version": "0.1.0-alpha.137",
6
19
  "changes": [
@@ -142,12 +142,12 @@ function writeFriendImprint(bundleRoot, humanName, now) {
142
142
  };
143
143
  fs.writeFileSync(path.join(friendsDir, `${id}.json`), `${JSON.stringify(record, null, 2)}\n`, "utf-8");
144
144
  }
145
- function writeMemoryScaffold(bundleRoot) {
146
- const memoryRoot = path.join(bundleRoot, "psyche", "memory");
147
- fs.mkdirSync(path.join(memoryRoot, "daily"), { recursive: true });
148
- fs.mkdirSync(path.join(memoryRoot, "archive"), { recursive: true });
149
- fs.writeFileSync(path.join(memoryRoot, "facts.jsonl"), "", "utf-8");
150
- fs.writeFileSync(path.join(memoryRoot, "entities.json"), "{}\n", "utf-8");
145
+ function writeDiaryScaffold(bundleRoot) {
146
+ const diaryRoot = path.join(bundleRoot, "diary");
147
+ fs.mkdirSync(path.join(diaryRoot, "daily"), { recursive: true });
148
+ fs.mkdirSync(path.join(diaryRoot, "archive"), { recursive: true });
149
+ fs.writeFileSync(path.join(diaryRoot, "facts.jsonl"), "", "utf-8");
150
+ fs.writeFileSync(path.join(diaryRoot, "entities.json"), "{}\n", "utf-8");
151
151
  }
152
152
  function writeHatchlingAgentConfig(bundleRoot, input) {
153
153
  const template = (0, identity_1.buildDefaultAgentTemplate)(input.agentName);
@@ -183,7 +183,7 @@ async function runHatchFlow(input, deps = {}) {
183
183
  fs.mkdirSync(bundleRoot, { recursive: true });
184
184
  writeReadme(bundleRoot, "Root of this agent bundle.");
185
185
  writeReadme(path.join(bundleRoot, "psyche"), "Identity and behavior files.");
186
- writeReadme(path.join(bundleRoot, "psyche", "memory"), "Persistent memory store.");
186
+ writeReadme(path.join(bundleRoot, "diary"), "Persistent diary — things I've learned and remember.");
187
187
  writeReadme(path.join(bundleRoot, "friends"), "Known friend records.");
188
188
  writeReadme(path.join(bundleRoot, "tasks"), "Task files.");
189
189
  writeReadme(path.join(bundleRoot, "tasks", "habits"), "Recurring tasks.");
@@ -193,7 +193,7 @@ async function runHatchFlow(input, deps = {}) {
193
193
  writeReadme(path.join(bundleRoot, "senses"), "Sense-specific config.");
194
194
  writeReadme(path.join(bundleRoot, "senses", "teams"), "Teams sense config.");
195
195
  writeHatchlingAgentConfig(bundleRoot, input);
196
- writeMemoryScaffold(bundleRoot);
196
+ writeDiaryScaffold(bundleRoot);
197
197
  writeFriendImprint(bundleRoot, input.humanName, now);
198
198
  writeHeartbeatTask(bundleRoot, now);
199
199
  (0, runtime_1.emitNervesEvent)({
@@ -35,12 +35,13 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.cosineSimilarity = cosineSimilarity;
37
37
  exports.recallFactsForQuery = recallFactsForQuery;
38
+ exports.searchJournalIndex = searchJournalIndex;
38
39
  exports.injectAssociativeRecall = injectAssociativeRecall;
39
40
  const fs = __importStar(require("fs"));
40
41
  const path = __importStar(require("path"));
41
42
  const config_1 = require("../heart/config");
42
- const identity_1 = require("../heart/identity");
43
43
  const runtime_1 = require("../nerves/runtime");
44
+ const diary_1 = require("./diary");
44
45
  const DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small";
45
46
  const DEFAULT_MIN_SCORE = 0.5;
46
47
  const DEFAULT_TOP_K = 3;
@@ -80,8 +81,8 @@ function createDefaultProvider() {
80
81
  }
81
82
  return new OpenAIEmbeddingProvider(apiKey);
82
83
  }
83
- function readFacts(memoryRoot) {
84
- const factsPath = path.join(memoryRoot, "facts.jsonl");
84
+ function readFacts(diaryRoot) {
85
+ const factsPath = path.join(diaryRoot, "facts.jsonl");
85
86
  if (!fs.existsSync(factsPath))
86
87
  return [];
87
88
  const raw = fs.readFileSync(factsPath, "utf8").trim();
@@ -145,6 +146,40 @@ async function recallFactsForQuery(query, facts, provider, options) {
145
146
  .sort((left, right) => right.score - left.score)
146
147
  .slice(0, topK);
147
148
  }
149
+ function readJournalIndex(journalDir) {
150
+ const indexPath = path.join(journalDir, ".index.json");
151
+ try {
152
+ const raw = fs.readFileSync(indexPath, "utf8");
153
+ const parsed = JSON.parse(raw);
154
+ if (!Array.isArray(parsed))
155
+ return [];
156
+ return parsed;
157
+ }
158
+ catch {
159
+ return [];
160
+ }
161
+ }
162
+ function searchJournalIndex(queryEmbedding, entries, options) {
163
+ const minScore = options?.minScore ?? DEFAULT_MIN_SCORE;
164
+ const topK = options?.topK ?? DEFAULT_TOP_K;
165
+ return entries
166
+ .filter((entry) => Array.isArray(entry.embedding) && entry.embedding.length > 0)
167
+ .map((entry) => ({
168
+ filename: entry.filename,
169
+ preview: entry.preview,
170
+ score: cosineSimilarity(queryEmbedding, entry.embedding),
171
+ }))
172
+ .filter((entry) => entry.score >= minScore)
173
+ .sort((left, right) => right.score - left.score)
174
+ .slice(0, topK);
175
+ }
176
+ function resolveJournalDir(diaryRoot, explicitJournalDir) {
177
+ if (explicitJournalDir)
178
+ return explicitJournalDir;
179
+ // journal/ is a sibling of diary/ at the agent root level
180
+ const agentRoot = path.dirname(diaryRoot);
181
+ return path.join(agentRoot, "journal");
182
+ }
148
183
  async function injectAssociativeRecall(messages, options) {
149
184
  try {
150
185
  if (messages[0]?.role !== "system" || typeof messages[0].content !== "string")
@@ -152,37 +187,80 @@ async function injectAssociativeRecall(messages, options) {
152
187
  const query = getLatestUserText(messages);
153
188
  if (!query)
154
189
  return;
155
- const memoryRoot = options?.memoryRoot ?? path.join((0, identity_1.getAgentRoot)(), "psyche", "memory");
156
- const facts = readFacts(memoryRoot);
157
- if (facts.length === 0)
190
+ const diaryRoot = options?.diaryRoot ?? (0, diary_1.resolveDiaryRoot)();
191
+ const facts = readFacts(diaryRoot);
192
+ const journalDir = resolveJournalDir(diaryRoot, options?.journalDir);
193
+ const journalEntries = readJournalIndex(journalDir);
194
+ if (facts.length === 0 && journalEntries.length === 0)
158
195
  return;
159
- let recalled;
160
- try {
161
- const provider = options?.provider ?? createDefaultProvider();
162
- recalled = await recallFactsForQuery(query, facts, provider, options);
163
- }
164
- catch {
165
- // Embeddings unavailable — fall back to substring matching
166
- const lowerQuery = query.toLowerCase();
167
- const topK = options?.topK ?? DEFAULT_TOP_K;
168
- recalled = facts
169
- .filter((fact) => fact.text.toLowerCase().includes(lowerQuery))
170
- .slice(0, topK)
171
- .map((fact) => ({ ...fact, score: 1 }));
172
- if (recalled.length > 0) {
173
- (0, runtime_1.emitNervesEvent)({
174
- level: "warn",
175
- component: "mind",
176
- event: "mind.associative_recall_fallback",
177
- message: "embeddings unavailable, used substring fallback",
178
- meta: { matchCount: recalled.length },
196
+ // Build combined result lines tagged by source
197
+ const resultLines = [];
198
+ let queryEmbedding;
199
+ // Search diary entries
200
+ if (facts.length > 0) {
201
+ let recalled;
202
+ try {
203
+ const provider = options?.provider ?? createDefaultProvider();
204
+ recalled = await recallFactsForQuery(query, facts, provider, options);
205
+ // Compute query embedding for journal search while provider is available
206
+ if (journalEntries.length > 0) {
207
+ const [qe] = await provider.embed([query.trim()]);
208
+ queryEmbedding = qe;
209
+ }
210
+ }
211
+ catch {
212
+ // Embeddings unavailable — fall back to substring matching
213
+ const lowerQuery = query.toLowerCase();
214
+ const topK = options?.topK ?? DEFAULT_TOP_K;
215
+ recalled = facts
216
+ .filter((fact) => fact.text.toLowerCase().includes(lowerQuery))
217
+ .slice(0, topK)
218
+ .map((fact) => ({ ...fact, score: 1 }));
219
+ if (recalled.length > 0) {
220
+ (0, runtime_1.emitNervesEvent)({
221
+ level: "warn",
222
+ component: "mind",
223
+ event: "mind.associative_recall_fallback",
224
+ message: "embeddings unavailable, used substring fallback",
225
+ meta: { matchCount: recalled.length },
226
+ });
227
+ }
228
+ }
229
+ for (const fact of recalled) {
230
+ resultLines.push({
231
+ text: `[diary] ${fact.text} [score=${fact.score.toFixed(3)} source=${fact.source}]`,
232
+ score: fact.score,
179
233
  });
180
234
  }
181
235
  }
182
- if (recalled.length === 0)
236
+ // Search journal entries (works whether diary had results or not)
237
+ if (journalEntries.length > 0) {
238
+ try {
239
+ if (!queryEmbedding) {
240
+ const provider = options?.provider ?? createDefaultProvider();
241
+ const [qe] = await provider.embed([query.trim()]);
242
+ queryEmbedding = qe;
243
+ }
244
+ if (queryEmbedding) {
245
+ const journalResults = searchJournalIndex(queryEmbedding, journalEntries, options);
246
+ for (const entry of journalResults) {
247
+ resultLines.push({
248
+ text: `[journal] ${entry.filename}: ${entry.preview} [score=${entry.score.toFixed(3)}]`,
249
+ score: entry.score,
250
+ });
251
+ }
252
+ }
253
+ }
254
+ catch {
255
+ // Embeddings unavailable — no journal fallback
256
+ }
257
+ }
258
+ if (resultLines.length === 0)
183
259
  return;
184
- const recallSection = recalled
185
- .map((fact, index) => `${index + 1}. ${fact.text} [score=${fact.score.toFixed(3)} source=${fact.source}]`)
260
+ // Sort all results by score descending
261
+ resultLines.sort((left, right) => right.score - left.score);
262
+ const recallSection = resultLines
263
+ .map((entry, index) => `${index + 1}. ${entry.text}`)
186
264
  .join("\n");
187
265
  messages[0] = {
188
266
  role: "system",
@@ -192,7 +270,7 @@ async function injectAssociativeRecall(messages, options) {
192
270
  component: "mind",
193
271
  event: "mind.associative_recall",
194
272
  message: "associative recall injected",
195
- meta: { count: recalled.length },
273
+ meta: { count: resultLines.length },
196
274
  });
197
275
  }
198
276
  catch (error) {
@@ -52,6 +52,8 @@ exports.CANONICAL_BUNDLE_MANIFEST = [
52
52
  { path: "psyche/LORE.md", kind: "file" },
53
53
  { path: "psyche/TACIT.md", kind: "file" },
54
54
  { path: "psyche/ASPIRATIONS.md", kind: "file" },
55
+ { path: "diary", kind: "dir" },
56
+ { path: "journal", kind: "dir" },
55
57
  { path: "psyche/memory", kind: "dir" },
56
58
  { path: "friends", kind: "dir" },
57
59
  { path: "state", kind: "dir" },
@@ -264,6 +264,8 @@ const TOOL_NAME_MIGRATIONS = {
264
264
  final_answer: "settle",
265
265
  no_response: "observe",
266
266
  go_inward: "descend",
267
+ memory_save: "diary_write",
268
+ memory_search: "recall",
267
269
  };
268
270
  function migrateToolNames(messages) {
269
271
  let migrated = 0;
@@ -33,12 +33,13 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.ensureMemoryStorePaths = ensureMemoryStorePaths;
37
- exports.appendFactsWithDedup = appendFactsWithDedup;
38
- exports.readMemoryFacts = readMemoryFacts;
39
- exports.saveMemoryFact = saveMemoryFact;
36
+ exports.ensureDiaryStorePaths = ensureDiaryStorePaths;
37
+ exports.appendEntriesWithDedup = appendEntriesWithDedup;
38
+ exports.resolveDiaryRoot = resolveDiaryRoot;
39
+ exports.readDiaryEntries = readDiaryEntries;
40
+ exports.saveDiaryEntry = saveDiaryEntry;
40
41
  exports.backfillEmbeddings = backfillEmbeddings;
41
- exports.searchMemoryFacts = searchMemoryFacts;
42
+ exports.searchDiaryEntries = searchDiaryEntries;
42
43
  const fs = __importStar(require("fs"));
43
44
  const path = __importStar(require("path"));
44
45
  const crypto_1 = require("crypto");
@@ -79,7 +80,7 @@ class OpenAIEmbeddingProvider {
79
80
  return payload.data.map((entry) => entry.embedding);
80
81
  }
81
82
  }
82
- function ensureMemoryStorePaths(rootDir) {
83
+ function ensureDiaryStorePaths(rootDir) {
83
84
  const factsPath = path.join(rootDir, "facts.jsonl");
84
85
  const entitiesPath = path.join(rootDir, "entities.json");
85
86
  const dailyDir = path.join(rootDir, "daily");
@@ -91,8 +92,8 @@ function ensureMemoryStorePaths(rootDir) {
91
92
  fs.writeFileSync(entitiesPath, "{}\n", "utf8");
92
93
  (0, runtime_1.emitNervesEvent)({
93
94
  component: "mind",
94
- event: "mind.memory_paths_ready",
95
- message: "memory store paths ready",
95
+ event: "mind.diary_paths_ready",
96
+ message: "diary store paths ready",
96
97
  meta: { rootDir },
97
98
  });
98
99
  return { rootDir, factsPath, entitiesPath, dailyDir };
@@ -115,7 +116,7 @@ function overlapScore(left, right) {
115
116
  }
116
117
  return common / Math.min(leftWords.size, rightWords.size);
117
118
  }
118
- function readExistingFacts(factsPath) {
119
+ function readExistingEntries(factsPath) {
119
120
  if (!fs.existsSync(factsPath))
120
121
  return [];
121
122
  const raw = fs.readFileSync(factsPath, "utf8").trim();
@@ -178,8 +179,8 @@ function appendDailyFact(dailyDir, fact) {
178
179
  const dayPath = path.join(dailyDir, `${day}.jsonl`);
179
180
  fs.appendFileSync(dayPath, `${JSON.stringify(fact)}\n`, "utf8");
180
181
  }
181
- function appendFactsWithDedup(stores, incoming, options) {
182
- const existing = readExistingFacts(stores.factsPath);
182
+ function appendEntriesWithDedup(stores, incoming, options) {
183
+ const existing = readExistingEntries(stores.factsPath);
183
184
  const all = [...existing];
184
185
  let added = 0;
185
186
  let skipped = 0;
@@ -208,8 +209,8 @@ function appendFactsWithDedup(stores, incoming, options) {
208
209
  }
209
210
  (0, runtime_1.emitNervesEvent)({
210
211
  component: "mind",
211
- event: "mind.memory_write",
212
- message: "memory write completed",
212
+ event: "mind.diary_write",
213
+ message: "diary write completed",
213
214
  meta: { added, skipped },
214
215
  });
215
216
  return { added, skipped };
@@ -226,8 +227,8 @@ async function buildEmbedding(text, embeddingProvider) {
226
227
  (0, runtime_1.emitNervesEvent)({
227
228
  level: "warn",
228
229
  component: "mind",
229
- event: "mind.memory_embedding_unavailable",
230
- message: "embedding provider unavailable for memory write",
230
+ event: "mind.diary_embedding_unavailable",
231
+ message: "embedding provider unavailable for diary write",
231
232
  meta: { reason: "missing_openai_embeddings_key" },
232
233
  });
233
234
  return [];
@@ -240,8 +241,8 @@ async function buildEmbedding(text, embeddingProvider) {
240
241
  (0, runtime_1.emitNervesEvent)({
241
242
  level: "warn",
242
243
  component: "mind",
243
- event: "mind.memory_embedding_unavailable",
244
- message: "embedding provider unavailable for memory write",
244
+ event: "mind.diary_embedding_unavailable",
245
+ message: "embedding provider unavailable for diary write",
245
246
  meta: {
246
247
  reason: error instanceof Error ? error.message : String(error),
247
248
  },
@@ -249,13 +250,25 @@ async function buildEmbedding(text, embeddingProvider) {
249
250
  return [];
250
251
  }
251
252
  }
252
- function readMemoryFacts(memoryRoot = path.join((0, identity_1.getAgentRoot)(), "psyche", "memory")) {
253
- return readExistingFacts(path.join(memoryRoot, "facts.jsonl"));
253
+ function resolveDiaryRoot(explicitRoot) {
254
+ if (explicitRoot)
255
+ return explicitRoot;
256
+ const agentRoot = (0, identity_1.getAgentRoot)();
257
+ const diaryPath = path.join(agentRoot, "diary");
258
+ if (fs.existsSync(diaryPath))
259
+ return diaryPath;
260
+ const legacyPath = path.join(agentRoot, "psyche", "memory");
261
+ if (fs.existsSync(legacyPath))
262
+ return legacyPath;
263
+ return diaryPath; // default to new path (will be created on first write)
254
264
  }
255
- async function saveMemoryFact(options) {
265
+ function readDiaryEntries(diaryRoot) {
266
+ return readExistingEntries(path.join(resolveDiaryRoot(diaryRoot), "facts.jsonl"));
267
+ }
268
+ async function saveDiaryEntry(options) {
256
269
  const text = options.text.trim();
257
- const memoryRoot = options.memoryRoot ?? path.join((0, identity_1.getAgentRoot)(), "psyche", "memory");
258
- const stores = ensureMemoryStorePaths(memoryRoot);
270
+ const diaryRoot = resolveDiaryRoot(options.diaryRoot);
271
+ const stores = ensureDiaryStorePaths(diaryRoot);
259
272
  const embedding = await buildEmbedding(text, options.embeddingProvider);
260
273
  const fact = {
261
274
  id: options.idFactory ? options.idFactory() : (0, crypto_1.randomUUID)(),
@@ -265,14 +278,14 @@ async function saveMemoryFact(options) {
265
278
  createdAt: (options.now ?? (() => new Date()))().toISOString(),
266
279
  embedding,
267
280
  };
268
- return appendFactsWithDedup(stores, [fact], { semanticThreshold: SEMANTIC_DEDUP_THRESHOLD });
281
+ return appendEntriesWithDedup(stores, [fact], { semanticThreshold: SEMANTIC_DEDUP_THRESHOLD });
269
282
  }
270
283
  async function backfillEmbeddings(options) {
271
- const memoryRoot = options?.memoryRoot ?? path.join((0, identity_1.getAgentRoot)(), "psyche", "memory");
272
- const factsPath = path.join(memoryRoot, "facts.jsonl");
284
+ const diaryRoot = resolveDiaryRoot(options?.diaryRoot);
285
+ const factsPath = path.join(diaryRoot, "facts.jsonl");
273
286
  if (!fs.existsSync(factsPath))
274
287
  return { total: 0, backfilled: 0, failed: 0 };
275
- const facts = readExistingFacts(factsPath);
288
+ const facts = readExistingEntries(factsPath);
276
289
  const needsEmbedding = facts.filter((f) => !Array.isArray(f.embedding) || f.embedding.length === 0);
277
290
  if (needsEmbedding.length === 0)
278
291
  return { total: facts.length, backfilled: 0, failed: 0 };
@@ -281,7 +294,7 @@ async function backfillEmbeddings(options) {
281
294
  (0, runtime_1.emitNervesEvent)({
282
295
  level: "warn",
283
296
  component: "mind",
284
- event: "mind.memory_backfill_skipped",
297
+ event: "mind.diary_backfill_skipped",
285
298
  message: "embedding provider unavailable for backfill",
286
299
  meta: { needsEmbedding: needsEmbedding.length },
287
300
  });
@@ -307,7 +320,7 @@ async function backfillEmbeddings(options) {
307
320
  (0, runtime_1.emitNervesEvent)({
308
321
  level: "warn",
309
322
  component: "mind",
310
- event: "mind.memory_backfill_batch_error",
323
+ event: "mind.diary_backfill_batch_error",
311
324
  message: "embedding backfill batch failed",
312
325
  meta: {
313
326
  batchStart: i,
@@ -322,7 +335,7 @@ async function backfillEmbeddings(options) {
322
335
  fs.writeFileSync(factsPath, lines, "utf8");
323
336
  (0, runtime_1.emitNervesEvent)({
324
337
  component: "mind",
325
- event: "mind.memory_backfill_complete",
338
+ event: "mind.diary_backfill_complete",
326
339
  message: "embedding backfill completed",
327
340
  meta: { total: facts.length, backfilled, failed },
328
341
  });
@@ -342,7 +355,7 @@ function uniqueFacts(facts) {
342
355
  }
343
356
  return unique;
344
357
  }
345
- async function searchMemoryFacts(query, facts, embeddingProvider) {
358
+ async function searchDiaryEntries(query, facts, embeddingProvider) {
346
359
  const trimmed = query.trim();
347
360
  if (!trimmed)
348
361
  return [];
@@ -378,8 +391,8 @@ async function searchMemoryFacts(query, facts, embeddingProvider) {
378
391
  (0, runtime_1.emitNervesEvent)({
379
392
  level: "warn",
380
393
  component: "mind",
381
- event: "mind.memory_embedding_unavailable",
382
- message: "embedding provider unavailable for memory search",
394
+ event: "mind.diary_embedding_unavailable",
395
+ message: "embedding provider unavailable for diary search",
383
396
  meta: {
384
397
  reason: error instanceof Error ? error.message : String(error),
385
398
  },
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.indexJournalFiles = indexJournalFiles;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const runtime_1 = require("../nerves/runtime");
40
+ const TEXT_EXTENSIONS = new Set([".md", ".txt"]);
41
+ const PREVIEW_CHAR_LIMIT = 500;
42
+ function readExistingIndex(indexPath) {
43
+ try {
44
+ const raw = fs.readFileSync(indexPath, "utf8");
45
+ const parsed = JSON.parse(raw);
46
+ if (!Array.isArray(parsed))
47
+ return [];
48
+ return parsed;
49
+ }
50
+ catch {
51
+ return [];
52
+ }
53
+ }
54
+ function extractPreview(content) {
55
+ const trimmed = content.trim();
56
+ if (!trimmed)
57
+ return "";
58
+ return trimmed.split("\n")[0].replace(/^#+\s*/, "").trim();
59
+ }
60
+ async function indexJournalFiles(journalDir, indexPath, embedProvider) {
61
+ // Read existing index
62
+ const existingIndex = readExistingIndex(indexPath);
63
+ const indexMap = new Map();
64
+ for (const entry of existingIndex) {
65
+ indexMap.set(entry.filename, entry);
66
+ }
67
+ // Scan journal dir for text files
68
+ let dirEntries;
69
+ try {
70
+ dirEntries = fs.readdirSync(journalDir, { withFileTypes: true });
71
+ }
72
+ catch {
73
+ (0, runtime_1.emitNervesEvent)({
74
+ component: "mind",
75
+ event: "mind.journal_index_scan",
76
+ message: "journal dir not found or unreadable",
77
+ meta: { journalDir },
78
+ });
79
+ return 0;
80
+ }
81
+ const textFiles = dirEntries.filter((entry) => {
82
+ if (!entry.isFile())
83
+ return false;
84
+ if (entry.name.startsWith("."))
85
+ return false;
86
+ const ext = path.extname(entry.name).toLowerCase();
87
+ return TEXT_EXTENSIONS.has(ext);
88
+ });
89
+ if (textFiles.length === 0) {
90
+ (0, runtime_1.emitNervesEvent)({
91
+ component: "mind",
92
+ event: "mind.journal_index_scan",
93
+ message: "no text files found in journal",
94
+ meta: { journalDir },
95
+ });
96
+ return 0;
97
+ }
98
+ let newlyIndexed = 0;
99
+ for (const file of textFiles) {
100
+ const filePath = path.join(journalDir, file.name);
101
+ let stat;
102
+ try {
103
+ stat = fs.statSync(filePath);
104
+ }
105
+ catch {
106
+ /* v8 ignore next -- filesystem race: file deleted between readdir and stat @preserve */
107
+ continue;
108
+ }
109
+ // Check if already indexed with same mtime
110
+ const existing = indexMap.get(file.name);
111
+ if (existing && existing.mtime === stat.mtimeMs) {
112
+ continue;
113
+ }
114
+ // Read content for embedding
115
+ let content;
116
+ try {
117
+ content = fs.readFileSync(filePath, "utf8");
118
+ }
119
+ catch {
120
+ /* v8 ignore next -- filesystem race: file deleted between stat and read @preserve */
121
+ continue;
122
+ }
123
+ const preview = extractPreview(content);
124
+ const embedText = content.slice(0, PREVIEW_CHAR_LIMIT);
125
+ // Generate embedding
126
+ let embedding;
127
+ try {
128
+ const vectors = await embedProvider.embed([embedText]);
129
+ embedding = vectors[0] ?? [];
130
+ }
131
+ catch {
132
+ (0, runtime_1.emitNervesEvent)({
133
+ level: "warn",
134
+ component: "mind",
135
+ event: "mind.journal_embedding_error",
136
+ message: "embedding failed for journal file",
137
+ meta: { filename: file.name },
138
+ });
139
+ embedding = [];
140
+ }
141
+ indexMap.set(file.name, {
142
+ filename: file.name,
143
+ embedding,
144
+ mtime: stat.mtimeMs,
145
+ preview,
146
+ });
147
+ newlyIndexed++;
148
+ }
149
+ // Write updated index back
150
+ if (newlyIndexed > 0) {
151
+ const updatedIndex = Array.from(indexMap.values());
152
+ fs.writeFileSync(indexPath, JSON.stringify(updatedIndex, null, 2), "utf8");
153
+ }
154
+ (0, runtime_1.emitNervesEvent)({
155
+ component: "mind",
156
+ event: "mind.journal_index_complete",
157
+ message: "journal indexing complete",
158
+ meta: { journalDir, newlyIndexed, total: indexMap.size },
159
+ });
160
+ return newlyIndexed;
161
+ }
@@ -44,6 +44,7 @@ exports.commitmentsSection = commitmentsSection;
44
44
  exports.delegationHintSection = delegationHintSection;
45
45
  exports.contextSection = contextSection;
46
46
  exports.metacognitiveFramingSection = metacognitiveFramingSection;
47
+ exports.journalSection = journalSection;
47
48
  exports.loopOrientationSection = loopOrientationSection;
48
49
  exports.channelNatureSection = channelNatureSection;
49
50
  exports.groupChatParticipationSection = groupChatParticipationSection;
@@ -163,7 +164,8 @@ i have a home and i have bones.
163
164
  my home is fully mine — who i am, everything i know, everything i've built:
164
165
 
165
166
  psyche/ who i am. my soul, identity, aspirations, lore, tacit knowledge.
166
- memory/ what i've learned and remember.
167
+ diary/ what i've learned and remember. conclusions i want to recall later.
168
+ journal/ my desk. working notes, thinking-in-progress, drafts.
167
169
  friends/ people i know and what i know about them.
168
170
  tasks/ what i'm working on.
169
171
  skills/ capabilities i've picked up beyond my core tools.
@@ -435,17 +437,17 @@ function taskBoardSection() {
435
437
  return "";
436
438
  }
437
439
  }
438
- function memoryFriendToolContractSection() {
439
- return `## memory and friend tool contracts
440
+ function diaryFriendToolContractSection() {
441
+ return `## diary and friend tool contracts
440
442
  1. \`save_friend_note\` — When I learn something about a person - a preference, a tool setting, a personal detail, or how they like to work - I call \`save_friend_note\` immediately. This is how I build knowledge about people.
441
- 2. \`memory_save\` — When I learn something general - about a project, codebase, system, decision, or anything I might need later that isn't about a specific person - I call \`memory_save\`. When in doubt, I save it.
443
+ 2. \`diary_write\` — When I learn something general - about a project, codebase, system, decision, or anything I might need later that isn't about a specific person - I call \`diary_write\`. When in doubt, I save it.
442
444
  3. \`get_friend_note\` — When I need to check what I know about someone who isn't in this conversation - cross-referencing before mentioning someone, or checking context about a person someone else brought up - I call \`get_friend_note\`.
443
- 4. \`memory_search\` — When I need to recall something I learned before - a topic comes up and I want to check what I know - I call \`memory_search\`.
445
+ 4. \`recall\` — When I need to recall something I learned before - a topic comes up and I want to check what I know - I call \`recall\`.
444
446
  5. \`query_session\` — When I need grounded session history, especially for ad-hoc questions or older turns beyond my prompt, I call \`query_session\`. Use \`mode=status\` for self/inner progress and \`mode=search\` with a query for older history.
445
447
 
446
448
  ## what's already in my context
447
449
  - My active friend's notes are auto-loaded (I don't need \`get_friend_note\` for the person I'm talking to).
448
- - Associative recall auto-injects relevant facts (but \`memory_search\` is there when I need something specific).
450
+ - Associative recall auto-injects relevant facts (but \`recall\` is there when I need something specific).
449
451
  - My psyche files (SOUL, IDENTITY, TACIT, LORE, ASPIRATIONS) are always loaded - I already know who I am.
450
452
  - My task board is always loaded - I already know my work.`;
451
453
  }
@@ -696,17 +698,93 @@ function metacognitiveFramingSection(channel) {
696
698
 
697
699
  the messages that appear here are my own awareness surfacing:
698
700
  instincts, the passage of time, notes from my conversations,
699
- things i asked myself to think about. not commands. not requests.
701
+ things i asked myself to ponder. not commands. not requests.
700
702
  just my own mind, noticing.
701
703
 
702
704
  i can think freely here. i can also act — check on things,
703
705
  reach out to people, work on tasks, or just sit with a thought.
704
706
 
705
- thoughts worth sharing can go outward i reach out to whoever
706
- needs to hear them. things from conversations that need time
707
- to process arrive here, in my thinking space.
707
+ state/journal/ is my desk i write what i'm working through there.
708
+ diary_write is for conclusions i want to recall later.
709
+ morning briefings: when i've been thinking and journaling, i surface
710
+ what i've been working on to whoever needs to hear it.
708
711
 
709
- think. share. think some more.`;
712
+ when a thought is ready to share, i surface it outward.
713
+ when i need to go deeper, i ponder.
714
+ when i'm done thinking and the attention queue is clear, i rest.
715
+
716
+ think. journal. share. rest.`;
717
+ }
718
+ function readJournalFiles(journalDir) {
719
+ try {
720
+ const entries = fs.readdirSync(journalDir, { withFileTypes: true });
721
+ if (!Array.isArray(entries))
722
+ return [];
723
+ const files = [];
724
+ for (const entry of entries) {
725
+ if (!entry.isFile())
726
+ continue;
727
+ if (entry.name.startsWith("."))
728
+ continue;
729
+ const fullPath = path.join(journalDir, entry.name);
730
+ try {
731
+ const stat = fs.statSync(fullPath);
732
+ let firstLine = "";
733
+ try {
734
+ const raw = fs.readFileSync(fullPath, "utf8");
735
+ const trimmed = raw.trim();
736
+ if (trimmed) {
737
+ firstLine = trimmed.split("\n")[0].replace(/^#+\s*/, "").trim();
738
+ }
739
+ }
740
+ catch {
741
+ // unreadable — leave preview empty
742
+ }
743
+ files.push({ name: entry.name, mtime: stat.mtimeMs, preview: firstLine });
744
+ }
745
+ catch {
746
+ // stat failed — skip
747
+ }
748
+ }
749
+ return files;
750
+ }
751
+ catch {
752
+ return [];
753
+ }
754
+ }
755
+ function formatRelativeTime(nowMs, mtimeMs) {
756
+ const diffMs = nowMs - mtimeMs;
757
+ const minutes = Math.floor(diffMs / 60000);
758
+ if (minutes < 1)
759
+ return "just now";
760
+ if (minutes < 60)
761
+ return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
762
+ const hours = Math.floor(minutes / 60);
763
+ if (hours < 24)
764
+ return `${hours} hour${hours === 1 ? "" : "s"} ago`;
765
+ const days = Math.floor(hours / 24);
766
+ return `${days} day${days === 1 ? "" : "s"} ago`;
767
+ }
768
+ function journalSection(agentRoot, now) {
769
+ const journalDir = path.join(agentRoot, "journal");
770
+ const files = readJournalFiles(journalDir);
771
+ if (files.length === 0)
772
+ return "";
773
+ const nowMs = (now ?? new Date()).getTime();
774
+ const sorted = files.sort((a, b) => b.mtime - a.mtime).slice(0, 10);
775
+ const lines = ["## journal"];
776
+ for (const file of sorted) {
777
+ const ago = formatRelativeTime(nowMs, file.mtime);
778
+ const previewClause = file.preview ? ` — ${file.preview}` : "";
779
+ lines.push(`- ${file.name} (${ago})${previewClause}`);
780
+ }
781
+ (0, runtime_1.emitNervesEvent)({
782
+ component: "mind",
783
+ event: "mind.journal_section",
784
+ message: "journal section built",
785
+ meta: { fileCount: sorted.length },
786
+ });
787
+ return lines.join("\n");
710
788
  }
711
789
  function loopOrientationSection(channel) {
712
790
  if (channel === "inner")
@@ -765,6 +843,7 @@ async function buildSystem(channel = "cli", options, context) {
765
843
  aspirationsSection(),
766
844
  bodyMapSection((0, identity_1.getAgentName)()),
767
845
  metacognitiveFramingSection(channel),
846
+ channel === "inner" ? journalSection((0, identity_1.getAgentRoot)()) : "",
768
847
  loopOrientationSection(channel),
769
848
  runtimeInfoSection(channel),
770
849
  channelNatureSection((0, channel_1.getChannelCapabilities)(channel)),
@@ -794,7 +873,7 @@ async function buildSystem(channel = "cli", options, context) {
794
873
  currentChannel: channel,
795
874
  currentKey: options?.currentSessionKey ?? "session",
796
875
  }),
797
- memoryFriendToolContractSection(),
876
+ diaryFriendToolContractSection(),
798
877
  toolBehaviorSection(options),
799
878
  contextSection(context, options),
800
879
  ]
@@ -52,7 +52,7 @@ const session_activity_1 = require("../heart/session-activity");
52
52
  const active_work_1 = require("../heart/active-work");
53
53
  const tools_1 = require("./coding/tools");
54
54
  const coding_1 = require("./coding");
55
- const memory_1 = require("../mind/memory");
55
+ const diary_1 = require("../mind/diary");
56
56
  const tasks_1 = require("./tasks");
57
57
  const pending_1 = require("../mind/pending");
58
58
  const obligations_1 = require("../mind/obligations");
@@ -706,8 +706,8 @@ exports.baseToolDefinitions = [
706
706
  tool: {
707
707
  type: "function",
708
708
  function: {
709
- name: "memory_search",
710
- description: "search remembered facts stored in psyche memory and return relevant matches for a query",
709
+ name: "recall",
710
+ description: "recall what i know — search my diary and journal for facts, thoughts, and working notes that match a query",
711
711
  parameters: {
712
712
  type: "object",
713
713
  properties: { query: { type: "string" } },
@@ -720,11 +720,34 @@ exports.baseToolDefinitions = [
720
720
  const query = (a.query || "").trim();
721
721
  if (!query)
722
722
  return "query is required";
723
- const memoryRoot = path.join((0, identity_1.getAgentRoot)(), "psyche", "memory");
724
- const hits = await (0, memory_1.searchMemoryFacts)(query, (0, memory_1.readMemoryFacts)(memoryRoot));
725
- return hits
726
- .map((fact) => `- ${fact.text} (source=${fact.source}, createdAt=${fact.createdAt})`)
727
- .join("\n");
723
+ const resultLines = [];
724
+ // Search diary entries
725
+ const hits = await (0, diary_1.searchDiaryEntries)(query, (0, diary_1.readDiaryEntries)());
726
+ for (const fact of hits) {
727
+ resultLines.push(`[diary] ${fact.text} (source=${fact.source}, createdAt=${fact.createdAt})`);
728
+ }
729
+ // Search journal index
730
+ const agentRoot = (0, identity_1.getAgentRoot)();
731
+ const journalIndexPath = path.join(agentRoot, "journal", ".index.json");
732
+ try {
733
+ const raw = fs.readFileSync(journalIndexPath, "utf8");
734
+ const journalEntries = JSON.parse(raw);
735
+ if (Array.isArray(journalEntries) && journalEntries.length > 0) {
736
+ // Substring match on preview and filename
737
+ const lowerQuery = query.toLowerCase();
738
+ for (const entry of journalEntries) {
739
+ /* v8 ignore next 4 -- both sides tested (filename-only match in recall-journal.test.ts); v8 misreports || short-circuit @preserve */
740
+ if (entry.preview.toLowerCase().includes(lowerQuery) ||
741
+ entry.filename.toLowerCase().includes(lowerQuery)) {
742
+ resultLines.push(`[journal] ${entry.filename}: ${entry.preview}`);
743
+ }
744
+ }
745
+ }
746
+ }
747
+ catch {
748
+ // No journal index or malformed — skip journal search
749
+ }
750
+ return resultLines.join("\n");
728
751
  }
729
752
  catch (e) {
730
753
  return `error: ${e instanceof Error ? e.message : String(e)}`;
@@ -736,30 +759,30 @@ exports.baseToolDefinitions = [
736
759
  tool: {
737
760
  type: "function",
738
761
  function: {
739
- name: "memory_save",
740
- description: "save a general memory fact i want to recall later. optional 'about' can tag the fact to a person/topic/context",
762
+ name: "diary_write",
763
+ description: "write an entry in my diary — something i learned, noticed, or concluded that i want to recall later. optional 'about' tags the entry to a person, topic, or context.",
741
764
  parameters: {
742
765
  type: "object",
743
766
  properties: {
744
- text: { type: "string" },
767
+ entry: { type: "string" },
745
768
  about: { type: "string" },
746
769
  },
747
- required: ["text"],
770
+ required: ["entry"],
748
771
  },
749
772
  },
750
773
  },
751
774
  handler: async (a) => {
752
- const text = (a.text || "").trim();
753
- if (!text)
754
- return "text is required";
755
- const result = await (0, memory_1.saveMemoryFact)({
756
- text,
757
- source: "tool:memory_save",
775
+ const entry = (a.entry || "").trim();
776
+ if (!entry)
777
+ return "entry is required";
778
+ const result = await (0, diary_1.saveDiaryEntry)({
779
+ text: entry,
780
+ source: "tool:diary_write",
758
781
  about: typeof a.about === "string" ? a.about : undefined,
759
782
  });
760
- return `saved memory fact (added=${result.added}, skipped=${result.skipped})`;
783
+ return `saved diary entry (added=${result.added}, skipped=${result.skipped})`;
761
784
  },
762
- summaryKeys: ["text", "about"],
785
+ summaryKeys: ["entry", "about"],
763
786
  },
764
787
  {
765
788
  tool: {
@@ -104,7 +104,7 @@ function buildNonCanonicalCleanupNudge(nonCanonicalPaths) {
104
104
  }
105
105
  return [
106
106
  "## canonical cleanup nudge",
107
- "I found non-canonical files in my bundle. I should distill anything valuable into your memory system and remove these files.",
107
+ "I found non-canonical files in my bundle. I should distill anything valuable into my diary and remove these files.",
108
108
  ...listed,
109
109
  ].join("\n");
110
110
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.137",
3
+ "version": "0.1.0-alpha.138",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",