@letta-ai/letta-code 0.34.26 → 0.34.27

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 (99) hide show
  1. package/cursor-worker.js +4 -3
  2. package/dist/agent-presets.js +5 -5
  3. package/dist/agent-presets.js.map +1 -1
  4. package/dist/mcp-client.js +2 -2
  5. package/dist/mcp-client.js.map +1 -1
  6. package/dist/mcp-oauth.js +26 -16
  7. package/dist/mcp-oauth.js.map +6 -5
  8. package/dist/memory-confinement.js +109 -51
  9. package/dist/memory-confinement.js.map +9 -8
  10. package/dist/types/agent/mcp-originator-authority-file.d.ts +1 -0
  11. package/dist/types/agent/mcp-originator-authority-file.d.ts.map +1 -1
  12. package/dist/types/agent/memory-filesystem.d.ts +3 -3
  13. package/dist/types/agent/memory-filesystem.d.ts.map +1 -1
  14. package/dist/types/agent/memory-git.d.ts +2 -2
  15. package/dist/types/agent/memory-git.d.ts.map +1 -1
  16. package/dist/types/agent/skills.d.ts +3 -3
  17. package/dist/types/agent/skills.d.ts.map +1 -1
  18. package/dist/types/agent/subagents/index.d.ts +4 -0
  19. package/dist/types/agent/subagents/index.d.ts.map +1 -1
  20. package/dist/types/agent/subagents/subagent-launcher.d.ts.map +1 -1
  21. package/dist/types/backend/api/schedules.d.ts +16 -2
  22. package/dist/types/backend/api/schedules.d.ts.map +1 -1
  23. package/dist/types/channels/types.d.ts +1 -1
  24. package/dist/types/cli/helpers/chunk-log.d.ts.map +1 -1
  25. package/dist/types/cron/cron-file.d.ts.map +1 -1
  26. package/dist/types/hooks/loader.d.ts.map +1 -1
  27. package/dist/types/mods/paths.d.ts +3 -3
  28. package/dist/types/mods/paths.d.ts.map +1 -1
  29. package/dist/types/permissions/agent-memory-prefixes.d.ts.map +1 -1
  30. package/dist/types/permissions/analyzer.d.ts +1 -0
  31. package/dist/types/permissions/analyzer.d.ts.map +1 -1
  32. package/dist/types/permissions/cross-agent-guard.d.ts.map +1 -1
  33. package/dist/types/permissions/loader.d.ts +2 -1
  34. package/dist/types/permissions/loader.d.ts.map +1 -1
  35. package/dist/types/permissions/sandbox-policy.d.ts +9 -7
  36. package/dist/types/permissions/sandbox-policy.d.ts.map +1 -1
  37. package/dist/types/sandbox/bwrap.d.ts +2 -2
  38. package/dist/types/sandbox/bwrap.d.ts.map +1 -1
  39. package/dist/types/sandbox/policy.d.ts +20 -3
  40. package/dist/types/sandbox/policy.d.ts.map +1 -1
  41. package/dist/types/sandbox/seatbelt.d.ts +1 -1
  42. package/dist/types/sandbox/seatbelt.d.ts.map +1 -1
  43. package/dist/types/settings-manager.d.ts +4 -12
  44. package/dist/types/settings-manager.d.ts.map +1 -1
  45. package/dist/types/tools/commit-trailer.d.ts +27 -0
  46. package/dist/types/tools/commit-trailer.d.ts.map +1 -0
  47. package/dist/types/tools/impl/codex-app-server.d.ts +2 -0
  48. package/dist/types/tools/impl/codex-app-server.d.ts.map +1 -1
  49. package/dist/types/tools/impl/cursor-session.d.ts +2 -0
  50. package/dist/types/tools/impl/cursor-session.d.ts.map +1 -1
  51. package/dist/types/tools/impl/cursor-worker.d.ts +4 -1
  52. package/dist/types/tools/impl/cursor-worker.d.ts.map +1 -1
  53. package/dist/types/tools/impl/overflow.d.ts +1 -1
  54. package/dist/types/tools/impl/overflow.d.ts.map +1 -1
  55. package/dist/types/tools/impl/ripgrep-manager.d.ts.map +1 -1
  56. package/dist/types/tools/impl/shell-env.d.ts.map +1 -1
  57. package/dist/types/tools/impl/wake.d.ts.map +1 -1
  58. package/dist/types/tools/script-tool-scope.d.ts.map +1 -1
  59. package/dist/types/tools/tool-definitions.d.ts.map +1 -1
  60. package/dist/types/utils/fs.d.ts +5 -1
  61. package/dist/types/utils/fs.d.ts.map +1 -1
  62. package/dist/types/utils/local-backend-paths.d.ts +1 -1
  63. package/dist/types/utils/local-backend-paths.d.ts.map +1 -1
  64. package/dist/types/utils/secrets.d.ts +1 -1
  65. package/dist/types/utils/secrets.d.ts.map +1 -1
  66. package/dist/types/utils/settings-path.d.ts +14 -0
  67. package/dist/types/utils/settings-path.d.ts.map +1 -0
  68. package/dist/types/utils/transcript-paths.d.ts +1 -1
  69. package/dist/types/websocket/listener/constants.d.ts +6 -0
  70. package/dist/types/websocket/listener/constants.d.ts.map +1 -1
  71. package/dist/types/websocket/listener/input-disposition-path.d.ts.map +1 -1
  72. package/dist/types/websocket/listener/interrupted-turn-storage.d.ts.map +1 -1
  73. package/dist/types/websocket/listener/org-credentials.d.ts.map +1 -1
  74. package/dist/types/websocket/listener/remote-settings.d.ts +2 -2
  75. package/dist/types/websocket/listener/remote-settings.d.ts.map +1 -1
  76. package/dist/types/websocket/listener/turn-finished-replay.d.ts +1 -0
  77. package/dist/types/websocket/listener/turn-finished-replay.d.ts.map +1 -1
  78. package/letta.js +2592 -1947
  79. package/npm-shrinkwrap.json +90 -86
  80. package/package.json +3 -2
  81. package/scripts/isolated-unit-tests.json +10 -0
  82. package/scripts/test-home-preload.ts +1 -0
  83. package/skills/acquiring-skills/SKILL.md +4 -4
  84. package/skills/creating-mods/SKILL.md +3 -3
  85. package/skills/creating-mods/references/analysis-mode.md +7 -5
  86. package/skills/creating-mods/references/architecture.md +2 -2
  87. package/skills/creating-mods/references/plan-mode.md +12 -11
  88. package/skills/creating-mods/references/providers.md +1 -1
  89. package/skills/creating-skills/scripts/init-skill.ts +4 -2
  90. package/skills/customizing-commands/SKILL.md +3 -3
  91. package/skills/customizing-statusline/SKILL.md +3 -3
  92. package/skills/customizing-statusline/references/api.md +3 -3
  93. package/skills/customizing-statusline/references/migration.md +2 -2
  94. package/skills/initializing-memory/SKILL.md +1 -1
  95. package/skills/self-configuration/SKILL.md +4 -4
  96. package/skills/self-configuration/scripts/add_permission.py +26 -5
  97. package/skills/self-configuration/scripts/show_config.py +17 -2
  98. package/skills/video-generation/SKILL.md +152 -0
  99. package/skills/workflow-authoring/SKILL.md +1 -1
