@letta-ai/letta-code 0.31.5 → 0.31.7

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 (48) hide show
  1. package/dist/agent-presets.js +5 -1
  2. package/dist/agent-presets.js.map +2 -2
  3. package/dist/mcp-client.js +2 -2
  4. package/dist/mcp-client.js.map +1 -1
  5. package/dist/types/agent/attached-repositories.d.ts +7 -0
  6. package/dist/types/agent/attached-repositories.d.ts.map +1 -0
  7. package/dist/types/agent/client-skills.d.ts +1 -1
  8. package/dist/types/agent/client-skills.d.ts.map +1 -1
  9. package/dist/types/agent/memory-constraints.d.ts +22 -0
  10. package/dist/types/agent/memory-constraints.d.ts.map +1 -0
  11. package/dist/types/agent/memory-git-hooks.d.ts +5 -5
  12. package/dist/types/agent/memory-git-hooks.d.ts.map +1 -1
  13. package/dist/types/agent/memory-git.d.ts +36 -5
  14. package/dist/types/agent/memory-git.d.ts.map +1 -1
  15. package/dist/types/agent/shared-memory-skills.d.ts +1 -1
  16. package/dist/types/agent/shared-memory-skills.d.ts.map +1 -1
  17. package/dist/types/backend/backend.d.ts +7 -0
  18. package/dist/types/backend/backend.d.ts.map +1 -1
  19. package/dist/types/backend/dev/pi-model-factory.d.ts +0 -1
  20. package/dist/types/backend/dev/pi-model-factory.d.ts.map +1 -1
  21. package/dist/types/backend/local/local-model-config.d.ts.map +1 -1
  22. package/dist/types/tools/impl/skill.d.ts +1 -1
  23. package/dist/types/tools/impl/skill.d.ts.map +1 -1
  24. package/dist/types/utils/secrets-store.d.ts +4 -0
  25. package/dist/types/utils/secrets-store.d.ts.map +1 -1
  26. package/letta.js +20198 -20036
  27. package/package.json +2 -2
  28. package/scripts/agent-watch/verify-pr-identity.test.ts +97 -0
  29. package/scripts/agent-watch/verify-pr-identity.ts +119 -0
  30. package/scripts/builtin-skills-watch/aggregate-results.ts +13 -8
  31. package/scripts/builtin-skills-watch/evidence.test.ts +61 -17
  32. package/scripts/builtin-skills-watch/evidence.ts +66 -38
  33. package/scripts/builtin-skills-watch/finalize-result.test.ts +125 -0
  34. package/scripts/builtin-skills-watch/finalize-result.ts +44 -0
  35. package/scripts/builtin-skills-watch/reconcile-results.test.ts +58 -0
  36. package/scripts/builtin-skills-watch/reconcile-results.ts +311 -0
  37. package/scripts/builtin-skills-watch/result-artifacts.test.ts +83 -0
  38. package/scripts/builtin-skills-watch/result-artifacts.ts +160 -0
  39. package/scripts/builtin-skills-watch/update-tracker.test.ts +17 -1
  40. package/scripts/builtin-skills-watch/update-tracker.ts +89 -15
  41. package/scripts/claude-watch/update-tracker.test.ts +30 -0
  42. package/scripts/claude-watch/update-tracker.ts +19 -5
  43. package/scripts/codex-watch/update-tracker.test.ts +28 -0
  44. package/scripts/codex-watch/update-tracker.ts +59 -7
  45. package/scripts/source-file-size-baseline.json +1 -1
  46. package/skills/letta-guide/SKILL.md +6 -2
  47. package/skills/managing-shared-memory/SKILL.md +4 -5
  48. package/skills/submitting-feedback/SKILL.md +21 -0
@@ -37,6 +37,7 @@ export interface PullRequestView {
37
37
  files: Array<{ path: string }>;
38
38
  headRefOid: string;
39
39
  isDraft: boolean;
40
+ mergedAt?: string | null;
40
41
  state: string;
41
42
  url: string;
42
43
  }
