@letta-ai/letta-code 0.30.31 → 0.31.0

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.
Files changed (80) hide show
  1. package/dist/agent-presets-agent-presets.js +11 -0
  2. package/dist/agent-presets-agent-presets.js.map +9 -0
  3. package/dist/agent-presets-personality-asset-content.js +42 -0
  4. package/dist/agent-presets-personality-asset-content.js.map +10 -0
  5. package/dist/agent-presets.js +17 -2
  6. package/dist/agent-presets.js.map +3 -3
  7. package/dist/channels-public.js +16 -1
  8. package/dist/channels-public.js.map +5 -4
  9. package/dist/gateway-core.js +15 -7
  10. package/dist/gateway-core.js.map +3 -3
  11. package/dist/mcp-client.js +2 -2
  12. package/dist/mcp-client.js.map +1 -1
  13. package/dist/types/agent/create-agent-request.d.ts +3 -0
  14. package/dist/types/agent/create-agent-request.d.ts.map +1 -1
  15. package/dist/types/agent/model.d.ts.map +1 -1
  16. package/dist/types/agent/personality-asset-content.d.ts +3 -0
  17. package/dist/types/agent/personality-asset-content.d.ts.map +1 -0
  18. package/dist/types/agent/skills.d.ts.map +1 -1
  19. package/dist/types/backend/api/ephemeral-conversations.d.ts +15 -0
  20. package/dist/types/backend/api/ephemeral-conversations.d.ts.map +1 -0
  21. package/dist/types/channels/gateway-core.d.ts.map +1 -1
  22. package/dist/types/channels/route-thread-key.d.ts +20 -0
  23. package/dist/types/channels/route-thread-key.d.ts.map +1 -0
  24. package/dist/types/channels-public.d.ts +1 -0
  25. package/dist/types/channels-public.d.ts.map +1 -1
  26. package/dist/types/providers/byok-providers.d.ts.map +1 -1
  27. package/dist/types/tools/impl/exec-command.d.ts.map +1 -1
  28. package/dist/types/tools/impl/shell-runner.d.ts +1 -0
  29. package/dist/types/tools/impl/shell-runner.d.ts.map +1 -1
  30. package/dist/types/tools/impl/skill.d.ts.map +1 -1
  31. package/dist/types/tools/toolset.d.ts +1 -1
  32. package/dist/types/tools/toolset.d.ts.map +1 -1
  33. package/dist/types/types/external-tool-protocol.d.ts +2 -2
  34. package/dist/types/types/external-tool-protocol.d.ts.map +1 -1
  35. package/dist/types/types/protocol_v2.d.ts +30 -190
  36. package/dist/types/types/protocol_v2.d.ts.map +1 -1
  37. package/dist/types/types/runtime-scope.d.ts +4 -2
  38. package/dist/types/types/runtime-scope.d.ts.map +1 -1
  39. package/dist/types/types/runtime-start-protocol.d.ts +21 -0
  40. package/dist/types/types/runtime-start-protocol.d.ts.map +1 -0
  41. package/dist/types/types/schedule-protocol.d.ts +151 -1
  42. package/dist/types/types/schedule-protocol.d.ts.map +1 -1
  43. package/dist/types/utils/frontmatter.d.ts.map +1 -1
  44. package/dist/types/websocket/listener/cwd-change.d.ts.map +1 -1
  45. package/dist/types/websocket/listener/protocol-outbound.d.ts +1 -1
  46. package/dist/types/websocket/listener/protocol-outbound.d.ts.map +1 -1
  47. package/dist/types/websocket/listener/scope.d.ts +1 -1
  48. package/dist/types/websocket/listener/scope.d.ts.map +1 -1
  49. package/letta.js +1230 -511
  50. package/package.json +5 -3
  51. package/scripts/builtin-skills-watch/agent-watch.test.ts +104 -0
  52. package/scripts/builtin-skills-watch/agent-watch.ts +337 -0
  53. package/scripts/builtin-skills-watch/aggregate-results.test.ts +79 -0
  54. package/scripts/builtin-skills-watch/aggregate-results.ts +326 -0
  55. package/scripts/builtin-skills-watch/analysis.test.ts +70 -0
  56. package/scripts/builtin-skills-watch/analysis.ts +228 -0
  57. package/scripts/builtin-skills-watch/evidence.test.ts +114 -0
  58. package/scripts/builtin-skills-watch/evidence.ts +145 -0
  59. package/scripts/builtin-skills-watch/github.ts +161 -0
  60. package/scripts/builtin-skills-watch/tracker.test.ts +249 -0
  61. package/scripts/builtin-skills-watch/tracker.ts +506 -0
  62. package/scripts/builtin-skills-watch/update-tracker.test.ts +234 -0
  63. package/scripts/builtin-skills-watch/update-tracker.ts +508 -0
  64. package/scripts/claude-watch/release-source.test.ts +16 -4
  65. package/scripts/claude-watch/release-source.ts +41 -2
  66. package/scripts/codex-watch/agent-watch.ts +8 -5
  67. package/scripts/codex-watch/release-analysis.test.ts +21 -27
  68. package/scripts/codex-watch/release-analysis.ts +30 -10
  69. package/scripts/codex-watch/tracker.test.ts +12 -33
  70. package/scripts/codex-watch/tracker.ts +30 -22
  71. package/scripts/pi-ai-watch/agent-watch.ts +9 -14
  72. package/scripts/pi-ai-watch/release-analysis.test.ts +24 -18
  73. package/scripts/pi-ai-watch/release-analysis.ts +85 -45
  74. package/scripts/pi-ai-watch/tracker.test.ts +25 -18
  75. package/scripts/pi-ai-watch/tracker.ts +7 -19
  76. package/scripts/pi-ai-watch/update-tracker.ts +2 -2
  77. package/scripts/source-file-size-baseline.json +3 -3
  78. package/skills/dispatching-coding-agents/SKILL.md +16 -7
  79. package/skills/self-configuration/SKILL.md +20 -1
  80. package/skills/syncing-memory-filesystem/SKILL.md +118 -226