@@ -10,10 +10,10 @@ Use this as the command-specific entrypoint for local mod slash commands. For br
10
10
  Mod files live in:
11
11
 
12
12
  ```text
13
- ~/.letta/mods/
13
+ ${LETTA_CONFIG_DIR:-$HOME/.letta}/mods/
14
14
  ```
15
15
 
16
- Use a focused file name, e.g. `~/.letta/mods/review.ts` or `~/.letta/mods/commands.ts`.
16
+ Use a focused file name, e.g. `${LETTA_CONFIG_DIR:-$HOME/.letta}/mods/review.ts` or `${LETTA_CONFIG_DIR:-$HOME/.letta}/mods/commands.ts`.
17
17
 
18
18
  ## First decide whether a command is right
19
19
 
@@ -29,7 +29,7 @@ If the command is a reusable workflow like `/goal`, put the workflow instruction
29
29
 
30
30
  ## Workflow
31
31
 
32
- 1. Inspect `~/.letta/mods/` for related command files.
32
+ 1. Inspect `${LETTA_CONFIG_DIR:-$HOME/.letta}/mods/` for related command files.
33
33
  2. Preserve unrelated mod code; create a focused new file if merging is messy.
34
34
  3. Register with `letta.commands.register()` and guard with `letta.capabilities.commands`.