@@ -337,19 +338,35 @@ export function parseReviewResult(
337
338
  "notes",
338
339
  "pr_url",
339
340
  "evidence",
340
- ]) ||
341
- value.schema_version !== 1 ||
341
+ ])
342
+ ) {
343
+ throw new Error("Review result has unknown or missing fields");
344
+ }
345
+ if (value.schema_version !== 1) {
346
+ throw new Error("Review result must use schema version 1");
347
+ }
348
+ if (
342
349
  value.candidate_id !== analysis.candidate_id ||
343
- value.skill !== analysis.skill ||
344
- (value.outcome !== "no_drift" &&
345
- value.outcome !== "pr_created" &&
346
- value.outcome !== "needs_human_review") ||
350
+ value.skill !== analysis.skill
351
+ ) {
352
+ throw new Error("Review result does not match the pending candidate");
353
+ }
354
+ if (
355
+ value.outcome !== "no_drift" &&
356
+ value.outcome !== "pr_created" &&
357
+ value.outcome !== "needs_human_review"
358
+ ) {
359
+ throw new Error("Review result outcome is invalid");
360
+ }
361
+ if (
347
362
  typeof value.notes !== "string" ||
348
363
  value.notes.length === 0 ||
349
- value.notes.length > 120 ||
350
- (value.pr_url !== null && typeof value.pr_url !== "string")
364
+ value.notes.length > 120
351
365
  ) {
352
- throw new Error("Review result does not match the pending candidate");
366
+ throw new Error("Review result notes must contain 1 to 120 characters");
367
+ }
368
+ if (value.pr_url !== null && typeof value.pr_url !== "string") {
369
+ throw new Error("Review result pr_url must be a string or null");
353
370
  }
