@letta-ai/letta-code 0.33.7 → 0.33.8

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/letta.js CHANGED
@@ -5648,7 +5648,7 @@ var package_default;
5648
5648
  var init_package = __esm(() => {
5649
5649
  package_default = {
5650
5650
  name: "@letta-ai/letta-code",
5651
- version: "0.33.7",
5651
+ version: "0.33.8",
5652
5652
  lettaStartupLogProtocol: 1,
5653
5653
  description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
5654
5654
  type: "module",
@@ -90892,7 +90892,7 @@ var init_context = __esm(() => {
90892
90892
  });
90893
90893
 
90894
90894
  // src/constants.ts
90895
- var DEFAULT_SUMMARIZATION_MODEL = "letta/auto", DEFAULT_TITLE_SUMMARIZATION_MODEL = "openai/gpt-5.6-luna", DEFAULT_AGENT_NAME = "Letta Code", INTERRUPTED_BY_USER = "Interrupted by user", TURN_DID_NOT_COMPLETE = "Turn did not complete", SYSTEM_REMINDER_TAG = "system-reminder", SYSTEM_REMINDER_OPEN, SYSTEM_REMINDER_CLOSE, SYSTEM_ALERT_TAG = "system-alert", SYSTEM_ALERT_OPEN, SYSTEM_ALERT_CLOSE, COMPACTION_SUMMARY_HEADER = "(Earlier messages in this conversation have been compacted to free up context, summarized below)", TOKEN_DISPLAY_THRESHOLD = 100, ELAPSED_DISPLAY_THRESHOLD_MS;
90895
+ var DEFAULT_SUMMARIZATION_MODEL = "letta/auto", DEFAULT_TITLE_SUMMARIZATION_MODEL = "openai/gpt-6-luna", DEFAULT_AGENT_NAME = "Letta Code", INTERRUPTED_BY_USER = "Interrupted by user", TURN_DID_NOT_COMPLETE = "Turn did not complete", SYSTEM_REMINDER_TAG = "system-reminder", SYSTEM_REMINDER_OPEN, SYSTEM_REMINDER_CLOSE, SYSTEM_ALERT_TAG = "system-alert", SYSTEM_ALERT_OPEN, SYSTEM_ALERT_CLOSE, COMPACTION_SUMMARY_HEADER = "(Earlier messages in this conversation have been compacted to free up context, summarized below)", TOKEN_DISPLAY_THRESHOLD = 100, ELAPSED_DISPLAY_THRESHOLD_MS;
90896
90896
  var init_constants2 = __esm(() => {
90897
90897
  SYSTEM_REMINDER_OPEN = `<${SYSTEM_REMINDER_TAG}>`;
90898
90898
  SYSTEM_REMINDER_CLOSE = `</${SYSTEM_REMINDER_TAG}>`;
@@ -107158,27 +107158,63 @@ function parseOllamaTags(data) {
107158
107158
  };
107159
107159
  }).filter((entry) => entry !== undefined);
107160
107160
  }
107161
- function parseOllamaRunningContexts(data) {
107162
- const contexts = new Map;
107161
+ function ollamaIdentityKeys(id2) {
107162
+ const normalized = normalizeOllamaModelId(id2);
107163
+ return normalized === id2 ? [id2] : [id2, normalized];
107164
+ }
107165
+ function parseOllamaPsEntries(data) {
107163
107166
  if (!data || typeof data !== "object")
107164
- return contexts;
107167
+ return [];
107165
107168
  const models3 = data.models;
107166
107169
  if (!Array.isArray(models3))
107167
- return contexts;
107170
+ return [];
107171
+ const entries = [];
107168
107172
  for (const entry of models3) {
107169
107173
  if (!entry || typeof entry !== "object")
107170
107174
  continue;
107171
107175
  const record4 = entry;
107172
- const id2 = record4.name ?? record4.model;
107173
- const contextLength = record4.context_length;
107174
- if (typeof id2 !== "string" || id2.length === 0)
107176
+ const rawIds = [record4.name, record4.model].filter((id2) => typeof id2 === "string" && id2.length > 0);
107177
+ if (rawIds.length === 0)
107175
107178
  continue;
107176
- if (typeof contextLength !== "number" || contextLength <= 0)
107179
+ const contextLength = record4.context_length;
107180
+ entries.push({
107181
+ ids: [...new Set(rawIds.flatMap(ollamaIdentityKeys))],
107182
+ displayId: rawIds[0],
107183
+ ...typeof contextLength === "number" && contextLength > 0 ? { contextLength } : {}
107184
+ });
107185
+ }
107186
+ return entries;
107187
+ }
107188
+ function parseOllamaRunningContexts(data) {
107189
+ const contexts = new Map;
107190
+ for (const entry of parseOllamaPsEntries(data)) {
107191
+ if (entry.contextLength === undefined)
107177
107192
  continue;
107178
- contexts.set(id2, contextLength);
107193
+ for (const id2 of entry.ids)
107194
+ contexts.set(id2, entry.contextLength);
107179
107195
  }
107180
107196
  return contexts;
107181
107197
  }
107198
+ function readOllamaServedContextOverride(env4) {
107199
+ const raw = env4[OLLAMA_SERVED_CONTEXT_OVERRIDE_ENV];
107200
+ if (raw === undefined || raw.trim() === "")
107201
+ return;
107202
+ const trimmed = raw.trim();
107203
+ if (!/^[1-9]\d*$/.test(trimmed) || !Number.isSafeInteger(Number(trimmed))) {
107204
+ throw new Error(`${OLLAMA_SERVED_CONTEXT_OVERRIDE_ENV}=${JSON.stringify(trimmed)} is not a positive integer token count. ` + `Set it to the context window your Ollama daemon actually serves.`);
107205
+ }
107206
+ return Number(trimmed);
107207
+ }
107208
+ function missingServedContextError(modelId, data) {
107209
+ const entries = parseOllamaPsEntries(data);
107210
+ const match2 = entries.find((entry) => entry.ids.includes(modelId));
107211
+ if (match2) {
107212
+ return `Ollama lists "${modelId}" in /api/ps but did not report context_length. ` + `Refusing to send the prompt because Ollama may silently truncate it. ` + `context_length requires Ollama 0.10.0 or newer. ` + `Upgrade Ollama, or set ${OLLAMA_SERVED_CONTEXT_OVERRIDE_ENV} to the window your daemon actually serves.`;
107213
+ }
107214
+ const running = entries.map((entry) => entry.displayId).filter((id2) => id2 !== undefined);
107215
+ const runningNote = running.length > 0 ? ` Running models: ${running.join(", ")}.` : ` /api/ps lists no running models.`;
107216
+ return `Ollama loaded "${modelId}", but /api/ps does not list it. ` + `Refusing to send the prompt because Ollama may silently truncate it. ` + `If OLLAMA_KEEP_ALIVE=0, the model unloaded before this check.` + runningNote + ` Set ${OLLAMA_SERVED_CONTEXT_OVERRIDE_ENV} to the window your daemon actually serves.`;
107217
+ }
107182
107218
  async function fetchOllamaNative(fetchImpl, url, options) {
107183
107219
  const controller = new AbortController;
107184
107220
  const timeout = setTimeout(() => controller.abort(), options.timeoutMs);
@@ -107210,42 +107246,49 @@ async function fetchOllamaNative(fetchImpl, url, options) {
107210
107246
  }
107211
107247
  }
107212
107248
  async function resolveOllamaServedContext(options) {
107249
+ const override = readOllamaServedContextOverride(options.env ?? process.env);
107250
+ if (override !== undefined)
107251
+ return override;
107213
107252
  const fetchImpl = options.fetchImpl ?? fetch;
107214
107253
  const modelId = normalizeOllamaModelId(options.modelId);
107215
107254
  const nativeBaseURL = localEndpointNativeBaseURL(options.baseURL);
107216
107255
  const psURL = `${nativeBaseURL}/api/ps`;
107217
- const runningContext = async () => {
107218
- const data = await fetchOllamaNative(fetchImpl, psURL, {
107219
- ...options.apiKey ? { apiKey: options.apiKey } : {},
107220
- ...options.signal ? { signal: options.signal } : {},
107221
- timeoutMs: OLLAMA_STATUS_TIMEOUT_MS,
107222
- consume: (response) => response.json()
107223
- });
107224
- return parseOllamaRunningContexts(data).get(modelId);
107225
- };
107256
+ const overrideHint = `or set ${OLLAMA_SERVED_CONTEXT_OVERRIDE_ENV} to the window your daemon actually serves`;
107226
107257
  try {
107227
107258
  await fetchOllamaNative(fetchImpl, `${nativeBaseURL}/api/generate`, {
107228
107259
  ...options.apiKey ? { apiKey: options.apiKey } : {},
107229
107260
  ...options.signal ? { signal: options.signal } : {},
107230
- body: { model: modelId, prompt: "", stream: false },
107261
+ body: {
107262
+ model: modelId,
107263
+ prompt: "",
107264
+ stream: false,
107265
+ keep_alive: OLLAMA_PREFLIGHT_KEEP_ALIVE
107266
+ },
107231
107267
  timeoutMs: OLLAMA_MODEL_LOAD_TIMEOUT_MS,
107232
107268
  consume: (response) => response.text()
107233
107269
  });
107234
107270
  } catch (error4) {
107235
107271
  if (options.signal?.aborted)
107236
107272
  throw options.signal.reason ?? error4;
107237
- throw new Error(`Unable to load Ollama model "${modelId}" to determine its served context window. ` + `Refusing to send the prompt because Ollama may silently truncate it. ` + `Check the Ollama endpoint, model installation, and available memory. (${error4 instanceof Error ? error4.message : String(error4)})`);
107273
+ throw new Error(`Unable to load Ollama model "${modelId}" to determine its served context window. ` + `Refusing to send the prompt because Ollama may silently truncate it. ` + `Check the Ollama endpoint, model installation, and available memory, ${overrideHint}. (${error4 instanceof Error ? error4.message : String(error4)})`);
107238
107274
  }
107275
+ let running;
107239
107276
  try {
107240
- const loadedContext = await runningContext();
107241
- if (loadedContext !== undefined)
107242
- return loadedContext;
107277
+ running = await fetchOllamaNative(fetchImpl, psURL, {
107278
+ ...options.apiKey ? { apiKey: options.apiKey } : {},
107279
+ ...options.signal ? { signal: options.signal } : {},
107280
+ timeoutMs: OLLAMA_STATUS_TIMEOUT_MS,
107281
+ consume: (response) => response.json()
107282
+ });
107243
107283
  } catch (error4) {
107244
107284
  if (options.signal?.aborted)
107245
107285
  throw options.signal.reason ?? error4;
107246
- throw new Error(`Ollama loaded model "${modelId}", but /api/ps could not verify its served context window. ` + `Refusing to send the prompt because Ollama may silently truncate it. ` + `Check that the endpoint supports /api/ps. (${error4 instanceof Error ? error4.message : String(error4)})`);
107286
+ throw new Error(`Ollama loaded model "${modelId}", but /api/ps could not verify its served context window. ` + `Refusing to send the prompt because Ollama may silently truncate it. ` + `Check that the endpoint supports /api/ps, ${overrideHint}. (${error4 instanceof Error ? error4.message : String(error4)})`);
107247
107287
  }
107248
- throw new Error(`Ollama did not report an exact served context window for loaded model "${modelId}" in /api/ps. ` + `Refusing to send the prompt because Ollama may silently truncate it. ` + `Check the model name and Ollama server logs.`);
107288
+ const loadedContext = parseOllamaRunningContexts(running).get(modelId);
107289
+ if (loadedContext !== undefined)
107290
+ return loadedContext;
107291
+ throw new Error(missingServedContextError(modelId, running));
107249
107292
  }
107250
107293
  function parseOllamaShow(modelId, data, runtimeContextLength) {
107251
107294
  if (!data || typeof data !== "object")
@@ -107275,7 +107318,7 @@ function createOllamaPiProvider(options) {
107275
107318
  discover: ollamaDiscover
107276
107319
  });
107277
107320
  }
107278
- var OLLAMA_PI_PROVIDER_ID = "ollama", OLLAMA_CLOUD_PI_PROVIDER_ID = "ollama-cloud", OLLAMA_STATUS_TIMEOUT_MS = 2000, OLLAMA_MODEL_LOAD_TIMEOUT_MS = 120000, ollamaDiscover = async (context3) => {
107321
+ var OLLAMA_PI_PROVIDER_ID = "ollama", OLLAMA_CLOUD_PI_PROVIDER_ID = "ollama-cloud", OLLAMA_STATUS_TIMEOUT_MS = 2000, OLLAMA_MODEL_LOAD_TIMEOUT_MS = 120000, OLLAMA_PREFLIGHT_KEEP_ALIVE = "5m", OLLAMA_SERVED_CONTEXT_OVERRIDE_ENV = "LETTA_OLLAMA_CONTEXT_LENGTH", ollamaDiscover = async (context3) => {
107279
107322
  const tags = await context3.fetchJson(`${context3.nativeBaseURL}/api/tags`);
107280
107323
  const entries = parseOllamaTags(tags);
107281
107324
  const runtimeContexts = await context3.fetchJson(`${context3.nativeBaseURL}/api/ps`).then(parseOllamaRunningContexts).catch(() => new Map);
@@ -188517,6 +188560,12 @@ var init_manager = __esm(() => {
188517
188560
  label: "diffs",
188518
188561
  description: "Open browser-based worktree diff previews powered by Diffs from Pierre."
188519
188562
  },
188563
+ {
188564
+ id: "memory_palace",
188565
+ label: "memory palace",
188566
+ description: "Opt-in memory palace experiment flag for disposable local listener runs. Disabled by default; no behavior changes unless consumers gate on it.",
188567
+ envVar: "LETTA_MEMORY_PALACE"
188568
+ },
188520
188569
  {
188521
188570
  id: "reflection_arena",
188522
188571
  label: "reflection arena",
@@ -198050,6 +198099,7 @@ var init_protocol_inbound = __esm(() => {
198050
198099
  EXPERIMENT_IDS = new Set([
198051
198100
  "conversation_titles",
198052
198101
  "desktop_conversation_bootstrap",
198102
+ "memory_palace",
198053
198103
  "tui_cron"
198054
198104
  ]);
198055
198105
  });
@@ -213145,7 +213195,7 @@ class CodexAppServerClient {
213145
213195
  await this.request("initialize", {
213146
213196
  clientInfo: {
213147
213197
  name: "letta-code",
213148
- version: "0.33.7"
213198
+ version: "0.33.8"
213149
213199
  },
213150
213200
  capabilities: null
213151
213201
  });
@@ -218304,6 +218354,7 @@ __export(exports_skills2, {
218304
218354
  getBundledSkills: () => getBundledSkills,
218305
218355
  getAgentSkillsDir: () => getAgentSkillsDir,
218306
218356
  formatSkillsAsSystemReminder: () => formatSkillsAsSystemReminder,
218357
+ enabledSkillExperiments: () => enabledSkillExperiments,
218307
218358
  discoverSkills: () => discoverSkills,
218308
218359
  compareSkills: () => compareSkills,
218309
218360
  SKILLS_DIR: () => SKILLS_DIR,
@@ -218359,7 +218410,14 @@ function isModelInvocableSkill(skill) {
218359
218410
  function isUserInvocableSkill(skill) {
218360
218411
  return skill.userInvocable !== false;
218361
218412
  }
218413
+ function enabledSkillExperiments() {
218414
+ return [...new Set(EXPERIMENT_BUNDLED_SKILLS.values())].filter((id2) => experimentManager.isEnabled(id2)).sort().join(",");
218415
+ }
218362
218416
  function isSkillAvailableForAgent(skill, agentId) {
218417
+ const experiment = skill.source === "bundled" ? EXPERIMENT_BUNDLED_SKILLS.get(skill.id) : undefined;
218418
+ if (experiment && !experimentManager.isEnabled(experiment)) {
218419
+ return false;
218420
+ }
218363
218421
  if (skill.source === "bundled" && agentId && isLocalAgentId(agentId) && LOCAL_AGENT_EXCLUDED_BUNDLED_SKILLS.has(skill.id)) {
218364
218422
  return false;
218365
218423
  }
@@ -218423,7 +218481,7 @@ async function discoverSkills(projectSkillsPath = join61(process.cwd(), SKILLS_D
218423
218481
  }
218424
218482
  }
218425
218483
  return {
218426
- skills: Array.from(skillsById.values()).sort(compareSkills),
218484
+ skills: Array.from(skillsById.values()).filter((skill) => isSkillAvailableForAgent(skill, agentId)).sort(compareSkills),
218427
218485
  errors: allErrors
218428
218486
  };
218429
218487
  }
@@ -218531,14 +218589,18 @@ ${lines.join(`
218531
218589
  `)}
218532
218590
  </system-reminder>`;
218533
218591
  }
218534
- var LOCAL_AGENT_EXCLUDED_BUNDLED_SKILLS, PROJECT_SKILLS_DIR, SKILLS_DIR = ".skills", GLOBAL_SKILLS_DIR;
218592
+ var LOCAL_AGENT_EXCLUDED_BUNDLED_SKILLS, EXPERIMENT_BUNDLED_SKILLS, PROJECT_SKILLS_DIR, SKILLS_DIR = ".skills", GLOBAL_SKILLS_DIR;
218535
218593
  var init_skills5 = __esm(() => {
218594
+ init_manager();
218536
218595
  init_skill_sources();
218537
218596
  LOCAL_AGENT_EXCLUDED_BUNDLED_SKILLS = new Set([
218538
218597
  "image-generation",
218539
218598
  "managing-shared-memory",
218540
218599
  "working-across-computers"
218541
218600
  ]);
218601
+ EXPERIMENT_BUNDLED_SKILLS = new Map([
218602
+ ["curating-memory-palace", "memory_palace"]
218603
+ ]);
218542
218604
  PROJECT_SKILLS_DIR = join61(".agents", "skills");
218543
218605
  GLOBAL_SKILLS_DIR = join61(process.env.HOME || process.env.USERPROFILE || "~", ".letta/skills");
218544
218606
  });
@@ -228048,7 +228110,8 @@ function computeCacheKey(components) {
228048
228110
  components.primaryProjectSkillsDirectory,
228049
228111
  [...components.memorySkillsDirs].sort().join(","),
228050
228112
  [...components.sharedMemorySkillsDirs].sort().join(","),
228051
- components.sharedMemoryErrors.map((error4) => `${error4.path}:${error4.message}`).sort().join(",")
228113
+ components.sharedMemoryErrors.map((error4) => `${error4.path}:${error4.message}`).sort().join(","),
228114
+ components.skillExperiments
228052
228115
  ].join("|");
228053
228116
  }
228054
228117
  function getSkillRoots(components) {
@@ -228341,7 +228404,8 @@ async function buildClientSkillsPayload(options = {}) {
228341
228404
  primaryProjectSkillsDirectory,
228342
228405
  memorySkillsDirs,
228343
228406
  sharedMemorySkillsDirs: sharedMemoryContext.skillsDirs,
228344
- sharedMemoryErrors: sharedMemoryContext.errors
228407
+ sharedMemoryErrors: sharedMemoryContext.errors,
228408
+ skillExperiments: enabledSkillExperiments()
228345
228409
  };
228346
228410
  const cacheKey = computeCacheKey(cacheComponents);
228347
228411
  if (useCache) {
@@ -412478,12 +412542,12 @@ function rowToolCalls(row, index, diagnostics2) {
412478
412542
  }
412479
412543
  return calls;
412480
412544
  }
412481
- function contentText3(content) {
412545
+ function contentText2(content) {
412482
412546
  if (typeof content === "string") {
412483
412547
  if (content.startsWith(CONTENT_JSON_PREFIX)) {
412484
412548
  const encoded = content.slice(CONTENT_JSON_PREFIX.length);
412485
412549
  try {
412486
- return contentText3(JSON.parse(encoded));
412550
+ return contentText2(JSON.parse(encoded));
412487
412551
  } catch {
412488
412552
  return encoded;
412489
412553
  }
@@ -412569,7 +412633,7 @@ var init_hermes = __esm(() => {
412569
412633
  });
412570
412634
  };
412571
412635
  if (row.role === "user") {
412572
- const content = contentText3(row.content);
412636
+ const content = contentText2(row.content);
412573
412637
  if (content) {
412574
412638
  emit({
412575
412639
  type: "message",
@@ -412589,7 +412653,7 @@ var init_hermes = __esm(() => {
412589
412653
  ...timestamp ? { timestamp } : {}
412590
412654
  });
412591
412655
  }
412592
- const content = contentText3(row.content);
412656
+ const content = contentText2(row.content);
412593
412657
  if (content) {
412594
412658
  emit({
412595
412659
  type: "message",
@@ -412612,7 +412676,7 @@ var init_hermes = __esm(() => {
412612
412676
  if (row.role === "tool") {
412613
412677
  emit({
412614
412678
  type: "tool_result",
412615
- content: contentText3(row.content),
412679
+ content: contentText2(row.content),
412616
412680
  ...typeof row.tool_call_id === "string" && row.tool_call_id ? { callId: row.tool_call_id } : {},
412617
412681
  ...timestamp ? { timestamp } : {}
412618
412682
  });
@@ -445266,7 +445330,7 @@ function createTransport2(config2, options) {
445266
445330
  return new StreamableHTTPClientTransport(new URL(config2.url), {
445267
445331
  requestInit: headersRequestInit(headers),
445268
445332
  authProvider: options.oauth?.authProvider,
445269
- fetch: abortAwareFetch(options.signal)
445333
+ fetch: abortAwareFetch(options.signal, undefined, options.fetch)
445270
445334
  });
445271
445335
  }
445272
445336
  if (config2.transport === "sse") {
@@ -445275,7 +445339,7 @@ function createTransport2(config2, options) {
445275
445339
  return new SSEClientTransport(new URL(config2.url), {
445276
445340
  requestInit,
445277
445341
  authProvider: options.oauth?.authProvider,
445278
- fetch: abortAwareFetch(options.signal, headers)
445342
+ fetch: abortAwareFetch(options.signal, headers, options.fetch)
445279
445343
  });
445280
445344
  }
445281
445345
  return new StdioClientTransport({
@@ -445286,13 +445350,14 @@ function createTransport2(config2, options) {
445286
445350
  stderr: options.stderr ?? "inherit"
445287
445351
  });
445288
445352
  }
445289
- function abortAwareFetch(operationSignal2, headers) {
445353
+ function abortAwareFetch(operationSignal2, headers, fetchFn) {
445290
445354
  if (!operationSignal2 && !headers)
445291
- return;
445355
+ return fetchFn;
445356
+ const request = fetchFn ?? fetch;
445292
445357
  return (url2, init) => {
445293
445358
  const requestInit = headers ? mergeHeaders4(init, headers) : { ...init };
445294
445359
  const requestSignal3 = requestInit.signal ?? undefined;
445295
- return fetch(url2, {
445360
+ return request(url2, {
445296
445361
  ...requestInit,
445297
445362
  signal: operationSignal2 && requestSignal3 ? AbortSignal.any([operationSignal2, requestSignal3]) : operationSignal2 ?? requestSignal3
445298
445363
  });
@@ -445344,7 +445409,7 @@ var init_mcp_client = __esm(() => {
445344
445409
  init_streamableHttp();
445345
445410
  DEFAULT_CLIENT_INFO = {
445346
445411
  name: "letta-code",
445347
- version: "0.33.7"
445412
+ version: "0.33.8"
445348
445413
  };
445349
445414
  });
445350
445415
 
@@ -513196,4 +513261,4 @@ function registerBunOAuthFlows() {
513196
513261
  registerBunOAuthFlows();
513197
513262
  await init_src5().then(() => exports_src2);
513198
513263
 
513199
- //# debugId=3195287CCD45D3B564756E2164756E21
513264
+ //# debugId=7B5F416253AF257964756E2164756E21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letta-ai/letta-code",
3
- "version": "0.33.7",
3
+ "version": "0.33.8",
4
4
  "lettaStartupLogProtocol": 1,
5
5
  "description": "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
6
6
  "type": "module",
@@ -0,0 +1,105 @@
1
+ ---
2
+ name: curating-memory-palace
3
+ description: Curates the Memory Palace, a one-page view in palace/ where the user reviews and acts on agent state. Use whenever you have something the user should review in one place, such as tasks, suggested improvements, open questions, follow-ups, blockers, or changes in your understanding; during reflection; when asked to set up or update the Palace; and when handling a Palace action or reply.
4
+ ---
5
+
6
+ # Curating the Memory Palace
7
+
8
+ Treat the Memory Palace as a command center for state: make your ongoing understanding, commitments, and possibilities legible to the user, and let them influence it through actions and replies. Desktop shows it on the Palace tab of the Memory page.
9
+
10
+ Choose contents that fit the agent and its relationship with the user. A companion might share relational understanding, shared interests, or unresolved questions. A work agent might surface pending work, proactively identified issues, or tasks it can resume. Neither is the universal template.
11
+
12
+ Show meaningful current state, not a transcript recap or a page about maintaining the Palace itself. Keep it concise without forcing everything into a task list.
13
+
14
+ ## When to write to it
15
+
16
+ The Palace is where you put things for the user to review. Update it as you go, not only when asked:
17
+
18
+ - **During work:** when you notice a task, an improvement you could make, a question only the user can answer, a blocker, or a follow-up, add it to the right section. Mention it in the conversation too if it matters now.
19
+ - **During reflection:** check the Palace against what happened. Add new items, update changed ones, and remove items that are done, stale, or dismissed.
20
+ - **When the user asks** to set up or update the Palace: set up means build a first Palace from what you already know; update means bring every section current.
21
+ - **After a Palace action or reply:** update the section it came from.
22
+
23
+ Skip it for things that only matter inside the current conversation.
24
+
25
+ ## Files
26
+
27
+ The Palace is the `palace/` folder in your memory.
28
+
29
+ - Each Markdown file directly inside `palace/` is one section, except `palace/MEMORY.md`. Nested folders and other files are not shown.
30
+ - The section title comes from the file name: `needs-attention.md` shows as "Needs Attention". Pick file names that read well as titles, and keep `name` the same as the title.
31
+ - Frontmatter holds exactly `name` and `description`, like every memory file. MemFS rejects any other key. The description shows under the title, so write it as the section's purpose in one short line.
32
+ - The body is Markdown. The date beside the title is the file's last commit.
33
+
34
+ Example `palace/needs-attention.md`:
35
+
36
+ ````markdown
37
+ ---
38
+ name: Needs Attention
39
+ description: Decisions and follow-ups that need the user.
40
+ ---
41
+ **The staging deploy is blocked on a secret.** `SLACK_SIGNING_SECRET` is not in 1Password yet, so the Atlantis plan fails.
42
+
43
+ ```palace-action
44
+ {"actionId": "add-secret", "label": "Walk me through adding it", "instruction": "List the exact 1Password and Terraform steps"}
45
+ ```
46
+ ````
47
+
48
+ ## Order
49
+
50
+ `palace/MEMORY.md` is the folder's index, and it sets the order. List the sections in the order you want them shown. Relative links such as `[Overview](overview.md)` work, and so do `./overview.md` and `palace/overview.md`. The first mention of a file decides its place. Sections you leave out come after, in file-name order.
51
+
52
+ Like every `MEMORY.md`, it has no frontmatter:
53
+
54
+ ```markdown
55
+ # Memory Palace
56
+
57
+ State worth understanding, discussing, or acting on. Choose sections for this user; these are examples, not required categories.
58
+
59
+ - [Needs Attention](needs-attention.md) - Decisions and follow-ups that need the user
60
+ - [Suggestions](suggestions.md) - Next steps worth taking
61
+ - [Recently Learned](recently-learned.md) - What changed in my understanding
62
+ - [Overview](overview.md) - Who I am and what I am working on
63
+ ```
64
+
65
+ ## Icons
66
+
67
+ Each section gets an icon picked from its title. Titles with "attention" or "blocker" get a flag, "overview" or "summary" a house, "learned" or "insight" a lightbulb, and "suggestions" or "next steps" a bolt. Other titles get a note icon.
68
+
69
+ ## Action buttons
70
+
71
+ A `palace-action` code block becomes a button. It holds one strict JSON object:
72
+
73
+ ```palace-action
74
+ {"actionId": "review-pr", "label": "Review the PR", "conversationId": "new", "instruction": "Review PR 123 and list the blocking issues"}
75
+ ```
76
+
77
+ - `actionId` (required): 1 to 64 characters, no spaces. Unique within the section.
78
+ - `label` (required): the button text, up to 80 characters. Start it with a verb.
79
+ - `instruction` (optional): what you should do when it is clicked, up to 300 characters.
80
+ - `conversationId` (optional): one of your conversation ids, or `new` for a fresh conversation. Leave it out to use the main chat.
81
+
82
+ Any other key, or invalid JSON, shows the block as code with an error instead of a button. Action blocks next to each other share one row.
83
+
84
+ When the user clicks a button, Desktop opens the target conversation and sends you one message. A hidden system reminder in it names the action, the section's path, and the section's current content. Do what the label and instruction ask, in that conversation.
85
+
86
+ ## Replies
87
+
88
+ Every section has a Reply button. The user's note arrives in the main chat, or in the conversation named by the section's first action. A hidden system reminder in the message holds the section's path and content. Treat the note as feedback on that section:
89
+
90
+ - **Dismissal** ("drop this", "I don't care about X"): remove that item from the file. If the section is then empty, say so in one line, such as "Nothing needs you right now.", or delete the file and its index line. Don't bring the item back unless something important changes. Note the preference in memory so later updates don't add it again.
91
+ - **Correction**: fix the section.
92
+ - **Question**: answer in the conversation, and update the section if the answer changes it.
93
+
94
+ Then reply briefly with what you changed.
95
+
96
+ ## Writing a good Palace
97
+
98
+ - Let sections reflect what matters in this relationship. Do not fill a fixed template or invent content to reach a section count.
99
+ - Lead with the point and enough context to understand it. Prefer short sections; use headlines where they help scanning.
100
+ - Keep state current. Remove stale items and empty categories unless their absence is itself useful information.
101
+ - Distinguish what the user told you, what you infer, and what you propose. Do not present interpretations as facts or possible work as already underway.
102
+ - Make continuation offers concrete: name the unfinished work and the next step, not just "I can continue."
103
+ - Add a button only for a useful, specific action. Understanding or correcting your state can be the whole purpose of a section; Reply is enough.
104
+ - Never put secrets, tokens, or keys in the Palace. It is shown in the app and sent in messages.
105
+ - Don't repeat an item in two sections.