35
35
  4. Return the unregister function, or a disposer that calls it plus any timer/panel cleanup.
@@ -8,7 +8,7 @@ description: Creates, edits, and migrates Letta Code statusline mods. Use when h
8
8
  Use this skill to create or update the global Letta Code statusline mod:
9
9
 
10
10
  ```text
11
- ~/.letta/mods/statusline.tsx
11
+ ${LETTA_CONFIG_DIR:-$HOME/.letta}/mods/statusline.tsx
12
12
  ```
13
13
 
14
14
  The statusline is a panel registered at `order: 0` — the primary line just below the input. It overrides the built-in `agent · model` line. Host UI can still temporarily preempt it for safety confirmations and transient hints.
@@ -26,14 +26,14 @@ The order-0 panel owns the whole primary row. It renders text (not React) and ow
26
26
 
27
27
  ## Workflow
28
28
 
29
- 1. Check whether `~/.letta/mods/statusline.tsx` exists.
29
+ 1. Check whether `${LETTA_CONFIG_DIR:-$HOME/.letta}/mods/statusline.tsx` exists.
30
30
  2. If it exists, read it before editing and preserve unrelated code.
31
31
  3. If it does not exist, synthesize a focused starter for the user's request.
32
32
  4. If the user asks to migrate, import a `.sh` file, or match a shell prompt, read `references/migration.md`.
33
33
  5. If API details or concrete patterns are needed, read `references/api.md` and `references/examples.md`.
34
34
  6. If the request combines statusline work with commands, tools, events, other panels, or stateful mod behavior, also use `creating-mods` and its `references/architecture.md`.
35
35
  7. Guard panel work with `letta.capabilities.ui.panels` when writing new files.
36
- 8. Edit `~/.letta/mods/statusline.tsx`.
36
+ 8. Edit `${LETTA_CONFIG_DIR:-$HOME/.letta}/mods/statusline.tsx`.
37
37
  9. Summarize the absolute file path changed and tell the user to run `/reload` unless the command can reload automatically.
38
38
 
39
39
  ## Bare `/statusline` behavior
@@ -1,11 +1,11 @@
1
1
  # Statusline Mod API
2
2
 
3
- Use this reference when creating or editing `~/.letta/mods/statusline.tsx`.
3
+ Use this reference when creating or editing `${LETTA_CONFIG_DIR:-$HOME/.letta}/mods/statusline.tsx`.
4
4
 
5
5
  ## Location
6
6
 
7
7
  ```text
8
- ~/.letta/mods/statusline.tsx
8
+ ${LETTA_CONFIG_DIR:-$HOME/.letta}/mods/statusline.tsx
9
9
  ```
10
10
 
11
11
  This is a trusted, user-owned global mod file. Project mods are intentionally unsupported for now.
@@ -147,7 +147,7 @@ render: ({ width, columns }) => columns(["left", "middle", "right"], width),
147
147
 
148
148
  ## Reload behavior
149
149
 
150
- After editing `~/.letta/mods/statusline.tsx`, tell the user to run:
150
+ After editing `${LETTA_CONFIG_DIR:-$HOME/.letta}/mods/statusline.tsx`, tell the user to run:
151
151
 