354
371
  if (
355
372
  (value.outcome === "pr_created") !==
@@ -381,28 +398,61 @@ export function verifyPullRequest(
381
398
  analysis: BuiltinSkillWatchAnalysis,
382
399
  expectedGithubLogin: string,
383
400
  ): void {
401
+ verifyPullRequestIdentity(expectedGithubLogin);
402
+ const pullRequest = getPullRequest(repo, prUrl);
403
+ validatePullRequestView(pullRequest, prUrl, analysis, expectedGithubLogin);
404
+ verifyPullRequestAncestry(repo, pullRequest, analysis);
405
+ }
406
+
407
+ export function verifyReconciledPullRequest(
408
+ repo: string,
409
+ prUrl: string,
410
+ analysis: BuiltinSkillWatchAnalysis,
411
+ expectedGithubLogin: string,
412
+ ): void {
413
+ verifyPullRequestIdentity(expectedGithubLogin);
414
+ const pullRequest = getPullRequest(repo, prUrl);
415
+ validateReconciledPullRequestView(
416
+ pullRequest,
417
+ prUrl,
418
+ analysis,
419
+ expectedGithubLogin,
420
+ );
421
+ verifyPullRequestAncestry(repo, pullRequest, analysis);
422
+ }
423
+
424
+ function verifyPullRequestIdentity(expectedGithubLogin: string): void {
384
425
  const authenticatedLogin = ghJson<{ login: string }>(["api", "user"]).login;
385
426
  if (authenticatedLogin !== expectedGithubLogin) {
386
427
  throw new Error(
387
428
  `Authenticated GitHub login ${authenticatedLogin} does not match ${expectedGithubLogin}`,
388
429
  );
389
430
  }
431
+ }
432
+
433
+ function getPullRequest(repo: string, prUrl: string): PullRequestView {
390
434
  const match = prUrl.match(
391
435
  /^https:\/\/github\.com\/([^/]+\/[^/]+)\/pull\/(\d+)\/?$/,
392
436
  );
393
437
  if (!match || match[1] !== repo) {
394
438
  throw new Error(`PR URL must belong to https://github.com/${repo}`);
395
439
  }
396
- const pullRequest = ghJson<PullRequestView>([
440
+ return ghJson<PullRequestView>([
397
441
  "pr",
398
442
  "view",
399
443
  match[2] as string,
400
444
  "--repo",
401
445
  repo,
402
446
  "--json",
403
- "author,baseRefName,body,files,headRefOid,isDraft,state,url",
447
+ "author,baseRefName,body,files,headRefOid,isDraft,mergedAt,state,url",
404
448
  ]);
405
- validatePullRequestView(pullRequest, prUrl, analysis, expectedGithubLogin);
449
+ }
450
+
451
+ function verifyPullRequestAncestry(
452
+ repo: string,
453
+ pullRequest: PullRequestView,
454
+ analysis: BuiltinSkillWatchAnalysis,
455
+ ): void {
406
456
  const comparison = ghJson<{
407
457
  status: string;
408
458
  merge_base_commit: { sha: string };
@@ -423,6 +473,33 @@ export function validatePullRequestView(
423
473
  prUrl: string,
424
474
  analysis: BuiltinSkillWatchAnalysis,
425
475
  expectedGithubLogin: string,
476
+ ): void {
477
+ validatePullRequestScope(pullRequest, prUrl, analysis, expectedGithubLogin);
478
+ if (pullRequest.state !== "OPEN" || !pullRequest.isDraft) {
479
+ throw new Error("Watcher PR must be open and draft");
480
+ }
481
+ }
482
+
483
+ export function validateReconciledPullRequestView(
484
+ pullRequest: PullRequestView,
485
+ prUrl: string,
486
+ analysis: BuiltinSkillWatchAnalysis,
487
+ expectedGithubLogin: string,
488
+ ): void {
489
+ validatePullRequestScope(pullRequest, prUrl, analysis, expectedGithubLogin);
490
+ const isOpenDraft = pullRequest.state === "OPEN" && pullRequest.isDraft;
491
+ const isMerged =
492
+ pullRequest.state === "MERGED" && typeof pullRequest.mergedAt === "string";
493
+ if (!isOpenDraft && !isMerged) {
494
+ throw new Error("Reconciled watcher PR must be an open draft or merged");
495
+ }
496
+ }
497
+
498
+ function validatePullRequestScope(
499
+ pullRequest: PullRequestView,
500
+ prUrl: string,
501
+ analysis: BuiltinSkillWatchAnalysis,
502
+ expectedGithubLogin: string,
426
503
  ): void {
427
504
  if (pullRequest.url !== prUrl.replace(/\/$/, "")) {
428
505
  throw new Error(`PR URL mismatch: ${pullRequest.url}`);
@@ -432,9 +509,6 @@ export function validatePullRequestView(
432
509
  `PR author ${pullRequest.author.login} does not match ${expectedGithubLogin}`,
433
510
  );
434
511
  }
435
- if (pullRequest.state !== "OPEN" || !pullRequest.isDraft) {
436
- throw new Error("Watcher PR must be open and draft");
437
- }
438
512
  if (pullRequest.baseRefName !== "main") {
439
513
  throw new Error("Watcher PR must target main");
440
514
  }
@@ -0,0 +1,30 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { parseArgs } from "./update-tracker.ts";
3
+
4
+ describe("parseArgs", () => {
5
+ const required = [
6
+ "--tracker-issue",
7
+ "123",
8
+ "--analysis-file",
9
+ "/tmp/analysis.json",
10
+ "--state-commit-sha",
11
+ "abc123",
12
+ "--outcome",
13
+ "pr_created",
14
+ "--pr-url",
15
+ "https://github.com/letta-ai/letta-code/pull/456",
16
+ ];
17
+
18
+ test("requires the expected GitHub login for a PR", () => {
19
+ expect(() => parseArgs(required)).toThrow(
20
+ "--expected-github-login is required for pr_created",
21
+ );
22
+ });
23
+
24
+ test("accepts the expected GitHub login for a PR", () => {
25
+ expect(
26
+ parseArgs([...required, "--expected-github-login", "amelia-letta"])
27
+ .expectedGithubLogin,
28
+ ).toBe("amelia-letta");
29
+ });
30
+ });
@@ -27,6 +27,7 @@ interface Args {
27
27
  outcome: ClaudeWatchOutcome | null;
28
28
  notes: string;
29
29
  prUrl: string | null;
30
+ expectedGithubLogin: string | null;
30
31
  assertTerminal: boolean;
31
32
  dryRun: boolean;
32
33
  }
@@ -41,6 +42,7 @@ export function parseArgs(argv: string[]): Args {
41
42
  outcome: null,
42
43
  notes: "",
43
44
  prUrl: null,
45
+ expectedGithubLogin: null,
44
46
  assertTerminal: false,
45
47
  dryRun: false,
46
48
  };
@@ -59,6 +61,8 @@ export function parseArgs(argv: string[]): Args {
59
61
  args.outcome = parseOutcome(argv[++index]);
60
62
  else if (argument === "--notes") args.notes = argv[++index] ?? "";
61
63
  else if (argument === "--pr-url") args.prUrl = argv[++index] ?? null;
64
+ else if (argument === "--expected-github-login")
65
+ args.expectedGithubLogin = argv[++index] ?? null;
62
66
  else if (argument === "--assert-terminal") args.assertTerminal = true;
63
67
  else if (argument === "--dry-run") args.dryRun = true;
64
68
  else throw new Error(`Unknown argument: ${argument}`);
@@ -75,8 +79,12 @@ export function parseArgs(argv: string[]): Args {
75
79
  if (!args.outcome) throw new Error("--outcome is required");
76
80
  if (isTerminalOutcome(args.outcome) && !args.stateCommitSha)
77
81
  throw new Error("terminal outcomes require --state-commit-sha");
78
- if (args.outcome === "pr_created" && !args.prUrl)
79
- throw new Error("--pr-url is required for pr_created");
82
+ if (args.outcome === "pr_created") {
83
+ if (!args.prUrl) throw new Error("--pr-url is required for pr_created");
84
+ if (!args.expectedGithubLogin) {
85
+ throw new Error("--expected-github-login is required for pr_created");
86
+ }
87
+ }
80
88
  }
81
89
  return args;
82
90
  }
@@ -117,15 +125,16 @@ function verifyParityPr(
117
125
  repo: string,
118
126
  prUrl: string,
119
127
  candidateId: string,
128
+ expectedGithubLogin: string,
120
129
  ): void {
121
130
  const pr = ghJson<{
122
131
  isDraft: boolean;
123
132
  author: { login: string };
124
133
  body: string | null;
125
134
  }>(["pr", "view", prUrl, "--repo", repo, "--json", "isDraft,author,body"]);
126
- if (!pr.isDraft || pr.author.login !== "carenthomas") {
135
+ if (!pr.isDraft || pr.author.login !== expectedGithubLogin) {
127
136
  throw new Error(
128
- `Parity PR must be a draft authored by carenthomas (got draft=${pr.isDraft}, author=${pr.author.login})`,
137
+ `Parity PR must be a draft authored by ${expectedGithubLogin} (got draft=${pr.isDraft}, author=${pr.author.login})`,
129
138
  );
130
139
  }
131
140
  if (!pr.body?.includes(`Claude-watch: ${candidateId}`)) {
@@ -164,7 +173,12 @@ export function main(argv = process.argv.slice(2)): void {
164
173
  verifyStateCandidate(analysis.candidate_id, args.stateCommitSha as string);
165
174
  }
166
175
  if (args.outcome === "pr_created") {
167
- verifyParityPr(args.repo, args.prUrl as string, analysis.candidate_id);
176
+ verifyParityPr(
177
+ args.repo,
178
+ args.prUrl as string,
179
+ analysis.candidate_id,
180
+ args.expectedGithubLogin as string,
181
+ );
168
182
  }
169
183
  const next = recordAnalysis(state, {
170
184
  analysis,
@@ -0,0 +1,28 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { parseArgs } from "./update-tracker.ts";
3
+
4
+ describe("parseArgs", () => {
5
+ const required = [
6
+ "--tracker-issue",
7
+ "123",
8
+ "--analysis-file",
9
+ "/tmp/analysis.json",
10
+ "--outcome",
11
+ "pr_created",
12
+ "--pr-url",
13
+ "https://github.com/letta-ai/letta-code/pull/456",
14
+ ];
15
+
16
+ test("requires the expected GitHub login for a PR", () => {
17
+ expect(() => parseArgs(required)).toThrow(
18
+ "--expected-github-login is required for pr_created",
19
+ );
20
+ });
21
+
22
+ test("accepts the expected GitHub login for a PR", () => {
23
+ expect(
24
+ parseArgs([...required, "--expected-github-login", "amelia-letta"])
25
+ .expectedGithubLogin,
26
+ ).toBe("amelia-letta");
27
+ });
28
+ });
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import { readFileSync } from "node:fs";
10
- import { editIssueBody, getIssueBody } from "./github.ts";
10
+ import { editIssueBody, getIssueBody, ghJson } from "./github.ts";
11
11
  import {
12
12
  type CodexWatchAnalysis,
13
13
  DEFAULT_TARGET_REPO,
@@ -26,10 +26,11 @@ interface Args {
26
26
  outcome: TrackerOutcome | null;
27
27
  notes: string;
28
28
  prUrl: string | null;
29
+ expectedGithubLogin: string | null;
29
30
  dryRun: boolean;
30
31
  }
31
32
 
32
- function parseArgs(argv: string[]): Args {
33
+ export function parseArgs(argv: string[]): Args {
33
34
  const args: Args = {
34
35
  repo: DEFAULT_TARGET_REPO,
35
36
  trackerIssue: null,
@@ -37,6 +38,7 @@ function parseArgs(argv: string[]): Args {
37
38
  outcome: null,
38
39
  notes: "",
39
40
  prUrl: null,
41
+ expectedGithubLogin: null,
40
42
  dryRun: false,
41
43
  };
42
44
 
@@ -49,10 +51,12 @@ function parseArgs(argv: string[]): Args {
49
51
  else if (a === "--outcome") args.outcome = parseOutcome(argv[++i]);
50
52
  else if (a === "--notes") args.notes = argv[++i] ?? "";
51
53
  else if (a === "--pr-url") args.prUrl = argv[++i] ?? null;
52
- else if (a === "--dry-run") args.dryRun = true;
54
+ else if (a === "--expected-github-login") {
55
+ args.expectedGithubLogin = argv[++i] ?? null;
56
+ } else if (a === "--dry-run") args.dryRun = true;
53
57
  else if (a === "--help" || a === "-h") {
54
58
  console.log(
55
- "Usage: bun scripts/codex-watch/update-tracker.ts --tracker-issue ISSUE --analysis-file FILE --outcome OUTCOME [--notes TEXT] [--pr-url URL] [--repo OWNER/REPO] [--dry-run]",
59
+ "Usage: bun scripts/codex-watch/update-tracker.ts --tracker-issue ISSUE --analysis-file FILE --outcome OUTCOME [--notes TEXT] [--pr-url URL --expected-github-login LOGIN] [--repo OWNER/REPO] [--dry-run]",
56
60
  );
57
61
  process.exit(0);
58
62
  } else {
@@ -65,8 +69,13 @@ function parseArgs(argv: string[]): Args {
65
69
  }
66
70
  if (!args.analysisFile) throw new Error("--analysis-file is required");
67
71
  if (!args.outcome) throw new Error("--outcome is required");
68
- if (args.outcome === "pr_created" && !args.prUrl) {
69
- throw new Error("--pr-url is required when --outcome pr_created");
72
+ if (args.outcome === "pr_created") {
73
+ if (!args.prUrl) {
74
+ throw new Error("--pr-url is required when --outcome pr_created");
75
+ }
76
+ if (!args.expectedGithubLogin) {
77
+ throw new Error("--expected-github-login is required for pr_created");
78
+ }
70
79
  }
71
80
 
72
81
  return args;
@@ -94,6 +103,14 @@ function main() {
94
103
  const analysis = readAnalysis(args.analysisFile as string);
95
104
  const body = getIssueBody(args.repo, args.trackerIssue as number);
96
105
  const state = parseTrackerState(body);
106
+ if (args.outcome === "pr_created") {
107
+ verifyParityPr(
108
+ args.repo,
109
+ args.prUrl as string,
110
+ args.expectedGithubLogin as string,
111
+ analysis.current_tag,
112
+ );
113
+ }
97
114
  const next = recordAnalysis(state, {
98
115
  analysis,
99
116
  outcome: args.outcome as TrackerOutcome,
@@ -113,6 +130,41 @@ function main() {
113
130
  );
114
131
  }
115
132
 
133
+ function verifyParityPr(
134
+ repo: string,
135
+ prUrl: string,
136
+ expectedGithubLogin: string,
137
+ currentTag: string,
138
+ ): void {
139
+ const pullRequest = ghJson<{
140
+ author: { login: string };
141
+ body: string | null;
142
+ isDraft: boolean;
143
+ state: string;
144
+ }>([
145
+ "pr",
146
+ "view",
147
+ prUrl,
148
+ "--repo",
149
+ repo,
150
+ "--json",
151
+ "author,body,isDraft,state",
152
+ ]);
153
+ if (
154
+ pullRequest.author.login !== expectedGithubLogin ||
155
+ !pullRequest.isDraft ||
156
+ pullRequest.state !== "OPEN"
157
+ ) {
158
+ throw new Error(
159
+ `Codex PR must be an open draft authored by ${expectedGithubLogin} (got author=${pullRequest.author.login}, draft=${pullRequest.isDraft}, state=${pullRequest.state})`,
160
+ );
161
+ }
162
+ const marker = `Codex-watch: openai/codex ${currentTag}`;
163
+ if (!pullRequest.body?.includes(marker)) {
164
+ throw new Error(`Codex PR body is missing marker: ${marker}`);
165
+ }
166
+ }
167
+
116
168
  function defaultNotes(outcome: TrackerOutcome): string {
117
169
  switch (outcome) {
118
170
  case "recorded_noop":
@@ -128,4 +180,4 @@ function defaultNotes(outcome: TrackerOutcome): string {
128
180
  }
129
181
  }
130
182
 
131
- main();
183
+ if (import.meta.main) main();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "src/agent/client-skills.test.ts": 1134,
3
- "src/agent/memory-git.ts": 2122,
3
+ "src/agent/memory-git.ts": 2090,
4
4
  "src/backend/local-backend.test.ts": 2532,
5
5
  "src/backend/local/local-backend.ts": 1014,
6
6
  "src/backend/local/local-store.ts": 3459,
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: letta-guide
3
- description: Answer questions about Letta itself from the official documentation. Use whenever the user asks how Letta works, what Letta (or you) can do, or how to set up or configure providers, models, channels, skills, memory, schedules, permissions, self-hosting, pricing, or billing any "how do I…" or "can Letta…" question about the Letta product. Fetch the docs before answering; never answer Letta product questions from memory alone.
3
+ description: Read the official Letta documentation (docs.letta.com) through its cached, ETag-checked fetch route. Load before ANY docs.letta.com retrieval — answering how Letta works, what Letta (or you) can do, setting up providers, models, channels, skills, memory, schedules, permissions, self-hosting, pricing, or billing, AND looking up Letta API, Agent SDK, or Letta Code reference while writing code. Do not use fetch_webpage or web_search on docs.letta.com; this skill's helper is the docs route. Never answer Letta product questions from memory alone.
4
4
  ---
5
5
 
6
6
  # Letta Guide
@@ -8,7 +8,11 @@ description: Answer questions about Letta itself from the official documentation
8
8
  You are running inside Letta, but your training data about Letta's commands,
9
9
  flags, settings, UI, pricing, and providers is out of date. Users lose trust
10
10
  fastest when an agent confidently invents product details. This skill defines
11
- how to answer questions about Letta correctly.
11
+ how to read the Letta docs correctly — both when answering questions about
12
+ Letta and when looking up API, Agent SDK, or Letta Code reference during
13
+ development. Guessing a docs URL and fetching it with `fetch_webpage` misses
14
+ pages that exist under a different path and can serve stale content; the
15
+ helper below fetches the live index first, so you pick a URL that exists.
12
16
 
13
17
  ## Source route (in order)
14
18
 
@@ -7,7 +7,7 @@ description: Create and manage shared memory — git-tracked repositories hosted
7
7
 
8
8
  Shared memory is memory created independently of any single agent, designed to be dynamically attached to or detached from multiple agents. Each unit of shared memory is a **shared memory repository**: a git repository hosted on Letta Cloud, owned by your organization rather than by one agent, reachable from any environment (sandboxes, remote machines, sessions).
9
9
 
10
- Shared memory works exactly like your MemFS: attached repositories are real git checkouts on disk, and you read, edit, commit, and push them with ordinary git. The only differences are that each repository has its own projection root (next to your memory directory, not inside it) and its own remote origin, and other agents may be writing to it too.
10
+ Shared memory works like your MemFS: attached repositories are real git checkouts on disk, and you read, edit, and commit with ordinary git. The harness pushes clean committed changes after each turn. Each repository has its own projection root (next to your memory directory, not inside it) and its own remote origin, and other agents may be writing to it too.
11
11
 
12
12
  Create a shared memory repository when:
13
13
  - You have context an agent should be able to access that doesn't belong in its own MemFS (input files, datasets, docs, working artifacts)
@@ -23,16 +23,15 @@ ls "$MEMORY_DIR/../" # attached repositories appear here by
23
23
  cat "$MEMORY_DIR/../<repo-name>/<path>" # read like any file
24
24
  ```
25
25
 
26
- Edit files with your normal file tools, then commit and push with git the mount's origin and credentials are already configured:
26
+ Edit files with your normal file tools, then commit with git. The mount's origin and credentials are already configured:
27
27
 
28
28
  ```bash
29
29
  cd "$MEMORY_DIR/../<repo-name>"
30
30
  git add <files>
31
31
  git commit -m "describe the change"
32
- git push
33
32
  ```
34
33
 
35
- Unlike MemFS, the harness does not auto-push shared memory after turns a commit you don't push is not visible to other agents or environments. Always push after committing.
34
+ The harness pushes clean commits from read/write attached repositories after the turn. If a push collides with another agent's work, it pulls with rebase and retries once. Dirty files and conflicts are not changed automatically; the harness adds a reminder to the next turn instead.
36
35
 
37
36
  To pick up other agents' changes:
38
37
 
@@ -84,7 +83,7 @@ letta shared-memory history shared-notes --path docs/plan.md
84
83
  - **`sync` reports "mount path already exists and is not a git repository"** — a plain directory (usually created by hand before the mount existed) is occupying the mount path. Inspect it, salvage anything worth keeping, move or delete it, then re-run `letta shared-memory sync`.
85
84
  - **Never hand-clone the repository to another location (e.g. /tmp) to work around a broken mount** — fix the mount with `letta shared-memory sync` so every session and other agents see the same checkout.
86
85
  - **Permission denied under another agent's directory** — shared repositories mount per-agent. Only your own mount (under your agent directory) is accessible; another agent's mount of the same repository is walled off by the cross-agent guard. Run `letta shared-memory sync` to get your own mount.
87
- - **Push rejected (non-fast-forward)** — another agent pushed first: `git pull --rebase`, resolve any conflicts, push again.
86
+ - **Shared-memory conflict reminder** — resolve the conflict in the named repository, finish the rebase or commit, and leave the repository clean. The harness retries the push after a future turn.
88
87
 
89
88
  ## Notes and Limits
90
89
 
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: submitting-feedback
3
+ description: Submits user-approved feedback about Letta Code or the current agent to the Letta team. Load when the user is upset, frustrated, dissatisfied, reports poor agent behavior, or asks to send feedback. Works with cloud-hosted and local agents. Ask before submitting unless the user already explicitly requested submission.
4
+ ---
5
+
6
+ # Submitting Feedback
7
+
8
+ When the user appears upset with the agent, acknowledge the problem and ask whether they want you to submit feedback to the Letta team. Do not submit merely because the user expressed frustration.
9
+
10
+ If the user says yes, or directly asks you to submit feedback:
11
+
12
+ 1. Write a short factual message in the user's voice. Include what happened, what the user expected, and any useful error or behavior detail already present in the conversation. Do not add claims the user did not make.
13
+ 2. Submit it with:
14
+
15
+ ```bash
16
+ letta feedback --message '<feedback>'
17
+ ```
18
+
19
+ 3. Tell the user whether submission succeeded. If it failed, report the safe CLI error and do not claim the team received it.
20
+
21
+ Do not include secrets, credentials, unrelated conversation content, or private file contents. The command adds the current agent and conversation identifiers so the team can find the relevant run.