@@ -42,11 +42,14 @@ export interface PiAiWatchAnalysis {
42
42
  installed_version: string;
43
43
  previous_version: string;
44
44
  current_version: string;
45
- is_adjacent_release: boolean;
45
+ is_latest_release: boolean;
46
46
  published_at: string;
47
47
  integrity: string | null;
48
48
  tarball_url: string | null;
49
+ previous_git_head: string | null;
49
50
  git_head: string | null;
51
+ previous_tag_commit: string;
52
+ current_tag_commit: string;
50
53
  release_url: string;
51
54
  compare_url: string;
52
55
  changelog_md: string;
@@ -79,6 +82,11 @@ export async function analyzePiAiRelease(
79
82
  `Could not find previous pi-ai release before ${current.version}`,
80
83
  );
81
84
  }
85
+ if (compareStableVersions(previous.version, current.version) >= 0) {
86
+ throw new Error(
87
+ `Previous pi-ai release ${previous.version} must precede ${current.version}`,
88
+ );
89
+ }
82
90
 
83
91
  const installedVersion = options.installedVersion ?? readInstalledVersion();
84
92
  const previousTag = `v${previous.version}`;
@@ -86,11 +94,13 @@ export async function analyzePiAiRelease(
86
94
  const temp = mkdtempSync(join(tmpdir(), "pi-ai-watch-"));
87
95
  try {
88
96
  const repoDir = clonePi(temp);
89
- fetchTag(repoDir, previousTag);
90
- fetchTag(repoDir, currentTag);
91
- verifyTagMatchesPackage(repoDir, previousTag, previous);
92
- verifyTagMatchesPackage(repoDir, currentTag, current);
93
- const changelog = showFile(repoDir, currentTag, "packages/ai/CHANGELOG.md");
97
+ const previousSource = resolveReleaseSource(repoDir, previousTag, previous);
98
+ const currentSource = resolveReleaseSource(repoDir, currentTag, current);
99
+ const changelog = showFile(
100
+ repoDir,
101
+ currentSource.sourceCommit,
102
+ "packages/ai/CHANGELOG.md",
103
+ );
94
104
  if (changelog === null) {
95
105
  throw new Error(`packages/ai/CHANGELOG.md is missing at ${currentTag}`);
96
106
  }
@@ -100,24 +110,35 @@ export async function analyzePiAiRelease(
100
110
  installed_version: installedVersion,
101
111
  previous_version: previous.version,
102
112
  current_version: current.version,
103
- is_adjacent_release: areAdjacentStableReleases(
104
- releases,
105
- previous.version,
106
- current.version,
107
- ),
113
+ is_latest_release: current.version === releases.at(-1)?.version,
108
114
  published_at: current.published_at,
109
115
  integrity: current.integrity,
110
116
  tarball_url: current.tarball_url,
117
+ previous_git_head: previous.git_head,
111
118
  git_head: current.git_head,
119
+ previous_tag_commit: previousSource.tagCommit,
120
+ current_tag_commit: currentSource.tagCommit,
112
121
  release_url: `https://github.com/${PI_AI_REPO}/releases/tag/${currentTag}`,
113
- compare_url: `https://github.com/${PI_AI_REPO}/compare/${previousTag}...${currentTag}`,
114
- changelog_md: extractChangelogSection(changelog, current.version),
115
- changed_files: changedFiles(repoDir, previousTag, currentTag),
116
- diff_stat: diffStat(repoDir, previousTag, currentTag),
122
+ compare_url: `https://github.com/${PI_AI_REPO}/compare/${previousSource.sourceCommit}...${currentSource.sourceCommit}`,
123
+ changelog_md: extractChangelogRange(
124
+ changelog,
125
+ previous.version,
126
+ current.version,
127
+ ),
128
+ changed_files: changedFiles(
129
+ repoDir,
130
+ previousSource.sourceCommit,
131
+ currentSource.sourceCommit,
132
+ ),
133
+ diff_stat: diffStat(
134
+ repoDir,
135
+ previousSource.sourceCommit,
136
+ currentSource.sourceCommit,
137
+ ),
117
138
  package_json_diff: diffPreview(
118
139
  repoDir,
119
- previousTag,
120
- currentTag,
140
+ previousSource.sourceCommit,
141
+ currentSource.sourceCommit,
121
142
  "packages/ai/package.json",
122
143
  ),
123
144
  workflow_run_url: workflowRunUrl(),
@@ -182,7 +203,7 @@ export function readInstalledVersion(lockfilePath = "bun.lock"): string {
182
203
  return match[1];
183
204
  }
184
205
 
185
- export function findNextStableRelease(
206
+ export function findLatestStableReleaseAfter(
186
207
  releases: PackageRelease[],
187
208
  cursorVersion: string,
188
209
  ): PackageRelease | null {
@@ -192,33 +213,33 @@ export function findNextStableRelease(
192
213
  if (index < 0) {
193
214
  throw new Error(`Could not find pi-ai cursor release ${cursorVersion}`);
194
215
  }
195
- return releases[index + 1] ?? null;
216
+ return index === releases.length - 1 ? null : (releases.at(-1) ?? null);
196
217
  }
197
218
 
198
- export function areAdjacentStableReleases(
199
- releases: PackageRelease[],
219
+ export function extractChangelogRange(
220
+ changelog: string,
200
221
  previousVersion: string,
201
222
  currentVersion: string,
202
- ): boolean {
203
- return (
204
- findPreviousStableRelease(releases, currentVersion)?.version ===
205
- previousVersion
206
- );
207
- }
208
-
209
- export function extractChangelogSection(
210
- changelog: string,
211
- version: string,
212
223
  ): string {
213
- const heading = new RegExp(`^## \\[${escapeRegExp(version)}\\].*$`, "m");
214
- const match = heading.exec(changelog);
215
- if (!match)
216
- throw new Error(`Could not find pi-ai changelog section ${version}`);
217
- const start = match.index;
218
- const remaining = changelog.slice(start + match[0].length);
219
- const next = /^## \[/m.exec(remaining);
220
- const end = next ? start + match[0].length + next.index : changelog.length;
221
- return changelog.slice(start, end).trim();
224
+ const currentHeading = new RegExp(
225
+ `^## \\[${escapeRegExp(currentVersion)}\\].*$`,
226
+ "m",
227
+ ).exec(changelog);
228
+ if (!currentHeading) {
229
+ throw new Error(`Could not find pi-ai changelog section ${currentVersion}`);
230
+ }
231
+ const previousHeading = new RegExp(
232
+ `^## \\[${escapeRegExp(previousVersion)}\\].*$`,
233
+ "m",
234
+ ).exec(changelog.slice(currentHeading.index + currentHeading[0].length));
235
+ if (!previousHeading) {
236
+ throw new Error(
237
+ `Could not find pi-ai changelog section ${previousVersion}`,
238
+ );
239
+ }
240
+ const end =
241
+ currentHeading.index + currentHeading[0].length + previousHeading.index;
242
+ return changelog.slice(currentHeading.index, end).trim();
222
243
  }
223
244
 
224
245
  function findPreviousStableRelease(
@@ -276,18 +297,37 @@ function fetchTag(repoDir: string, tag: string): void {
276
297
  );
277
298
  }
278
299
 
279
- function verifyTagMatchesPackage(
300
+ function resolveReleaseSource(
280
301
  repoDir: string,
281
302
  tag: string,
282
303
  release: PackageRelease,
283
- ): void {
284
- if (!release.git_head) return;
304
+ ): { sourceCommit: string; tagCommit: string } {
305
+ fetchTag(repoDir, tag);
285
306
  const tagCommit = git(["rev-list", "-n", "1", tag], repoDir).trim();
286
- if (tagCommit !== release.git_head) {
307
+ const sourceCommit = release.git_head ?? tagCommit;
308
+ if (sourceCommit !== tagCommit) {
309
+ fetchCommit(repoDir, sourceCommit);
310
+ }
311
+ const packageJson = showFile(
312
+ repoDir,
313
+ sourceCommit,
314
+ "packages/ai/package.json",
315
+ );
316
+ if (packageJson === null) {
317
+ throw new Error(`packages/ai/package.json is missing at ${sourceCommit}`);
318
+ }
319
+ const sourceVersion = (JSON.parse(packageJson) as { version?: unknown })
320
+ .version;
321
+ if (sourceVersion !== release.version) {
287
322
  throw new Error(
288
- `pi-ai npm ${release.version} gitHead ${release.git_head} does not match ${tag} commit ${tagCommit}`,
323
+ `pi-ai npm ${release.version} source ${sourceCommit} declares version ${String(sourceVersion)}`,
289
324
  );
290
325
  }
326
+ return { sourceCommit, tagCommit };
327
+ }
328
+
329
+ function fetchCommit(repoDir: string, commit: string): void {
330
+ git(["fetch", "--filter=blob:none", "origin", commit], repoDir);
291
331
  }
292
332
 
293
333
  function showFile(repoDir: string, tag: string, path: string): string | null {
@@ -1,7 +1,6 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
  import type { PiAiWatchAnalysis } from "./release-analysis.ts";
3
3
  import {
4
- advanceMergedPr,
5
4
  getPendingPrForCursor,
6
5
  hasCompletedRange,
7
6
  hasRecordedOutcome,
@@ -19,17 +18,18 @@ describe("pi-ai watch tracker", () => {
19
18
 
20
19
  test("advances after a no-upgrade decision", () => {
21
20
  const state = recordAnalysis(initialTrackerState("0.82.1"), {
22
- analysis: analysis("0.82.1", "0.83.0"),
21
+ analysis: analysis("0.82.1", "0.84.0"),
23
22
  outcome: "no_upgrade",
24
23
  notes: "upstream-only provider change",
25
24
  processedAt: "2026-08-21T00:00:00Z",
26
25
  });
27
- expect(state.audit_cursor_version).toBe("0.83.0");
28
- expect(hasCompletedRange(state, "0.82.1", "0.83.0")).toBe(true);
29
- expect(hasRecordedOutcome(state, "0.82.1", "0.83.0")).toBe(true);
26
+ expect(state.audit_cursor_version).toBe("0.84.0");
27
+ expect(hasCompletedRange(state, "0.82.1", "0.84.0")).toBe(true);
28
+ expect(hasRecordedOutcome(state, "0.82.1", "0.84.0")).toBe(true);
29
+ expect(renderTrackerBody(state)).toContain("[0.82.1...0.84.0]");
30
30
  });
31
31
 
32
- test("keeps a created PR pending until it merges", () => {
32
+ test("advances a created PR and keeps it pending at the cursor", () => {
33
33
  const pending = recordAnalysis(initialTrackerState("0.82.1"), {
34
34
  analysis: analysis("0.82.1", "0.83.0"),
35
35
  outcome: "pr_created",
@@ -37,14 +37,18 @@ describe("pi-ai watch tracker", () => {
37
37
  prUrl: "https://github.com/letta-ai/letta-code/pull/123",
38
38
  processedAt: "2026-08-21T00:00:00Z",
39
39
  });
40
- expect(pending.audit_cursor_version).toBe("0.82.1");
40
+ expect(pending.audit_cursor_version).toBe("0.83.0");
41
41
  expect(getPendingPrForCursor(pending)?.version).toBe("0.83.0");
42
- expect(hasCompletedRange(pending, "0.82.1", "0.83.0")).toBe(false);
42
+ expect(hasCompletedRange(pending, "0.82.1", "0.83.0")).toBe(true);
43
43
  expect(hasRecordedOutcome(pending, "0.82.1", "0.83.0")).toBe(true);
44
44
 
45
- const merged = advanceMergedPr(pending, "0.83.0");
46
- expect(merged.audit_cursor_version).toBe("0.83.0");
47
- expect(getPendingPrForCursor(merged)).toBeNull();
45
+ const later = recordAnalysis(pending, {
46
+ analysis: analysis("0.83.0", "0.84.0"),
47
+ outcome: "no_upgrade",
48
+ notes: "later release range",
49
+ });
50
+ expect(later.audit_cursor_version).toBe("0.84.0");
51
+ expect(getPendingPrForCursor(later)).toBeNull();
48
52
  });
49
53
 
50
54
  test("keeps errors retryable and replaces them with a later outcome", () => {
@@ -68,16 +72,16 @@ describe("pi-ai watch tracker", () => {
68
72
  expect(retried.processed[0]?.outcome).toBe("no_upgrade");
69
73
  });
70
74
 
71
- test("advances uncertain releases but not explicit non-adjacent replays", () => {
75
+ test("advances cumulative reviews but not replays behind the cursor", () => {
72
76
  const human = recordAnalysis(initialTrackerState("0.82.1"), {
73
- analysis: analysis("0.82.1", "0.83.0"),
77
+ analysis: analysis("0.82.1", "0.84.0"),
74
78
  outcome: "needs_human_review",
75
79
  notes: "product decision",
76
80
  });
77
- expect(human.audit_cursor_version).toBe("0.83.0");
81
+ expect(human.audit_cursor_version).toBe("0.84.0");
78
82
 
79
83
  const replay = recordAnalysis(initialTrackerState("0.82.1"), {
80
- analysis: analysis("0.82.1", "0.84.0", false),
84
+ analysis: analysis("0.82.1", "0.83.0", false),
81
85
  outcome: "no_upgrade",
82
86
  notes: "explicit replay",
83
87
  });
@@ -101,7 +105,7 @@ describe("pi-ai watch tracker", () => {
101
105
  const previous = `0.${index}.0`;
102
106
  const current = `0.${index}.1`;
103
107
  state = recordAnalysis(state, {
104
- analysis: analysis(previous, current, false),
108
+ analysis: analysis(previous, current),
105
109
  outcome: "error",
106
110
  notes: `attempt ${index}`,
107
111
  processedAt: `2026-08-21T00:${String(index).padStart(2, "0")}:00Z`,
@@ -115,18 +119,21 @@ describe("pi-ai watch tracker", () => {
115
119
  function analysis(
116
120
  previousVersion: string,
117
121
  currentVersion: string,
118
- adjacent = true,
122
+ isLatestRelease = true,
119
123
  ): PiAiWatchAnalysis {
120
124
  return {
121
125
  package: "@earendil-works/pi-ai",
122
126
  installed_version: "0.82.1",
123
127
  previous_version: previousVersion,
124
128
  current_version: currentVersion,
125
- is_adjacent_release: adjacent,
129
+ is_latest_release: isLatestRelease,
126
130
  published_at: "2026-08-14T00:00:00Z",
127
131
  integrity: "sha512-test",
128
132
  tarball_url: "https://example.test/pi-ai.tgz",
133
+ previous_git_head: "previous123",
129
134
  git_head: "abc123",
135
+ previous_tag_commit: "previous123",
136
+ current_tag_commit: "abc123",
130
137
  release_url: `https://github.com/earendil-works/pi/releases/tag/v${currentVersion}`,
131
138
  compare_url: `https://github.com/earendil-works/pi/compare/v${previousVersion}...v${currentVersion}`,
132
139
  changelog_md: "## Added",
@@ -87,9 +87,9 @@ export function recordAnalysis(
87
87
  ].slice(0, HISTORY_LIMIT);
88
88
 
89
89
  const advancesCursor =
90
- options.analysis.is_adjacent_release &&
90
+ options.analysis.is_latest_release &&
91
91
  entry.previous_version === state.audit_cursor_version &&
92
- (entry.outcome === "no_upgrade" || entry.outcome === "needs_human_review");
92
+ entry.outcome !== "error";
93
93
 
94
94
  return {
95
95
  audit_cursor_version: advancesCursor
@@ -123,8 +123,7 @@ export function hasCompletedRange(
123
123
  (entry) =>
124
124
  entry.previous_version === previousVersion &&
125
125
  entry.version === currentVersion &&
126
- (entry.outcome === "no_upgrade" ||
127
- entry.outcome === "needs_human_review"),
126
+ entry.outcome !== "error",
128
127
  );
129
128
  }
130
129
 
@@ -134,24 +133,13 @@ export function getPendingPrForCursor(
134
133
  return (
135
134
  state.processed.find(
136
135
  (entry) =>
137
- entry.previous_version === state.audit_cursor_version &&
136
+ entry.version === state.audit_cursor_version &&
138
137
  entry.outcome === "pr_created" &&
139
138
  entry.pr_url,
140
139
  ) ?? null
141
140
  );
142
141
  }
143
142
 
144
- export function advanceMergedPr(
145
- state: TrackerState,
146
- currentVersion: string,
147
- ): TrackerState {
148
- const pending = getPendingPrForCursor(state);
149
- if (!pending || pending.version !== currentVersion) {
150
- throw new Error(`No pending pi-ai PR for ${currentVersion}`);
151
- }
152
- return { ...state, audit_cursor_version: currentVersion };
153
- }
154
-
155
143
  export function renderTrackerBody(state: TrackerState): string {
156
144
  const normalized = normalizeState(state);
157
145
  return `${[
@@ -166,7 +154,7 @@ export function renderTrackerBody(state: TrackerState): string {
166
154
  "",
167
155
  "## Hidden state",
168
156
  "",
169
- "The workflow uses the hidden JSON block below for ordered release processing and dedupe.",
157
+ "The workflow uses the hidden JSON block below for cumulative release processing and dedupe.",
170
158
  "",
171
159
  serializeTrackerState(normalized),
172
160
  ].join("\n")}\n`;
@@ -193,12 +181,12 @@ function renderTable(state: TrackerState): string {
193
181
  if (entries.length === 0) return "_No pi-ai releases reviewed yet._";
194
182
 
195
183
  const rows = [
196
- "| Release | Installed | Outcome | PR | Notes |",
184
+ "| Range | Installed | Outcome | PR | Notes |",
197
185
  "|---|---|---|---|---|",
198
186
  ];
199
187
  for (const entry of entries) {
200
188
  rows.push(
201
- `| [${entry.version}](${entry.compare_url}) | ${entry.installed_version} | ${entry.outcome} | ${renderPr(entry.pr_url)} | ${escapeTable(entry.notes)} |`,
189
+ `| [${entry.previous_version}...${entry.version}](${entry.compare_url}) | ${entry.installed_version} | ${entry.outcome} | ${renderPr(entry.pr_url)} | ${escapeTable(entry.notes)} |`,
202
190
  );
203
191
  }
204
192
  return rows.join("\n");
@@ -202,13 +202,13 @@ function parseOutcome(value: string | undefined): TrackerOutcome {
202
202
  function defaultNotes(outcome: TrackerOutcome): string {
203
203
  switch (outcome) {
204
204
  case "no_upgrade":
205
- return "reviewed; no upgrade needed for this release";
205
+ return "reviewed; no upgrade needed for this release range";
206
206
  case "pr_created":
207
207
  return "opened pi-ai dependency upgrade PR";
208
208
  case "needs_human_review":
209
209
  return "needs human review";
210
210
  case "error":
211
- return "automation hit an error; retry this release";
211
+ return "automation hit an error; retry this release range";
212
212
  }
213
213
  }
214
214
 
@@ -37,13 +37,13 @@
37
37
  "src/settings-manager.ts": 2102,
38
38
  "src/tools/manager.ts": 2993,
39
39
  "src/tools/tool-execution-context.test.ts": 1138,
40
- "src/types/protocol_v2.ts": 2848,
40
+ "src/types/protocol_v2.ts": 2666,
41
41
  "src/websocket/listen-client-concurrency.test.ts": 2686,
42
42
  "src/websocket/listen-client-protocol.test.ts": 5819,
43
43
  "src/websocket/listener/commands/memory.ts": 1114,
44
44
  "src/websocket/listener/file-commands.ts": 1053,
45
45
  "src/websocket/listener/lifecycle.ts": 1048,
46
- "src/websocket/listener/protocol-inbound.ts": 2242,
46
+ "src/websocket/listener/protocol-inbound.ts": 2227,
47
47
  "src/websocket/listener/protocol-outbound.ts": 1047,
48
- "src/websocket/listener/turn.ts": 1070
48
+ "src/websocket/listener/turn.ts": 1058
49
49
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: dispatching-coding-agents
3
- description: Dispatch stateless coding agents (Claude Code or Codex) via Bash. Use when you're stuck, need a second opinion, or need parallel research on a hard problem. They have no memory you must provide all context.
3
+ description: Dispatch stateless coding agents through Claude Code or Codex. Model catalog checked 2026-08-25: Claude Code includes Sonnet 5, Opus 5, and Fable 5; Codex includes GPT-5.6 Luna, Terra, and Sol. Use when you're stuck, need a second opinion, or need parallel research on a hard problem. They have no memory, so you must provide all context.
4
4
  ---
5
5
 
6
6
  # Dispatching Coding Agents
@@ -43,14 +43,23 @@ Different agents have different strengths. Track what works in your memory over
43
43
 
44
44
  ### Categories
45
45
 
46
- **Codex:**
47
- - Use the configured default model. Model catalogs and account access change frequently, so only pass `--model` when the user explicitly requests one.
46
+ **Codex (catalog checked 2026-08-25):**
47
+ - `gpt-5.6-luna`: high-throughput, lower-latency work
48
+ - `gpt-5.6-terra`: strong performance with a balance of intelligence and cost
49
+ - `gpt-5.6-sol`: quality-first flagship for difficult coding and reasoning
50
+ - Use the configured default unless the user requests a model. Pass a requested model with `-m`, for example `codex exec "YOUR PROMPT" -m gpt-5.6-sol --sandbox workspace-write`.
48
51
  - If a requested model is rejected, inspect the installed CLI and account configuration rather than guessing another model name.
49
52
 
50
- **Claude Code:**
51
- - `opus` Excellent writer. Best for docs, refactors, open-ended tasks, and vague instructions.
52
- - Strengths: Excellent writer, understands vague instructions, excellent for coding but also general-purpose
53
- - Weaknesses: Tends to generate "slop", writing excessive quantities of code unnecessarily. Can hang on large repos.
53
+ **Claude Code (catalog checked 2026-08-25):**
54
+ - The current flagship models are Sonnet 5, Opus 5, and Fable 5. Use the configured default unless the user requests a model.
55
+ - Claude Code's stable aliases follow its current catalog:
56
+ - `best`: highest-capability model available to the account, using Fable 5 when available
57
+ - `fable`: Fable 5 for the hardest, longest-running tasks
58
+ - `opus`: Opus 5 for complex reasoning, docs, refactors, and open-ended tasks
59
+ - `sonnet`: Sonnet 5 for everyday coding
60
+ - `haiku`: fast, simple tasks
61
+ - Pass an alias with `--model`, for example `claude -p "YOUR PROMPT" --model fable --dangerously-skip-permissions`.
62
+ - Aliases update as Claude Code's catalog changes. If an alias is rejected, inspect the installed CLI version and account access rather than guessing a versioned model name.
54
63
 
55
64
  ### Cost and speed tradeoffs
56
65
  - Use each CLI's configured default model unless the task requires a model the user explicitly requested
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: self-configuration
3
- description: Inspect or modify Letta Code's own memory, model, context window, system prompt, compaction, permissions, toolsets, mods, skills, channels, schedules, and local runtime settings. Use when the user asks how this agent or conversation is configured, or asks you to change how you behave or how the harness runs you.
3
+ description: Inspect or modify Letta Code's own memory, model, context window, system prompt, compaction, permissions, toolsets, mods, skills, channels, schedules, agent secrets, and local runtime settings. Use when the user asks how this agent or conversation is configured, or asks you to change how you behave or how the harness runs you.
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -22,6 +22,7 @@ The important part is choosing the right layer. Do not smear a preference into d
22
22
  | Skills | Reusable procedural knowledge or bundled scripts | Load `creating-skills` or `acquiring-skills` |
23
23
  | Channels | Slack/Discord/Telegram/WhatsApp/Signal accounts, pairing, routing, listener state | Use `letta channels` or channel commands |
24
24
  | Schedules | Reminders and recurring prompts | Load `scheduling-tasks` and use `letta cron` |
25
+ | Agent secrets | Per-agent `$NAME` credential values for shell commands | Use `letta secret` (or `/secret` in a session) |
25
26
 
26
27
  Decision rule: if the model should remember and reason about it, use memory. If the runtime must enforce it or route it before the model decides anything, use settings, API fields, mods, channels, or schedules.
27
28
 
@@ -370,6 +371,24 @@ Never print provider keys. Shell expansion such as `--api-key "$OPENAI_API_KEY"`
370
371
 
371
372
  After connecting, verify the provider/model from the same backend and process that will run the agent. Do not infer success from a saved credential alone.
372
373
 
374
+ ## Agent secrets
375
+
376
+ Agent-scoped secrets hold credential values that are referenced as `$NAME` in shell commands. Cloud agents store them server-side on the agent; local agents use OS secure storage. The harness substitutes `$NAME` at exec time and scrubs values from tool output, so values never enter agent context.
377
+
378
+ ```bash
379
+ letta secret list # names only, never values
380
+ letta secret set GITHUB_TOKEN --env GITHUB_TOKEN # ingest from the environment
381
+ openssl rand -hex 32 | letta secret set WEBHOOK_TOKEN --stdin # generate without seeing the value
382
+ letta secret unset GITHUB_TOKEN # aliases: delete | remove | rm
383
+ ```
384
+
385
+ Rules:
386
+
387
+ - Pass the source variable *name* to `--env`, not `$NAME`. `--env $GITHUB_TOKEN` triggers harness substitution and places the resolved value in process arguments; `--env GITHUB_TOKEN` reads it from the CLI process environment without exposure.
388
+ - Never echo secret values into tool output. Pipe generated credentials straight into `--stdin`.
389
+ - Inside a session, `AGENT_ID`/`LETTA_AGENT_ID` resolves the target automatically; pass `--agent <agent-id>` otherwise.
390
+ - A running session loads its secret cache at startup; CLI-side changes apply to new sessions. The `/secret` slash command manages the same store interactively and refreshes the live cache.
391
+
373
392
  ## Channels
374
393
 
375
394
  Use channels when the user wants to talk through Slack, Discord, Telegram, WhatsApp, or Signal.