152
152
  ```text
153
153
  /reload
@@ -1,13 +1,13 @@
1
1
  # Statusline Migration
2
2
 
3
- Use this reference when migrating legacy command statuslines, standalone `.sh` statusline scripts, or shell PS1 prompts into `~/.letta/mods/statusline.tsx`.
3
+ Use this reference when migrating legacy command statuslines, standalone `.sh` statusline scripts, or shell PS1 prompts into `${LETTA_CONFIG_DIR:-$HOME/.letta}/mods/statusline.tsx`.
4
4
 
5
5
  ## Legacy Letta command statusline
6
6
 
7
7
  Inspect these files for old config:
8
8
 
9
9
  ```text
10
- ~/.letta/settings.json
10
+ ${LETTA_CONFIG_DIR:-$HOME/.letta}/settings.json
11
11
  <project>/.letta/settings.json
12
12
  <project>/.letta/settings.local.json
13
13
  ```
@@ -159,7 +159,7 @@ You — not the subagents — decide what becomes memory, and you write it. Synt
159
159
 
160
160
  ```bash
161
161
  node <SKILL_DIR>/scripts/history-coverage.mjs --prepared /tmp/letta-init-history \
162
- --journal ~/.letta/workflows/executions/<id>/journal.jsonl \
162
+ --journal "${LETTA_CONFIG_DIR:-$HOME/.letta}/workflows/executions/<id>/journal.jsonl" \
163
163
  --retry-out /tmp/letta-init-history/retry.json
164
164
  ```
165
165
 
@@ -17,7 +17,7 @@ The important part is choosing the right layer. Do not smear a preference into d
17
17
  | Memory and identity | Facts worth retaining, style preferences, persona changes, project knowledge, reusable skills | Edit `$MEMORY_DIR` files and sync the memory repo |
18
18
  | Server agent fields | Agent default model (only on explicit request), context limit, system prompt, compaction, agent name, description | Patch `/v1/agents/{agent_id}` |
19
19
  | Server conversation fields | Model/context changes for the current conversation (the normal target) | Patch `/v1/conversations/{conversation_id}` |
20
- | Local settings | Permissions, environment variables, UI/runtime preferences, pinned agents, toolset overrides, reflection cadence | Edit `~/.letta/settings.json`, `./.letta/settings.json`, or `./.letta/settings.local.json` |
20
+ | Local settings | Permissions, environment variables, UI/runtime preferences, pinned agents, toolset overrides, reflection cadence | Edit `$LETTA_CONFIG_DIR/settings.json` when configured (otherwise `~/.letta/settings.json`), `./.letta/settings.json`, or `./.letta/settings.local.json` |
21
21
  | Mods | New deterministic tools, slash commands, providers, statusline behavior, or lightweight UI | Load `creating-mods`, `customizing-commands`, or `customizing-statusline` |
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 |
@@ -232,7 +232,7 @@ Settings scopes:
232
232
 
233
233
  | File | Scope | Typical contents |
234
234
  | --- | --- | --- |
235
- | `~/.letta/settings.json` | User/global | Permissions, env keys, experiments, UI/runtime preferences, agents[] entries |
235
+ | `$LETTA_CONFIG_DIR/settings.json` when configured; otherwise `~/.letta/settings.json` | User/global | Permissions, env keys, experiments, UI/runtime preferences, agents[] entries |
236
236
  | `./.letta/settings.json` | Project/shared | Project settings committed with the repo |
237
237
  | `./.letta/settings.local.json` | Project-local | Personal project overrides, usually gitignored |
238
238
 
@@ -347,7 +347,7 @@ Use skills when the user wants you to become good at a repeatable workflow. Sour
347
347
 
348
348
  1. Project skills: `.agents/skills/` with `.skills/` as legacy fallback
349
349
  2. Agent skills: `$MEMORY_DIR/skills/`
350
- 3. Global skills: `~/.letta/skills/`
350
+ 3. Global skills: `$LETTA_CONFIG_DIR/skills/` when configured; otherwise `~/.letta/skills/`
351
351
  4. Bundled skills
352
352
 
353
353
  Load `creating-skills` to create or edit a skill. Load `acquiring-skills` when the user asks for a capability you do not already have. Project, global, bundled, and agent-owned skills have different visibility; verify the target scope before changing skills another agent may load.
@@ -385,7 +385,7 @@ letta channels pair --channel <channel> --code <code> --agent <agent-id> --conve
385
385
  letta server --channels <channel>
386
386
  ```
