clementine-agent 1.18.52 → 1.18.53

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.
@@ -14,4 +14,14 @@ export interface BuildChatContextOptions {
14
14
  * SDK then runs with just the bare `claude_code` preset.
15
15
  */
16
16
  export declare function buildChatSystemAppend(opts?: BuildChatContextOptions): string;
17
+ /**
18
+ * Read the long-term memory block for an autonomous run (cron, team-task).
19
+ * Returns the agent-specific MEMORY.md when a hired agent is active, the
20
+ * global MEMORY.md when running as Clementine, or empty when neither
21
+ * exists. Returns a heading-prefixed block ready to drop into a prompt.
22
+ *
23
+ * Heartbeats deliberately skip this — they're tool-free Haiku decisions
24
+ * and a 6KB memory block defeats the cost economy.
25
+ */
26
+ export declare function buildAutonomousMemoryContext(profile?: AgentProfile | null): string;
17
27
  //# sourceMappingURL=run-agent-context.d.ts.map
@@ -41,40 +41,70 @@ function trimTo(text, max) {
41
41
  */
42
42
  export function buildChatSystemAppend(opts = {}) {
43
43
  const blocks = [];
44
- // 1. Soul (personality + voice)
45
- const soul = readFileSafe(SOUL_FILE);
46
- if (soul.trim()) {
47
- blocks.push(`## Identity & Voice\n${trimTo(soul, SOUL_MAX_CHARS)}`);
44
+ const isHiredAgent = !!opts.profile && opts.profile.slug !== 'clementine';
45
+ // 1. Identity & Voice.
46
+ // - Clementine main agent: SOUL.md (the global personality file).
47
+ // - Hired agent main: their own profile.systemPromptBody IS their
48
+ // identity — don't load Clementine's SOUL on top of it.
49
+ if (!isHiredAgent) {
50
+ const soul = readFileSafe(SOUL_FILE);
51
+ if (soul.trim()) {
52
+ blocks.push(`## Identity & Voice\n${trimTo(soul, SOUL_MAX_CHARS)}`);
53
+ }
48
54
  }
49
- // 2. Long-term memory — agent-specific file overrides the global one.
50
- const profileMemoryPath = opts.profile?.slug
55
+ // 2. Long-term memory — agent-specific file when a hired agent is
56
+ // active, otherwise the global one.
57
+ const profileMemoryPath = isHiredAgent
51
58
  ? path.join(AGENTS_DIR, opts.profile.slug, 'MEMORY.md')
52
59
  : null;
53
- let memory = '';
54
60
  if (profileMemoryPath && fs.existsSync(profileMemoryPath)) {
55
- memory = readFileSafe(profileMemoryPath);
61
+ const memory = readFileSafe(profileMemoryPath);
56
62
  if (memory.trim()) {
57
- blocks.push(`## Long-Term Memory (${opts.profile?.name ?? opts.profile?.slug})\n${trimTo(memory, PROFILE_MEMORY_MAX_CHARS)}`);
63
+ blocks.push(`## Long-Term Memory (${opts.profile.name ?? opts.profile.slug})\n${trimTo(memory, PROFILE_MEMORY_MAX_CHARS)}`);
58
64
  }
59
65
  }
60
66
  else {
61
- memory = readFileSafe(MEMORY_FILE);
67
+ const memory = readFileSafe(MEMORY_FILE);
62
68
  if (memory.trim()) {
63
69
  blocks.push(`## Long-Term Memory\n${trimTo(memory, MEMORY_MAX_CHARS)}`);
64
70
  }
65
71
  }
66
72
  // 3. Team roster (only when not running AS a hired agent — Sasha
67
73
  // doesn't need to be told who Sasha is).
68
- if (!opts.profile) {
74
+ if (!isHiredAgent) {
69
75
  const agentsRoster = readFileSafe(AGENTS_FILE);
70
76
  if (agentsRoster.trim()) {
71
77
  blocks.push(`## Team Roster\n${trimTo(agentsRoster, AGENTS_MAX_CHARS)}`);
72
78
  }
73
79
  }
74
- // 4. Profile system prompt body (e.g. Sasha's role description).
80
+ // 4. Profile system prompt body (the hired agent's identity + role).
75
81
  if (opts.profileAppend?.trim()) {
76
82
  blocks.push(opts.profileAppend);
77
83
  }
78
84
  return blocks.join('\n\n');
79
85
  }
86
+ /**
87
+ * Read the long-term memory block for an autonomous run (cron, team-task).
88
+ * Returns the agent-specific MEMORY.md when a hired agent is active, the
89
+ * global MEMORY.md when running as Clementine, or empty when neither
90
+ * exists. Returns a heading-prefixed block ready to drop into a prompt.
91
+ *
92
+ * Heartbeats deliberately skip this — they're tool-free Haiku decisions
93
+ * and a 6KB memory block defeats the cost economy.
94
+ */
95
+ export function buildAutonomousMemoryContext(profile) {
96
+ const isHiredAgent = !!profile && profile.slug !== 'clementine';
97
+ const memoryPath = isHiredAgent
98
+ ? path.join(AGENTS_DIR, profile.slug, 'MEMORY.md')
99
+ : MEMORY_FILE;
100
+ if (!fs.existsSync(memoryPath))
101
+ return '';
102
+ const memory = readFileSafe(memoryPath);
103
+ if (!memory.trim())
104
+ return '';
105
+ const label = isHiredAgent
106
+ ? `Long-Term Memory (${profile.name ?? profile.slug})`
107
+ : 'Long-Term Memory';
108
+ return `## ${label}\n${trimTo(memory, isHiredAgent ? PROFILE_MEMORY_MAX_CHARS : MEMORY_MAX_CHARS)}\n\n`;
109
+ }
80
110
  //# sourceMappingURL=run-agent-context.js.map
@@ -20,6 +20,7 @@ import pino from 'pino';
20
20
  import { BASE_DIR, VAULT_DIR, CRON_PROGRESS_DIR, } from '../config.js';
21
21
  import { runAgent } from './run-agent.js';
22
22
  import { buildExtraMcpForRunAgent } from './run-agent-mcp.js';
23
+ import { buildAutonomousMemoryContext } from './run-agent-context.js';
23
24
  import { listAllGoals } from '../tools/shared.js';
24
25
  const CRON_PROGRESS_PENDING_MAX_ITEMS = 20;
25
26
  const CRON_PROGRESS_NOTES_MAX_CHARS = 2000;
@@ -234,6 +235,10 @@ export async function runAgentCron(opts) {
234
235
  const agentSlug = opts.profile?.slug;
235
236
  const ownerName = process.env.OWNER_NAME ?? 'the user';
236
237
  // ── Compose context blocks (mirrors legacy runCronJob) ─────────────
238
+ // Memory block goes first so the agent reads its long-term context
239
+ // before the run-specific progress/goals/etc. For a hired agent
240
+ // (Ross/Sasha) this is their own MEMORY.md, not Clementine's global.
241
+ const memoryContext = buildAutonomousMemoryContext(opts.profile);
237
242
  const progressContext = buildProgressContext(opts.jobName);
238
243
  const goalContext = buildGoalContext(opts.jobName);
239
244
  const delegationContext = buildDelegationContext(agentSlug);
@@ -246,6 +251,7 @@ export async function runAgentCron(opts) {
246
251
  // spawns isolated sub-agents without a hand-rolled prompt directive.
247
252
  // Final prompt
248
253
  const builtPrompt = `[Scheduled task: ${opts.jobName}]\n\n` +
254
+ memoryContext +
249
255
  progressContext +
250
256
  goalContext +
251
257
  skillContext +
@@ -17,15 +17,21 @@
17
17
  import pino from 'pino';
18
18
  import { runAgent } from './run-agent.js';
19
19
  import { buildExtraMcpForRunAgent } from './run-agent-mcp.js';
20
+ import { buildAutonomousMemoryContext } from './run-agent-context.js';
20
21
  const logger = pino({ name: 'clementine.run-agent-team-task' });
21
22
  export async function runAgentTeamTask(opts) {
22
23
  const taskName = `team-msg:${opts.fromSlug}-to-${opts.profile.slug}`;
23
24
  const now = new Date();
24
25
  const timestamp = now.toISOString().slice(0, 16).replace('T', ' ');
26
+ // Inject the recipient's own long-term memory so they have context
27
+ // about prior work, preferences, and team relationships before
28
+ // processing the message.
29
+ const memoryContext = buildAutonomousMemoryContext(opts.profile);
25
30
  // Match the legacy phase-1 prompt shape so existing agent training
26
31
  // (Sasha/Ross/Nora) keeps responding the same way. Phases 2+ are no
27
32
  // longer needed — the SDK keeps the conversation in one session.
28
33
  const builtPrompt = `[TEAM MESSAGE from ${opts.fromName} (${opts.fromSlug}) — ${timestamp}]\n\n` +
34
+ memoryContext +
29
35
  `You received a direct message from a teammate. Process it fully and autonomously.\n\n` +
30
36
  `MESSAGE:\n${opts.content}\n\n` +
31
37
  `IMPORTANT:\n` +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.18.52",
3
+ "version": "1.18.53",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",