387
387
 
388
- Channel state lives under `~/.letta/channels/<channel>/` (`config.yaml`, `accounts.json`, routing/pairing files, and channel runtimes). Account tokens may be plaintext in `file` mode or keyring placeholders in `keyring`/`auto` mode. Configure storage with `channelCredentialsStore` (`file`, `keyring`, `auto`) or `LETTA_CHANNEL_CREDENTIALS_STORE`; do not treat keyring placeholders as usable secrets and do not print tokens. Channel configuration and pairing can route external messages to other agents/conversations; verify IDs and get human consent for interactive authorization.
388
+ Channel state lives under `${LETTA_CONFIG_DIR:-$HOME/.letta}/channels/<channel>/` (`config.yaml`, `accounts.json`, routing/pairing files, and channel runtimes). Account tokens may be plaintext in `file` mode or keyring placeholders in `keyring`/`auto` mode. Configure storage with `channelCredentialsStore` (`file`, `keyring`, `auto`) or `LETTA_CHANNEL_CREDENTIALS_STORE`; do not treat keyring placeholders as usable secrets and do not print tokens. Channel configuration and pairing can route external messages to other agents/conversations; verify IDs and get human consent for interactive authorization.
389
389
 
390
390
  `letta channels configure <channel>` is an interactive TTY wizard. Do not launch it as unattended work or claim setup succeeded while it is waiting for input; hand the authorization/setup step to the user.
391
391
 
@@ -19,10 +19,27 @@ from pathlib import Path
19
19
  from typing import Any
20
20
 
21
21
 
22
+ def get_user_config_dir() -> Path:
23
+ """Resolve the product-selected global config directory."""
24
+ override = os.environ.get("LETTA_CONFIG_DIR", "").strip()
25
+ if override:
26
+ path = Path(override)
27
+ if not path.is_absolute():
28
+ raise ValueError("LETTA_CONFIG_DIR must be absolute")
29
+ return path
30
+ return Path.home() / ".letta"
31
+
32
+
22
33
  def get_settings_path(scope: str, working_directory: str) -> Path:
23
34
  """Get the settings file path for a given scope."""
24
35
  if scope == "user":
25
- return Path.home() / ".letta" / "settings.json"
36
+ return get_user_config_dir() / "settings.json"
37
+ if os.environ.get("LETTA_CONFIG_DIR", "").strip() and (
38
+ Path(working_directory).resolve() == Path.home().resolve()
39
+ ):
40
+ raise ValueError(
41
+ f"Cannot save {scope} permissions under HOME while LETTA_CONFIG_DIR is set"
42
+ )
26
43
  if scope == "project":
27
44
  return Path(working_directory) / ".letta" / "settings.json"
28
45
  if scope == "local":
@@ -48,10 +65,14 @@ def load_settings(path: Path) -> dict[str, Any]:
48
65
  return parsed
49
66
 
50
67
 
51
- def save_settings(path: Path, settings: dict[str, Any]) -> None:
52
- """Atomically save settings, preserving an existing file mode where practical."""
68
+ def save_settings(path: Path, settings: dict[str, Any], secure: bool = False) -> None:
69
+ """Atomically save settings, securing the global config when requested."""
53
70
  path.parent.mkdir(parents=True, exist_ok=True)
54
- mode = stat.S_IMODE(path.stat().st_mode) if path.exists() else None
71
+ if secure:
72
+ os.chmod(path.parent, 0o700)
73
+ mode = 0o600 if secure else (
74
+ stat.S_IMODE(path.stat().st_mode) if path.exists() else None
75
+ )
55
76
  temp_path: str | None = None
56
77
 
57
78
  try:
@@ -194,7 +215,7 @@ def main() -> None:
194
215
 
195
216
  if added:
196
217
  try:
197
- save_settings(settings_path, settings)
218
+ save_settings(settings_path, settings, secure=args.scope == "user")
198
219
  except OSError as exc:
199
220
  print(f"Could not save {settings_path}: {exc}", file=sys.stderr)
200
221
  sys.exit(1)
@@ -54,10 +54,25 @@ VERSION_TIMEOUT_SECONDS = 2
54
54
  MAX_VERSION_TEXT = 200
55
55
 
56
56
 
57
+ def get_user_config_dir() -> Path:
58
+ """Resolve the product-selected global config directory."""
59
+ override = os.environ.get("LETTA_CONFIG_DIR", "").strip()
60
+ if override:
61
+ path = Path(override)
62
+ if not path.is_absolute():
63
+ raise ValueError("LETTA_CONFIG_DIR must be absolute")
64
+ return path
65
+ return Path.home() / ".letta"
66
+
67
+
57
68
  def get_settings_paths(working_directory: str) -> list[tuple[str, Path]]:
58
69
  """Return (scope, path) in precedence order (lowest to highest)."""
59
- return [
60
- ("user", Path.home() / ".letta" / "settings.json"),
70
+ paths = [("user", get_user_config_dir() / "settings.json")]
71
+ if os.environ.get("LETTA_CONFIG_DIR", "").strip() and (
72
+ Path(working_directory).resolve() == Path.home().resolve()
73
+ ):
74
+ return paths
75
+ return paths + [
61
76
  ("project", Path(working_directory) / ".letta" / "settings.json"),
62
77
  ("local", Path(working_directory) / ".letta" / "settings.local.json"),
63
78
  ]
@@ -0,0 +1,152 @@
1
+ ---
2
+ name: video-generation
3
+ description: Generate videos from text prompts (and optional reference or frame images) using Letta's asynchronous video generation API. Use when the user asks to create, generate, or make a video or animation from a description, animate an existing image, or turn a prompt into a short video clip.
4
+ ---
5
+
6
+ # Video Generation
7
+
8
+ Generate videos via Letta's async `POST /v1/videos` using `curl` + `jq`. Requires `LETTA_BASE_URL` and `LETTA_API_KEY`, which the runtime provides. If either is unset, stop and ask the user to authenticate with Letta Cloud; do not ask for a video provider key.
9
+
10
+ This skill is adapted from OpenRouter's `openrouter-video` skill
11
+ ([OpenRouterTeam/skills](https://github.com/OpenRouterTeam/skills/blob/83827c3f3b1200bc3b609f4ab4ec3241c0cfb7ad/skills/openrouter-video/SKILL.md)).
12
+ Letta's endpoint forwards the request with Letta's key and charges the
13
+ organization's Letta credits for the cost of each completed video.
14
+
15
+ Build every URL relative to `${LETTA_BASE_URL%/}` and send `Authorization: Bearer $LETTA_API_KEY`. Do not hardcode `https://api.letta.com`: Desktop and remote runtimes may provide a proxy base URL, and the credential may only be valid through that URL.
16
+
17
+ ## The three steps
18
+
19
+ Video generation is async: submit → poll → download. A single request can't return the video because generation takes 30s–a few minutes. Tell the user the job was submitted so they know the delay is expected.
20
+
21
+ 1. `POST /v1/videos` → `{ id, polling_url, status: "pending" }` (`polling_url` is the absolute URL of step 2)
22
+ 2. `GET /v1/videos/{id}` every ~30s until `status` is `completed` (terminal failures: `failed`, `cancelled`, `expired` — surface the `error` field verbatim)
23
+ 3. `GET /v1/videos/{id}/content?index=0` **with the auth header** → MP4 bytes
24
+
25
+ A completed job also has `urls`: signed downloads that need no API key until `expires_at` (Unix seconds); anyone holding one can download the video, so prefer step 3.
26
+
27
+ ## Pick parameters from the models endpoint, don't guess
28
+
29
+ `resolution`, `aspect_ratio`, `duration`, and `frame_images[].frame_type` are per-model. Before the first submit for a new model (or whenever the user asks for something specific), fetch the model's capabilities and only send values from the returned sets:
30
+
31
+ ```bash
32
+ curl -sS "${LETTA_BASE_URL%/}/v1/videos/models" \
33
+ -H "Authorization: Bearer $LETTA_API_KEY" \
34
+ | jq '.data[] | select(.id == "MODEL_ID")'
35
+ ```
36
+
37
+ Fields on each model worth knowing: `supported_resolutions`, `supported_aspect_ratios`, `supported_sizes`, `supported_durations` (often discrete like `[4,6,8]`, not a range), `supported_frame_images` (which `frame_type` values are accepted), `generate_audio` and `seed` (capability bools), `pricing_skus`, and `allowed_passthrough_parameters`. An out-of-set value returns a 400, so validate client-side.
38
+
39
+ Models include `google/veo-3.1`, `bytedance/seedance-2.5`, `kwaivgi/kling-v3.0-pro`, `minimax/hailuo-3`, `minimax/hailuo-3-max`, and `heygen/avatar-iv` (a talking head from one photo); the models endpoint has the full list.
40
+
41
+ ## Full workflow (drop-in)
42
+
43
+ ```bash
44
+ #!/usr/bin/env bash
45
+ set -euo pipefail
46
+
47
+ PROMPT="a golden retriever playing fetch on a sunny beach"
48
+ MODEL="google/veo-3.1"
49
+ OUTPUT="video-$(date +%Y%m%d-%H%M%S).mp4"
50
+ base_url="${LETTA_BASE_URL%/}"
51
+
52
+ # Build payload — extend with resolution/aspect_ratio/duration/etc. as needed.
53
+ payload=$(jq -n --arg model "$MODEL" --arg prompt "$PROMPT" \
54
+ '{model: $model, prompt: $prompt}')
55
+
56
+ submit=$(curl -sS -X POST "$base_url/v1/videos" \
57
+ -H "Authorization: Bearer $LETTA_API_KEY" \
58
+ -H "Content-Type: application/json" \
59
+ -d "$payload")
60
+
61
+ job_id=$(echo "$submit" | jq -r '.id // empty')
62
+ if [ -z "$job_id" ]; then
63
+ echo "Submit failed: $(echo "$submit" | jq -r '.message // .')" >&2
64
+ exit 1
65
+ fi
66
+ echo "Submitted $job_id" >&2
67
+
68
+ while :; do
69
+ sleep 30
70
+ resp=$(curl -sS "$base_url/v1/videos/$job_id" -H "Authorization: Bearer $LETTA_API_KEY")
71
+ # Avoid the name `status` — zsh treats it as read-only.
72
+ st=$(echo "$resp" | jq -r '.status')
73
+ echo "Status: $st" >&2
74
+ case "$st" in
75
+ completed) break ;;
76
+ failed|cancelled|expired)
77
+ echo "Generation $st: $(echo "$resp" | jq -r '.error // "unknown"')" >&2
78
+ exit 1 ;;
79
+ esac
80
+ done
81
+
82
+ curl -sS -f "$base_url/v1/videos/$job_id/content?index=0" \
83
+ -H "Authorization: Bearer $LETTA_API_KEY" \
84
+ --output "$OUTPUT"
85
+
86
+ echo "$resp" | jq --arg out "$(realpath "$OUTPUT")" \
87
+ '{job_id: .id, video_saved: $out, billing}'
88
+ ```
89
+
90
+ Share the saved file the way your current surface supports: attach or upload it to the conversation or channel you are replying in. Keep credit amounts out of replies unless the user asks about cost; `billing.credits_charged` on the completed job has them.
91
+
92
+ ## Parameters
93
+
94
+ Required: `model`. `prompt` is required by every model except those that can generate from image input alone. Common optional fields:
95
+
96
+ - `duration` (int) — must be one of the model's `supported_durations`.
97
+ - `resolution` (string) / `aspect_ratio` (string) / `size` (string, `"WxH"`) — `size` is interchangeable with resolution + aspect_ratio.
98
+ - `generate_audio` (bool) — only meaningful if the model's `generate_audio` capability is true.
99
+ - `seed` (int) — honored only if the model's `seed` capability is true.
100
+ - `frame_images[]` — image-to-video; each entry is `{ type: "image_url", image_url: { url }, frame_type: "first_frame" | "last_frame" }`.
101
+ - `input_references[]` — reference-to-video (style guidance); image entries have the same shape, no `frame_type`. Audio and video references are `{ type: "audio_url", audio_url: { url } }` and `{ type: "video_url", video_url: { url } }`; only some models (such as `bytedance/seedance-2.5`) use them, and the rest ignore them. If both arrays are present, `frame_images` wins.
102
+ - `provider.options.<slug>.parameters.<key>` — provider passthrough, see below.
103
+
104
+ Image `url` can be a public `https://` URL or a local-file data URL: `MIME=image/png; B64=$(base64 < file.png | tr -d '\n'); url="data:${MIME};base64,${B64}"`. Audio and video references take URLs the same way.
105
+
106
+ ## Provider passthrough
107
+
108
+ Provider-specific params go under `provider.options.<slug>.parameters`. The allowed keys for a given model are listed (flat) in `allowed_passthrough_parameters` on the models endpoint — but the meaning, value range, and required combinations come from the *upstream provider's* API docs (Google Vertex, Alibaba Dashscope, Kwai, ByteDance Volc Engine, MiniMax, OpenAI, etc.). Read the upstream docs before using an unfamiliar key; casing conventions differ between providers (Google/OpenAI use camelCase, most others use snake_case).
109
+
110
+ Example:
111
+
112
+ ```json
113
+ {
114
+ "model": "google/veo-3.1",
115
+ "prompt": "a time-lapse of a flower blooming",
116
+ "provider": {
117
+ "options": {
118
+ "google-vertex": {
119
+ "parameters": {
120
+ "personGeneration": "allow",
121
+ "negativePrompt": "blurry, low quality"
122
+ }
123
+ }
124
+ }
125
+ }
126
+ }
127
+ ```
128
+
129
+ `heygen/avatar-iv` animates one photo into a lip-synced talking head: pass the photo as an image in `input_references` and describe the video in `prompt`. Its passthrough keys (slug `heygen`) include `voice_id`, `motion_prompt`, and `expressiveness`; take their values from HeyGen's API docs.
130
+
131
+ ```json
132
+ {
133
+ "model": "heygen/avatar-iv",
134
+ "prompt": "A friendly presenter welcomes viewers to the product tour",
135
+ "aspect_ratio": "9:16",
136
+ "input_references": [
137
+ { "type": "image_url", "image_url": { "url": "data:image/png;base64,..." } }
138
+ ]
139
+ }
140
+ ```
141
+
142
+ ## Chaining with other skills
143
+
144
+ - **Keyframes:** generate stills with the `image-generation` skill, then pass them as `frame_images` (`first_frame` / `last_frame`) or image `input_references` as data URLs.
145
+ - **Sound:** make a voiceover with the `speech-generation` skill or a soundtrack with the `music-generation` skill, then pass the audio as an `audio_url` input reference to a model that uses audio, or mix it onto the finished MP4 (for example with `ffmpeg`).
146
+
147
+ ## Notes
148
+
149
+ - **Billing**: the organization needs credits for an estimate before a job is submitted; once it completes, it is charged for the video's cost. Don't resubmit needlessly.
150
+ - **Errors**: `402` = insufficient credits (`credits_required` in body); `400`/`404`/`500` return `{ "message": "..." }` — surface it to the user.
151
+
152
+ Video generation is not ZDR-eligible because the provider must temporarily retain the output for the async download step.
@@ -256,7 +256,7 @@ quick checks.
256
256
 
257
257
  Every run persists its script, args, and a `journal.jsonl` with one line per
258
258
  completed subagent call (prompt, outcome, conversation id) under
259
- `~/.letta/workflows/executions/<id>/`; the tool result names the paths. Before
259
+ `${LETTA_CONFIG_DIR:-$HOME/.letta}/workflows/executions/<id>/`; the tool result names the paths. Before
260
260
  diagnosing why a workflow returned an empty or unexpected result, read that
261
261
  journal — it records each agent's actual return value and failure detail.
262
262
  A failed run is not resumable: fix the script and launch it again.