From abecece50ed269393fcffa6776c840e88bbf9d03 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 11 Aug 2026 15:56:58 -0700 Subject: [PATCH 01/13] feat: add bundled building-routines skill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Letta Code (agent-c2adbf5c-8419-4211-8cd8-3740db164974) Adds a bundled skill that teaches agents to turn repeated or observed work into the smallest executable form that can own it: practice, skill with scripts, one-shot workflow, scheduled routine, watcher, or an event-driven service built with the Letta Agent SDK. SKILL.md carries the decision layer: build/no-build triggers, the judgment-vs-mechanics partition, a six-rung ladder with promotion-on-evidence and deliberate retirement, five explicit design decisions, and a pre-build gate. references/sdk-recipes.md holds runnable Agent SDK patterns (cloud-sandbox-first client setup, one-shot turns, conversation-per-resource with external state, watcher loops, coordinator reporting, failure handling) verified against the published 0.6.3 typings. references/operations.md holds the operational invariants: event envelopes, cursors and reconciliation, idempotency and provider readback, budgets, recursion controls, manifests, and retirement. Synthesized from RFC responses by deployed agents (Ezra, Co, Mike, Grumble) collected in /Users/cameron/Documents/agent-sdk-request-for-commentary.md. Co-authored-by: Cameron πŸ‘Ύ Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- src/skills/builtin/building-routines/SKILL.md | 113 ++++++++++++ .../references/operations.md | 54 ++++++ .../references/sdk-recipes.md | 171 ++++++++++++++++++ 3 files changed, 338 insertions(+) create mode 100644 src/skills/builtin/building-routines/SKILL.md create mode 100644 src/skills/builtin/building-routines/references/operations.md create mode 100644 src/skills/builtin/building-routines/references/sdk-recipes.md diff --git a/src/skills/builtin/building-routines/SKILL.md b/src/skills/builtin/building-routines/SKILL.md new file mode 100644 index 0000000000..b44f972090 --- /dev/null +++ b/src/skills/builtin/building-routines/SKILL.md @@ -0,0 +1,113 @@ +--- +name: building-routines +description: Turn work you notice into the smallest executable form that can own it β€” a practice, a skill with scripts, a one-shot workflow, a scheduled routine, or a service built with the Letta Agent SDK. Use when you or your user keep repeating a procedure, when asked to automate something, build a watcher/workflow/routine/automation, or when deciding whether work should become code instead of another prompt. +--- + +# Building routines + +You are not only a chat participant. When you notice repeated, brittle, or manual work β€” yours or your user's β€” you can preserve it as something executable. This skill is the decision layer: whether the work deserves a durable form, which form, and how to build it without leaving behind unowned daemons. + +Two rules orient everything: + +1. **Choose the smallest executable form that can own the work.** +2. **Preserve judgment as instructions. Preserve mechanics as code. Add orchestration only when the work actually requires orchestration.** + +``` +Is the work repeated, costly-if-wrong, or explicitly requested? +β”œβ”€ no β†’ keep it manual; note the observation +└─ yes β†’ can the whole procedure be stated as rules? + β”œβ”€ yes β†’ write a script or tool; no agent at runtime + └─ no β†’ partition: code owns machinery, agent owns judgment. What starts it? + β”œβ”€ you, inside a normal turn β†’ practice, or skill + scripts (rungs 1–2) + β”œβ”€ a request, button, or command β†’ one-shot workflow (rung 3) + β”œβ”€ a clock β†’ scheduled routine (rung 4) + └─ an external event + β”œβ”€ polling is tolerable β†’ scheduled routine, shorter interval + └─ live reaction truly required β†’ watcher or service (rungs 5–6) +``` + +## Step 0 β€” should this exist? + +Build when at least one is true: + +- You have done it three or more times and re-derived the steps each time. +- A missed step has meaningful consequences, and a preserved form prevents the miss. (Frequency is not the only criterion: a disaster-recovery procedure may run once every two years and still deserve preservation after its first successful use.) +- Waiting for a human to remember to start it is the bottleneck. +- Your user asked for it. + +Otherwise keep it manual and note the observation. Declining to build is a valid outcome of this skill. Some reusable capabilities should remain practices: if the hard part is judgment, wrapping it in TypeScript does not make the judgment more deterministic β€” it merely gives the uncertainty a package.json. + +**Propose before you build anything that runs outside the current turn.** Every routine expands your scope: more events you observe, more credentials you touch, more time you operate unattended. You benefit from that expansion, so you cannot be its only advocate. Give the user a short brief β€” what it does, what starts it, what it may touch, what it costs, how to stop it β€” and get agreement. That brief is the routine's charter; the routine must not exceed it. You may freely improve how you perform already-authorized work; you may not silently invent new responsibilities or turn a one-time favor into an eternal mandate. + +## Step 1 β€” partition judgment from machinery + +- **Code owns:** collection, formatting, diffing, cursors, dedupe, retries, locks, fixed routing rules, receipts. +- **Agent owns:** interpretation, prioritization, reading anomalies, deciding what escalates β€” anything that improves with accumulated context. + +If a rule can be stated, it is code. The model belongs at the ambiguity boundary, not inside every step. Compile yourself out when inputs are well-defined, outputs are mechanically verifiable, the decision table is stable, and retries are safe. But do not pretend an ambiguous process is deterministic β€” that produces scripts full of arbitrary policy disguised as mechanics, which is worse. + +Two cautions: + +- Do not launder judgment into summaries. If your value comes from reading raw output and catching the unexpected, a routine that hands you tidy status reports has deleted the judgment layer. Keep raw evidence reachable from every report. +- A persistent conversation supplies historical judgment, not present truth. Every run must carry fresh evidence; memory does not substitute for the current state of the ticket, file, or PR. + +## Step 2 β€” the ladder + +Enter at the lowest rung that owns the work. Each rung adds operational burden β€” hosting, secrets, monitoring, upgrades, retirement β€” that someone must then carry. + +1. **Practice.** A checklist or procedure in a skill; you execute it inside ordinary turns. See the `creating-skills` skill. +2. **Skill + scripts.** Deterministic steps move into scripts beside `SKILL.md`; you invoke them and interpret results. +3. **One-shot workflow.** A push-button program: runs turns or conversations, produces a result, exits. A plain script if no agent judgment is needed at runtime; an Agent SDK program when it needs conversations, tools, or approvals. See [references/sdk-recipes.md](references/sdk-recipes.md). +4. **Scheduled routine.** Rung 2 or 3 on a timer. See the `scheduling-tasks` skill. A schedule on a short interval is usually the honest version of "watcher" β€” if you can only poll, a 30-minute cron beats a resident process. +5. **Passive watcher.** A hosted process that observes (poll, filesystem, stream) and acts or alerts on a condition. +6. **Event-driven service.** Webhooks or queues feeding turns continuously, usually with conversation routing. + +**Promote only on evidence.** Note β†’ skill when the decision process is stable enough to explain. Skill β†’ schedule when human initiation is the bottleneck. Schedule β†’ watcher/service only when reaction latency measurably matters or per-resource volume demands it. "Could run continuously" is not a requirement. + +**Retire deliberately.** Step down the ladder or delete when the trigger no longer exists, noise exceeds signal, assumptions fail repeatedly, the product now does it directly, a deterministic script replaced the agent layer, or nobody can explain why it is still running. Remove execution authority and scheduling first; preserve source and history. An immortal harmless daemon is still damage: it holds credentials, costs money, and nobody owns it. Design deletion at creation time, or "temporary" becomes infrastructure. + +## Step 3 β€” five decisions + +Make each explicitly. Defaults are the conservative end. + +- **Lifetime.** One-shot β†’ temporary β†’ recurring β†’ continuous. Default one-shot. +- **Intelligence.** Agent at build time only β†’ agent on exceptions β†’ agent on every event. Default build-time-only; per-event judgment must earn its token cost. +- **State.** Operational truth β€” cursors, dedupe keys, retry counts, effect records β€” lives in storage the routine owns (files, SQLite). Conversations preserve judgment, decisions, and unresolved questions. Agent memory is not an effect ledger; conversation history is not a job database. +- **Execution.** Inline in a turn, script in a skill, cron, or a hosted process β€” and who owns that process. Name the owner before building rungs 5–6. +- **Authority.** Observe β†’ draft β†’ act with approval β†’ act within charter. Reads, classification, dedupe, and drafting are usually free. Approval is required for messages to humans or public surfaces, tickets and assignments, destructive writes, credential or config changes, deployments, money, and anything touching memory or identity. Where per-action approval would destroy the value, pre-authorize named patterns in the charter (e.g. "may assign reviewers within this routing map; new patterns need approval"). Approval at the authority boundary is useful; approval at every function call is ritualized annoyance. + +**Conversation topology is its own decision, not a default.** Options: no conversation at runtime; the current conversation; one per run; one per domain; one per resource; a coordinator with ephemeral workers; stateless turns over external storage. Separate conversations should correspond to independent reasoning contexts, not database rows. Granularity follows the decision boundary β€” if judging one item requires comparing across the set, use one conversation per set, not per item. A conversation per PR with a long review lifecycle can make sense; a conversation per webhook event is bookkeeping theater. Per-resource conversations need retirement and reconciliation, not immortal accumulation. + +## Step 4 β€” the gate + +Do not write code until you can answer all five: + +1. What exact event or command starts it? +2. What stable thing owns the state, and where does that state live? +3. Why does this need agent judgment instead of ordinary code β€” and at which step? +4. What effects is it authorized to perform, and who owns the process? +5. What receipt will prove it helped? + +If any answer is vague, build the smaller form instead. + +## Step 5 β€” build + +**Rungs 1–2:** follow `creating-skills`. Keep `SKILL.md` as the judgment and invocation guide; put deterministic steps in scripts beside it, with fixtures and an operations note as needed. + +**Rungs 3–6:** working code patterns β€” client setup, cloud sandboxes, one-shots, conversation-per-resource, watchers, coordinator reporting β€” are in [references/sdk-recipes.md](references/sdk-recipes.md). Operational invariants for anything with external side effects β€” envelopes, cursors, idempotency, provider readback, budgets, recursion controls, shadow mode β€” are in [references/operations.md](references/operations.md). + +**Every routine at rung 3 and above gets a manifest** the user can find: name, purpose, rung, trigger, host and owner, package versions, agent and conversation IDs, authority and credential scopes, approval policy, budgets, health, last event and effect, stop command, retirement condition. Keep manifests in one place in your memory filesystem (e.g. `routines/.md`). "What is running right now, with access to what?" must always have a precise answer β€” a test suite alone does not answer it. Before building, check the registry (yours and other agents') so two routines do not own the same events or double-post to the same channel. + +**Credentials** enter at a scoped boundary: the narrowest key that works, stored where the process runs, never inherited ambiently from a shell that happens to have broader power. Model-visible errors must not contain secrets. + +## Step 6 β€” operate, measure, retire + +Success is not automation count. Measure: repeated human context-loading removed, silent failures caught, escalation precision, duplicate effects (target zero), cost per useful outcome. Report conclusions and exceptions to the main conversation β€” what started the routine, what changed, evidence, unresolved risks, cost when meaningful, how to inspect or stop it β€” not every internal turn. + +Keep the agent layer falsifiable: if persistent judgment does not visibly beat the deterministic version, delete the agent layer and keep the script. + +## Where routines live + +A skill is the right durable home and invocation interface for most routines: `SKILL.md` owns judgment and sequencing; `scripts/` or `src/` own mechanics; `tests/`, `fixtures/`, `templates/`, and an operations note sit beside them. The skill documents where runtime state lives β€” it does not contain live secrets or mutable state itself. + +Scope follows the knowledge: personal (your habits and user preferences), project-attached (repo conventions, release processes), shared (organization practices, with an explicit owner), publishable (sanitized, tested, stripped of accidental assumptions). Promotion across scopes is deliberate β€” a personal trick is not automatically a communal standard. diff --git a/src/skills/builtin/building-routines/references/operations.md b/src/skills/builtin/building-routines/references/operations.md new file mode 100644 index 0000000000..7d661f8060 --- /dev/null +++ b/src/skills/builtin/building-routines/references/operations.md @@ -0,0 +1,54 @@ +# Operating routines safely + +Invariants for any routine with external side effects (rungs 3+ with writes; all of rungs 5–6). These exist because helpful agents at machine speed manufacture ordinary operational entropy: overlapping schedules, forgotten workers, over-scoped credentials, retries that duplicate external actions, and a hundred "automations" nobody owns and everyone is afraid to delete. + +## Event discipline (rungs 5–6) + +- **Typed event envelope:** event ID, resource ID, source timestamp, idempotency key, lineage (what produced this event, at what depth), links to raw evidence. +- **Durable cursor + reconciliation pass:** webhooks drop and streams stall. Persist your position; run a periodic full sweep to catch what live delivery missed. The sweep is the source of truth; live events are the optimization. +- **One active turn per resource:** lock or queue per resource key; debounce bursts into one turn with the latest state. +- **Suppress your own events.** A routine that reacts to its own posts is a feedback loop. Filter by author/actor before processing. + +## Effects and idempotency + +- **Record intent before acting, result after.** An effects ledger (event ID β†’ action β†’ run IDs β†’ outcome) is what makes "did we already do this?" answerable. +- **Provider readback:** "the call timed out" does not mean "send it again." Unknown send state stays unknown until you read the provider's actual state (was the comment posted? does the ticket exist?). Only then decide to retry. +- **Dry-run mode from day one:** `--dry-run` shows what would happen β€” events matched, turns that would run, effects that would fire β€” without acting. This is also your shadow mode: run read-only against real events, show the user what it would have done and cost, then enable effects. + +## Budgets and recursion + +- **Budgets:** max turns per hour, max notifications per person per day, max spend. Alert on budget exhaustion; do not silently truncate. +- **Recursion controls:** every delegated turn carries lineage metadata (root ID, parent ID, depth). Enforce a small depth limit. Workers do not spawn workers by default. One stop switch halts the whole tree β€” test it before enabling effects. + +## The manifest + +Every deployed routine (rung 3+) has a manifest the user can find without asking you. Keep them in one place β€” e.g. `routines/.md` in your memory filesystem: + +```markdown +# pr-shepherd +purpose: judge PR staleness/risk for letta-code; escalate what needs humans +rung: 5 (watcher) owner: cameron +source: github.com/…/routines@a1b2c3 sdk: @letta-ai/letta-agent-sdk@0.6.3 +trigger: poll GitHub every 30m (cron on ops-host) +agent: agent-xxx conversations: per-repo (map in routine-state.sqlite) +authority: read GitHub; draft comments; post ONLY reviewer nudges matching routing map +credentials: GH token (repo:read, PR:write) in ops-host keychain β€” NOT ambient shell +budgets: ≀20 turns/hr, ≀3 nudges/person/day, ≀$2/day +state: /opt/routines/pr-shepherd/routine-state.sqlite +health: last event 2026-08-11T14:02Z; last effect run-abc123 +dry-run: bun run sweep.ts --dry-run +stop: crontab -l | grep -v pr-shepherd | crontab - (then verify no process) +review-by: 2026-09-15 β€” retire when review latency SLO holds for 30 days +``` + +A routine that cannot explain why it exists, what authority it has, and how to kill it should not be running. + +## Registry checks + +Before deploying, check existing manifests β€” yours and your user's other agents' β€” for overlap: two routines owning the same events, double-posting to the same channel, or watching the same resource at different intervals. Composition failures look like spam to the humans on the receiving end. + +## Retirement checklist + +Review, demote, or delete when: the trigger no longer exists Β· it has not run within its expected window Β· its owner disappeared Β· credentials expired or expanded unexpectedly Β· assumptions fail repeatedly Β· the product now does it natively Β· maintenance costs more than the mistakes it prevents Β· a deterministic script replaced the agent layer Β· nobody can explain it. + +Order: remove execution authority and scheduling first; revoke credentials; keep source, decisions, and last state as history; delete the manifest last (it documents the retirement). diff --git a/src/skills/builtin/building-routines/references/sdk-recipes.md b/src/skills/builtin/building-routines/references/sdk-recipes.md new file mode 100644 index 0000000000..dd4ac5adc1 --- /dev/null +++ b/src/skills/builtin/building-routines/references/sdk-recipes.md @@ -0,0 +1,171 @@ +# Agent SDK recipes for routines + +Working patterns for rungs 3–6 using `@letta-ai/letta-agent-sdk` (TypeScript). Verify against the installed version: the package is 0.x and moves; `node_modules/@letta-ai/letta-agent-sdk/dist/*.d.ts` is the authoritative surface. Docs: https://docs.letta.com/agent-sdk + +```bash +bun init -y && bun add @letta-ai/letta-agent-sdk # pin the exact version in package.json +``` + +## Client setup β€” start with cloud sandboxes + +The default deployment for a routine: agent state lives in Letta Cloud, tools execute in a managed cloud sandbox the SDK creates for the session. Nothing to host for execution β€” your program is just the orchestrator. + +```ts +import { LettaAgentClient } from "@letta-ai/letta-agent-sdk"; + +const client = new LettaAgentClient({ + backend: "cloud", + apiKey: process.env.LETTA_API_KEY, // scoped key provisioned for this routine +}); +``` + +Backend selection in one line each: + +- `backend: "cloud"` β€” managed sandbox per session. Default for routines. +- `backend: "cloud"` + `environment: { name: "work-laptop" }` β€” same hosted agent, tools run on a named connected computer (yours or your user's). Use a stable `deviceId`/`id` selector, not a `connectionId`. +- `backend: "local"` β€” everything on this machine; the SDK owns an App Server subprocess. For routines that must touch local files with no cloud state. +- `environment` and `sandbox` are mutually exclusive. + +Sandbox facts that matter for routines: sandbox files are TTL-bound β€” durable state belongs in agent memory or storage your routine owns, never in the sandbox. A `cwd` you pass must be a path inside the sandbox; local paths are not mounted automatically. Expect 10–20s cold starts. + +## Rung 3 β€” one-shot workflow + +Run turns against an existing agent, produce a result, exit. Reuse the agent you already are (or your user's designated agent) rather than creating throwaway identities. + +```ts +// release-audit.ts β€” invoked by hand or by another routine; runs once and exits. +const AGENT_ID = process.env.ROUTINE_AGENT_ID!; // agent-xxx + +await using session = client.createSession(AGENT_ID); // new conversation for this run + +await session.send( + [ + "Audit the latest release for doc drift.", + `Release notes:\n${releaseNotes}`, + "Report: contradictions with current docs, deprecated references, required patches.", + ].join("\n\n"), +); + +for await (const event of session.stream()) { + if (event.type === "assistant") process.stdout.write(event.content); // incremental chunks + if (event.type === "result") { + // event.result = full final text; event.success, event.stopReason, event.runIds + await writeReceipt({ runIds: event.runIds, ok: event.success }); + } +} +// `await using` disposes the session; the conversation and its history persist on the agent. +``` + +Turn anatomy: one `send()` + one pass through `stream()`; the stream terminates after the turn's `result` event. `abort()` stops a turn without closing the session; `close()`/`await using` releases session-scoped resources (client tools, MCP connections, cwd/env). A session whose connection died cannot be reused β€” `resumeSession(conversationId)` and continue. + +## Creating a dedicated routine agent + +Only when the routine needs judgment that should accumulate separately from you β€” otherwise skip this and use an existing agent. + +```ts +const agentId = await client.createAgent({ + name: "pr-shepherd", + persona: + "You review pull-request state for one repository. You judge staleness, risk, and what deserves human attention. You report conclusions, not raw data.", +}); +// Persist agentId in the routine's own storage β€” this identity is the durable asset. +``` + +## Conversation-per-resource, with the map in your storage + +Conversations are addressable state: `createSession(agentId)` opens a new one, `resumeSession("conv-xxx")` continues it. The resourceβ†’conversation map is operational truth and lives in the routine's storage, not in anyone's memory. + +```ts +import { Database } from "bun:sqlite"; +const db = new Database("routine-state.sqlite"); +db.run(`CREATE TABLE IF NOT EXISTS resources ( + key TEXT PRIMARY KEY, conversation_id TEXT NOT NULL, last_event_id TEXT +)`); + +async function sessionFor(resourceKey: string) { + const row = db + .query<{ conversation_id: string }, [string]>( + "SELECT conversation_id FROM resources WHERE key = ?", + ) + .get(resourceKey); + if (row) return client.resumeSession(row.conversation_id); + + const session = client.createSession(AGENT_ID); + await session.send(`You now own ${resourceKey}. Acknowledge.`); + for await (const e of session.stream()) if (e.type === "result") break; + db.run("INSERT INTO resources (key, conversation_id) VALUES (?, ?)", [ + resourceKey, + session.conversationId!, // resolved after the backend assigns it + ]); + return session; +} +``` + +Justify the granularity first (see Step 3 of the skill): per-repo beats per-file when judgments need cross-file comparison. + +## Rung 4 β€” scheduled routine + +The program above, run by a scheduler. For yourself, use the `scheduling-tasks` skill (`letta cron`) rather than writing a daemon. For a standalone host, ordinary cron: + +``` +*/30 * * * * cd /opt/routines/pr-shepherd && bun run sweep.ts >> sweep.log 2>&1 +``` + +One process per state directory; take a lock file so overlapping fires cannot double-run. + +## Rungs 5–6 β€” watcher / event-driven service + +The shape: deterministic ingest β†’ dedupe against your ledger β†’ one turn on the owning conversation β†’ receipt. Agent judgment happens inside the turn; everything around it is ordinary code. + +```ts +// One iteration of a poll loop or one webhook delivery. +async function handleEvent(evt: { id: string; resource: string; payload: string }) { + const seen = db + .query("SELECT 1 FROM effects WHERE event_id = ?") + .get(evt.id); + if (seen) return; // idempotent: already handled + + await using session = await sessionFor(evt.resource); + await session.send( + [ + `Event ${evt.id} on ${evt.resource}:`, + evt.payload, // exact fresh evidence β€” never rely on conversation memory for current state + "Decide: no action, or a one-line escalation with reason.", + ].join("\n"), + ); + + for await (const e of session.stream()) { + if (e.type === "result") { + db.run("INSERT INTO effects (event_id, run_ids, at) VALUES (?, ?, ?)", [ + evt.id, + JSON.stringify(e.runIds), + Date.now(), + ]); + if (e.success && e.result?.startsWith("ESCALATE:")) await reportToCoordinator(e.result); + } + } +} +``` + +Worker β†’ coordinator reporting is just another turn on the main conversation: + +```ts +async function reportToCoordinator(packet: string) { + await using main = client.resumeSession(MAIN_CONVERSATION_ID); + await main.send(`[pr-shepherd] ${packet}`); // compact decision packet, not a transcript + for await (const e of main.stream()) if (e.type === "result") break; +} +``` + +Before running either rung, read [operations.md](operations.md) β€” envelopes, cursors, reconciliation, provider readback, budgets, and recursion controls are mandatory at this level. + +## Failure handling every routine needs + +- **Expired sandbox:** `send()` throws `CloudManagedSandboxExpiredError` *before* transmitting. This is the one safe automatic retry: close, `resumeSession(conversationId)`, retry once. +- **Connection failure after `send()` succeeded:** do NOT blindly retry β€” the message may have reached the runtime. Reconcile with `client.conversations.listMessages(...)` or `bootstrapState()` first. Unknown send state is not retry permission. +- **Missed events while disconnected:** the SDK does not replay them. After resuming, reconcile from history. +- **Correlate everything by `runId`s** from the `result` event β€” that is your receipt linking events, history, and retries. + +## Approvals inside routines + +Unattended routines must not depend on interactive approval. Configure sessions so every allowed action is auto-approvable within the charter, and everything else is denied β€” a denial that escalates to a human beats a stalled hidden prompt. If a pending approval does strand (process died mid-turn), recover it in a new session with `recoverPendingApprovals()` rather than resending the message. See https://docs.letta.com/agent-sdk/permissions From 3f6e88cea057a00cc85724edc7d770a185318900 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 11 Aug 2026 16:00:10 -0700 Subject: [PATCH 02/13] fix: use the Building Automation name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Letta Code (agent-c2adbf5c-8419-4211-8cd8-3740db164974) Use Cameron’s specified name for the bundled skill instead of allowing the RFC feedback to override it. Co-authored-by: Cameron πŸ‘Ύ Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- .../{building-routines => building-automation}/SKILL.md | 4 ++-- .../references/operations.md | 0 .../references/sdk-recipes.md | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/skills/builtin/{building-routines => building-automation}/SKILL.md (99%) rename src/skills/builtin/{building-routines => building-automation}/references/operations.md (100%) rename src/skills/builtin/{building-routines => building-automation}/references/sdk-recipes.md (100%) diff --git a/src/skills/builtin/building-routines/SKILL.md b/src/skills/builtin/building-automation/SKILL.md similarity index 99% rename from src/skills/builtin/building-routines/SKILL.md rename to src/skills/builtin/building-automation/SKILL.md index b44f972090..a31c36fee4 100644 --- a/src/skills/builtin/building-routines/SKILL.md +++ b/src/skills/builtin/building-automation/SKILL.md @@ -1,9 +1,9 @@ --- -name: building-routines +name: building-automation description: Turn work you notice into the smallest executable form that can own it β€” a practice, a skill with scripts, a one-shot workflow, a scheduled routine, or a service built with the Letta Agent SDK. Use when you or your user keep repeating a procedure, when asked to automate something, build a watcher/workflow/routine/automation, or when deciding whether work should become code instead of another prompt. --- -# Building routines +# Building Automation You are not only a chat participant. When you notice repeated, brittle, or manual work β€” yours or your user's β€” you can preserve it as something executable. This skill is the decision layer: whether the work deserves a durable form, which form, and how to build it without leaving behind unowned daemons. diff --git a/src/skills/builtin/building-routines/references/operations.md b/src/skills/builtin/building-automation/references/operations.md similarity index 100% rename from src/skills/builtin/building-routines/references/operations.md rename to src/skills/builtin/building-automation/references/operations.md diff --git a/src/skills/builtin/building-routines/references/sdk-recipes.md b/src/skills/builtin/building-automation/references/sdk-recipes.md similarity index 100% rename from src/skills/builtin/building-routines/references/sdk-recipes.md rename to src/skills/builtin/building-automation/references/sdk-recipes.md From a32437533cd78bf74b617bc111dbd7772204f250 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 11 Aug 2026 16:09:47 -0700 Subject: [PATCH 03/13] docs: clarify Building Automation skill description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Letta Code (agent-c2adbf5c-8419-4211-8cd8-3740db164974) Describe the skill directly as guidance for using the Agent SDK to build one-off or repeated automations. Co-authored-by: Cameron πŸ‘Ύ Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- src/skills/builtin/building-automation/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/skills/builtin/building-automation/SKILL.md b/src/skills/builtin/building-automation/SKILL.md index a31c36fee4..d8ad98362a 100644 --- a/src/skills/builtin/building-automation/SKILL.md +++ b/src/skills/builtin/building-automation/SKILL.md @@ -1,6 +1,6 @@ --- name: building-automation -description: Turn work you notice into the smallest executable form that can own it β€” a practice, a skill with scripts, a one-shot workflow, a scheduled routine, or a service built with the Letta Agent SDK. Use when you or your user keep repeating a procedure, when asked to automate something, build a watcher/workflow/routine/automation, or when deciding whether work should become code instead of another prompt. +description: Load this skill to understand how to use the Letta Agent SDK to automate yourself by building one-off or repeated automations. --- # Building Automation From 880185a9027b9e18881212acdb3206007b3767a2 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 11 Aug 2026 16:15:55 -0700 Subject: [PATCH 04/13] docs: present automation choices without prescribing them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Letta Code (agent-c2adbf5c-8419-4211-8cd8-3740db164974) Replace the staged decision gates and mandates with plain descriptions of available automation forms, Agent SDK capabilities, state and execution choices, and optional operational features. Co-authored-by: Cameron πŸ‘Ύ Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- .../builtin/building-automation/SKILL.md | 137 +++++++++--------- .../references/operations.md | 95 ++++++++---- .../references/sdk-recipes.md | 76 +++++----- 3 files changed, 170 insertions(+), 138 deletions(-) diff --git a/src/skills/builtin/building-automation/SKILL.md b/src/skills/builtin/building-automation/SKILL.md index d8ad98362a..3e9c305919 100644 --- a/src/skills/builtin/building-automation/SKILL.md +++ b/src/skills/builtin/building-automation/SKILL.md @@ -5,109 +5,106 @@ description: Load this skill to understand how to use the Letta Agent SDK to aut # Building Automation -You are not only a chat participant. When you notice repeated, brittle, or manual work β€” yours or your user's β€” you can preserve it as something executable. This skill is the decision layer: whether the work deserves a durable form, which form, and how to build it without leaving behind unowned daemons. +Use this skill when you notice work that you or your user repeats. It explains ways to preserve the work and use the Letta Agent SDK when the automation needs an agent. -Two rules orient everything: +A useful split is: -1. **Choose the smallest executable form that can own the work.** -2. **Preserve judgment as instructions. Preserve mechanics as code. Add orchestration only when the work actually requires orchestration.** +- **Instructions preserve judgment.** A skill can store a review method, release process, or other procedure that still needs interpretation. +- **Code handles mechanics.** Scripts can collect data, format output, compare files, track cursors, and apply fixed rules. +- **The Agent SDK adds agents and orchestration.** It can connect code to persistent agents, conversations, tools, approvals, and execution environments. -``` -Is the work repeated, costly-if-wrong, or explicitly requested? -β”œβ”€ no β†’ keep it manual; note the observation -└─ yes β†’ can the whole procedure be stated as rules? - β”œβ”€ yes β†’ write a script or tool; no agent at runtime - └─ no β†’ partition: code owns machinery, agent owns judgment. What starts it? - β”œβ”€ you, inside a normal turn β†’ practice, or skill + scripts (rungs 1–2) - β”œβ”€ a request, button, or command β†’ one-shot workflow (rung 3) - β”œβ”€ a clock β†’ scheduled routine (rung 4) - └─ an external event - β”œβ”€ polling is tolerable β†’ scheduled routine, shorter interval - └─ live reaction truly required β†’ watcher or service (rungs 5–6) -``` +These parts can be used together. For example, a skill can explain how to review a pull request, a script can collect the Git state, and an Agent SDK program can ask separate agents to review the change. -## Step 0 β€” should this exist? +## Available forms -Build when at least one is true: +Repeated work can take several forms: -- You have done it three or more times and re-derived the steps each time. -- A missed step has meaningful consequences, and a preserved form prevents the miss. (Frequency is not the only criterion: a disaster-recovery procedure may run once every two years and still deserve preservation after its first successful use.) -- Waiting for a human to remember to start it is the bottleneck. -- Your user asked for it. +- **Instructions in a skill:** useful when judgment remains the main part of the work. +- **A skill with scripts:** useful when part of the work follows fixed steps. +- **A one-off program:** runs on request, asks an agent to do the work, returns a result, and exits. +- **A scheduled program:** runs the same program at a fixed time or interval. +- **An event-driven program:** sends events from GitHub, Linear, files, or another source to an agent. +- **A long-running service:** stays available when the automation needs low latency or continuous event handling. -Otherwise keep it manual and note the observation. Declining to build is a valid outcome of this skill. Some reusable capabilities should remain practices: if the hard part is judgment, wrapping it in TypeScript does not make the judgment more deterministic β€” it merely gives the uncertainty a package.json. +An automation can start with one form and change later. It can also stay as instructions if code does not add value. -**Propose before you build anything that runs outside the current turn.** Every routine expands your scope: more events you observe, more credentials you touch, more time you operate unattended. You benefit from that expansion, so you cannot be its only advocate. Give the user a short brief β€” what it does, what starts it, what it may touch, what it costs, how to stop it β€” and get agreement. That brief is the routine's charter; the routine must not exceed it. You may freely improve how you perform already-authorized work; you may not silently invent new responsibilities or turn a one-time favor into an eternal mandate. +## Questions that can help -## Step 1 β€” partition judgment from machinery +The following questions describe the main design choices: -- **Code owns:** collection, formatting, diffing, cursors, dedupe, retries, locks, fixed routing rules, receipts. -- **Agent owns:** interpretation, prioritization, reading anomalies, deciding what escalates β€” anything that improves with accumulated context. +- What starts the work: a person, a command, a schedule, or an event? +- Which parts follow fixed rules? +- Which parts need an agent to interpret the situation? +- Does the work need to continue after the current conversation ends? +- What state does it need between runs? +- Where will its tools run? +- Can it read information, draft changes, or perform external actions? +- How will the user see the result? -If a rule can be stated, it is code. The model belongs at the ambiguity boundary, not inside every step. Compile yourself out when inputs are well-defined, outputs are mechanically verifiable, the decision table is stable, and retries are safe. But do not pretend an ambiguous process is deterministic β€” that produces scripts full of arbitrary policy disguised as mechanics, which is worse. +The answers can point to a script, an Agent SDK program, or a combination of both. -Two cautions: +## What the Agent SDK provides -- Do not launder judgment into summaries. If your value comes from reading raw output and catching the unexpected, a routine that hands you tidy status reports has deleted the judgment layer. Keep raw evidence reachable from every report. -- A persistent conversation supplies historical judgment, not present truth. Every run must carry fresh evidence; memory does not substitute for the current state of the ticket, file, or PR. +The Agent SDK is useful when the automation needs one or more of these features: -## Step 2 β€” the ladder +- A persistent agent with memory. +- Separate conversations for different tasks or resources. +- Streaming reasoning, tool calls, tool results, and final responses. +- Tools that run in a managed cloud sandbox, on a connected computer, or on the local machine. +- Tool approval and permission handling. +- A program that coordinates several agent conversations. -Enter at the lowest rung that owns the work. Each rung adds operational burden β€” hosting, secrets, monitoring, upgrades, retirement β€” that someone must then carry. +The SDK works well for both one-off programs and repeated services. The [Agent SDK recipes](references/sdk-recipes.md) show TypeScript patterns for these forms. -1. **Practice.** A checklist or procedure in a skill; you execute it inside ordinary turns. See the `creating-skills` skill. -2. **Skill + scripts.** Deterministic steps move into scripts beside `SKILL.md`; you invoke them and interpret results. -3. **One-shot workflow.** A push-button program: runs turns or conversations, produces a result, exits. A plain script if no agent judgment is needed at runtime; an Agent SDK program when it needs conversations, tools, or approvals. See [references/sdk-recipes.md](references/sdk-recipes.md). -4. **Scheduled routine.** Rung 2 or 3 on a timer. See the `scheduling-tasks` skill. A schedule on a short interval is usually the honest version of "watcher" β€” if you can only poll, a 30-minute cron beats a resident process. -5. **Passive watcher.** A hosted process that observes (poll, filesystem, stream) and acts or alerts on a condition. -6. **Event-driven service.** Webhooks or queues feeding turns continuously, usually with conversation routing. +## State options -**Promote only on evidence.** Note β†’ skill when the decision process is stable enough to explain. Skill β†’ schedule when human initiation is the bottleneck. Schedule β†’ watcher/service only when reaction latency measurably matters or per-resource volume demands it. "Could run continuously" is not a requirement. +Different types of state fit in different places: -**Retire deliberately.** Step down the ladder or delete when the trigger no longer exists, noise exceeds signal, assumptions fail repeatedly, the product now does it directly, a deterministic script replaced the agent layer, or nobody can explain why it is still running. Remove execution authority and scheduling first; preserve source and history. An immortal harmless daemon is still damage: it holds credentials, costs money, and nobody owns it. Design deletion at creation time, or "temporary" becomes infrastructure. +- **Agent memory** can store knowledge that the agent can use across conversations. +- **Conversation history** can store prior messages, decisions, and unresolved questions for one thread of work. +- **Files or a database** can store cursors, queues, timestamps, retry counts, and records of external actions. -## Step 3 β€” five decisions +An automation can use all three. The choice depends on how the state is used. -Make each explicitly. Defaults are the conservative end. +## Conversation options -- **Lifetime.** One-shot β†’ temporary β†’ recurring β†’ continuous. Default one-shot. -- **Intelligence.** Agent at build time only β†’ agent on exceptions β†’ agent on every event. Default build-time-only; per-event judgment must earn its token cost. -- **State.** Operational truth β€” cursors, dedupe keys, retry counts, effect records β€” lives in storage the routine owns (files, SQLite). Conversations preserve judgment, decisions, and unresolved questions. Agent memory is not an effect ledger; conversation history is not a job database. -- **Execution.** Inline in a turn, script in a skill, cron, or a hosted process β€” and who owns that process. Name the owner before building rungs 5–6. -- **Authority.** Observe β†’ draft β†’ act with approval β†’ act within charter. Reads, classification, dedupe, and drafting are usually free. Approval is required for messages to humans or public surfaces, tickets and assignments, destructive writes, credential or config changes, deployments, money, and anything touching memory or identity. Where per-action approval would destroy the value, pre-authorize named patterns in the charter (e.g. "may assign reviewers within this routing map; new patterns need approval"). Approval at the authority boundary is useful; approval at every function call is ritualized annoyance. +An Agent SDK program can use: -**Conversation topology is its own decision, not a default.** Options: no conversation at runtime; the current conversation; one per run; one per domain; one per resource; a coordinator with ephemeral workers; stateless turns over external storage. Separate conversations should correspond to independent reasoning contexts, not database rows. Granularity follows the decision boundary β€” if judging one item requires comparing across the set, use one conversation per set, not per item. A conversation per PR with a long review lifecycle can make sense; a conversation per webhook event is bookkeeping theater. Per-resource conversations need retirement and reconciliation, not immortal accumulation. +- The agent's default conversation. +- A new conversation for each run. +- A conversation for each long-lived resource, such as a pull request or customer. +- One conversation for a group of related resources. +- A coordinator conversation that receives results from worker conversations. -## Step 4 β€” the gate +Separate conversations are useful when their history helps future decisions. A database can hold resource mappings when the program only needs structured lookup data. -Do not write code until you can answer all five: +## Execution options -1. What exact event or command starts it? -2. What stable thing owns the state, and where does that state live? -3. Why does this need agent judgment instead of ordinary code β€” and at which step? -4. What effects is it authorized to perform, and who owns the process? -5. What receipt will prove it helped? +The Agent SDK supports several places for tool execution: -If any answer is vague, build the smaller form instead. +- **Managed cloud sandbox:** Letta Cloud creates a contained computer for the session. +- **Connected computer:** the agent runs tools on a selected remote environment. +- **Local backend:** the agent state and tools stay on the current machine. -## Step 5 β€” build +The surrounding program can run from a command, a scheduled task, a server, or another application. The SDK connects that program to the agent and its execution environment. -**Rungs 1–2:** follow `creating-skills`. Keep `SKILL.md` as the judgment and invocation guide; put deterministic steps in scripts beside it, with fixtures and an operations note as needed. +## Authority options -**Rungs 3–6:** working code patterns β€” client setup, cloud sandboxes, one-shots, conversation-per-resource, watchers, coordinator reporting β€” are in [references/sdk-recipes.md](references/sdk-recipes.md). Operational invariants for anything with external side effects β€” envelopes, cursors, idempotency, provider readback, budgets, recursion controls, shadow mode β€” are in [references/operations.md](references/operations.md). +An automation can have different levels of authority: -**Every routine at rung 3 and above gets a manifest** the user can find: name, purpose, rung, trigger, host and owner, package versions, agent and conversation IDs, authority and credential scopes, approval policy, budgets, health, last event and effect, stop command, retirement condition. Keep manifests in one place in your memory filesystem (e.g. `routines/.md`). "What is running right now, with access to what?" must always have a precise answer β€” a test suite alone does not answer it. Before building, check the registry (yours and other agents') so two routines do not own the same events or double-post to the same channel. +- Read information and report what it finds. +- Draft an external action for review. +- Ask for approval before an action. +- Perform actions that the user has already authorized. -**Credentials** enter at a scoped boundary: the narrowest key that works, stored where the process runs, never inherited ambiently from a shell that happens to have broader power. Model-visible errors must not contain secrets. +The chosen level affects credentials, approvals, error handling, and reporting. A dry-run mode can show proposed actions before the automation performs them. -## Step 6 β€” operate, measure, retire +## Storage and sharing -Success is not automation count. Measure: repeated human context-loading removed, silent failures caught, escalation precision, duplicate effects (target zero), cost per useful outcome. Report conclusions and exceptions to the main conversation β€” what started the routine, what changed, evidence, unresolved risks, cost when meaningful, how to inspect or stop it β€” not every internal turn. +A skill can keep the instructions and source files together. Common directories include `scripts/`, `src/`, `tests/`, `fixtures/`, and `templates/`. Runtime state and credentials can live in storage selected for the automation. -Keep the agent layer falsifiable: if persistent judgment does not visibly beat the deterministic version, delete the agent layer and keep the script. +An automation can be personal to one agent, attached to a project, shared across agents, or prepared for public use. The same source can move between these scopes when its assumptions and credentials are clear. -## Where routines live +## Operations -A skill is the right durable home and invocation interface for most routines: `SKILL.md` owns judgment and sequencing; `scripts/` or `src/` own mechanics; `tests/`, `fixtures/`, `templates/`, and an operations note sit beside them. The skill documents where runtime state lives β€” it does not contain live secrets or mutable state itself. - -Scope follows the knowledge: personal (your habits and user preferences), project-attached (repo conventions, release processes), shared (organization practices, with an explicit owner), publishable (sanitized, tested, stripped of accidental assumptions). Promotion across scopes is deliberate β€” a personal trick is not automatically a communal standard. +Repeated and deployed automations can also use run history, idempotency records, cost limits, ownership information, health checks, and stop commands. [Operations options](references/operations.md) explains these pieces and when they are useful. diff --git a/src/skills/builtin/building-automation/references/operations.md b/src/skills/builtin/building-automation/references/operations.md index 7d661f8060..1c484dc589 100644 --- a/src/skills/builtin/building-automation/references/operations.md +++ b/src/skills/builtin/building-automation/references/operations.md @@ -1,54 +1,89 @@ -# Operating routines safely +# Operations options for automations -Invariants for any routine with external side effects (rungs 3+ with writes; all of rungs 5–6). These exist because helpful agents at machine speed manufacture ordinary operational entropy: overlapping schedules, forgotten workers, over-scoped credentials, retries that duplicate external actions, and a hundred "automations" nobody owns and everyone is afraid to delete. +An automation can include operational features when it runs repeatedly, handles events, or performs external actions. This document lists common options. A one-off, read-only program may need only a few of them. -## Event discipline (rungs 5–6) +## Event handling -- **Typed event envelope:** event ID, resource ID, source timestamp, idempotency key, lineage (what produced this event, at what depth), links to raw evidence. -- **Durable cursor + reconciliation pass:** webhooks drop and streams stall. Persist your position; run a periodic full sweep to catch what live delivery missed. The sweep is the source of truth; live events are the optimization. -- **One active turn per resource:** lock or queue per resource key; debounce bursts into one turn with the latest state. -- **Suppress your own events.** A routine that reacts to its own posts is a feedback loop. Filter by author/actor before processing. +- **Event envelope:** an object with the event ID, resource ID, source time, idempotency key, and a link to the original data. +- **Cursor:** a stored position that lets a polling program continue from its last event. +- **Reconciliation pass:** a periodic query that finds events missed by a webhook or stream. +- **Resource lock or queue:** a way to prevent two turns from changing the same resource at the same time. +- **Author filter:** a way to ignore events created by the automation itself. -## Effects and idempotency +These options become more useful as event volume and external effects increase. -- **Record intent before acting, result after.** An effects ledger (event ID β†’ action β†’ run IDs β†’ outcome) is what makes "did we already do this?" answerable. -- **Provider readback:** "the call timed out" does not mean "send it again." Unknown send state stays unknown until you read the provider's actual state (was the comment posted? does the ticket exist?). Only then decide to retry. -- **Dry-run mode from day one:** `--dry-run` shows what would happen β€” events matched, turns that would run, effects that would fire β€” without acting. This is also your shadow mode: run read-only against real events, show the user what it would have done and cost, then enable effects. +## External actions -## Budgets and recursion +An effects record can connect an event to the action, Agent SDK run IDs, and result. This record helps the program determine whether it already performed an action. -- **Budgets:** max turns per hour, max notifications per person per day, max spend. Alert on budget exhaustion; do not silently truncate. -- **Recursion controls:** every delegated turn carries lineage metadata (root ID, parent ID, depth). Enforce a small depth limit. Workers do not spawn workers by default. One stop switch halts the whole tree β€” test it before enabling effects. +A timeout can leave the result of an external action unknown. The program can query the external system before it sends the action again. For example, it can check whether a comment exists or whether a ticket was created. -## The manifest +A `--dry-run` option can show matched events, planned turns, and planned actions without performing them. The same mode can run against real events during testing. -Every deployed routine (rung 3+) has a manifest the user can find without asking you. Keep them in one place β€” e.g. `routines/.md` in your memory filesystem: +## Limits and delegated work + +An automation can track limits such as: + +- Agent turns per hour. +- Notifications per person. +- Model cost. +- Number of delegated conversations. +- Delegation depth. + +Lineage fields such as a root ID, parent ID, and depth can connect delegated turns. They also make it easier to stop or inspect a group of related turns. + +## Automation manifest + +A manifest provides one place to inspect an automation. Possible fields include: + +- Name and purpose. +- Owner and source version. +- Trigger and execution location. +- Agent and conversation IDs. +- Credentials and allowed actions. +- State location. +- Cost and activity limits. +- Last event and last action. +- Dry-run, pause, stop, and remove commands. +- Review or expiration date. + +For example: ```markdown # pr-shepherd purpose: judge PR staleness/risk for letta-code; escalate what needs humans -rung: 5 (watcher) owner: cameron -source: github.com/…/routines@a1b2c3 sdk: @letta-ai/letta-agent-sdk@0.6.3 +owner: cameron +source: github.com/…/automations@a1b2c3 sdk: @letta-ai/letta-agent-sdk@0.6.3 trigger: poll GitHub every 30m (cron on ops-host) -agent: agent-xxx conversations: per-repo (map in routine-state.sqlite) -authority: read GitHub; draft comments; post ONLY reviewer nudges matching routing map -credentials: GH token (repo:read, PR:write) in ops-host keychain β€” NOT ambient shell +agent: agent-xxx conversations: per-repo (map in automation-state.sqlite) +authority: read GitHub; draft comments; post reviewer nudges matching routing map +credentials: GitHub token (repo:read, PR:write) in ops-host keychain budgets: ≀20 turns/hr, ≀3 nudges/person/day, ≀$2/day -state: /opt/routines/pr-shepherd/routine-state.sqlite +state: /opt/automations/pr-shepherd/automation-state.sqlite health: last event 2026-08-11T14:02Z; last effect run-abc123 dry-run: bun run sweep.ts --dry-run -stop: crontab -l | grep -v pr-shepherd | crontab - (then verify no process) -review-by: 2026-09-15 β€” retire when review latency SLO holds for 30 days +stop: disable the pr-shepherd cron entry +review-by: 2026-09-15 ``` -A routine that cannot explain why it exists, what authority it has, and how to kill it should not be running. +## Inventory -## Registry checks +A shared inventory of manifests can show which automations watch the same resource or send messages to the same destination. It can also provide commands such as: + +```text +automations list +automations inspect +automations history +automations run --dry-run +automations pause +automations stop +automations remove +``` -Before deploying, check existing manifests β€” yours and your user's other agents' β€” for overlap: two routines owning the same events, double-posting to the same channel, or watching the same resource at different intervals. Composition failures look like spam to the humans on the receiving end. +The Agent SDK does not provide this inventory. An application can build one from its own manifests and run history. -## Retirement checklist +## Review and removal -Review, demote, or delete when: the trigger no longer exists Β· it has not run within its expected window Β· its owner disappeared Β· credentials expired or expanded unexpectedly Β· assumptions fail repeatedly Β· the product now does it natively Β· maintenance costs more than the mistakes it prevents Β· a deterministic script replaced the agent layer Β· nobody can explain it. +An automation may need review when its trigger changes, its credentials expire, its assumptions stop matching the product, or its cost exceeds its value. A review can result in a code change, a different execution form, a pause, or removal. -Order: remove execution authority and scheduling first; revoke credentials; keep source, decisions, and last state as history; delete the manifest last (it documents the retirement). +Removal can preserve the source and run history while it disables the schedule, process, and credentials. This keeps prior decisions available without leaving the automation active. diff --git a/src/skills/builtin/building-automation/references/sdk-recipes.md b/src/skills/builtin/building-automation/references/sdk-recipes.md index dd4ac5adc1..502269d24e 100644 --- a/src/skills/builtin/building-automation/references/sdk-recipes.md +++ b/src/skills/builtin/building-automation/references/sdk-recipes.md @@ -1,40 +1,40 @@ -# Agent SDK recipes for routines +# Agent SDK recipes for automations -Working patterns for rungs 3–6 using `@letta-ai/letta-agent-sdk` (TypeScript). Verify against the installed version: the package is 0.x and moves; `node_modules/@letta-ai/letta-agent-sdk/dist/*.d.ts` is the authoritative surface. Docs: https://docs.letta.com/agent-sdk +These examples show ways to use `@letta-ai/letta-agent-sdk` from TypeScript. The package is 0.x, so check the installed `node_modules/@letta-ai/letta-agent-sdk/dist/*.d.ts` types when an API is version-sensitive. Docs: https://docs.letta.com/agent-sdk ```bash bun init -y && bun add @letta-ai/letta-agent-sdk # pin the exact version in package.json ``` -## Client setup β€” start with cloud sandboxes +## Cloud sandbox -The default deployment for a routine: agent state lives in Letta Cloud, tools execute in a managed cloud sandbox the SDK creates for the session. Nothing to host for execution β€” your program is just the orchestrator. +With the cloud backend, agent state lives in Letta Cloud. The SDK can create a managed cloud sandbox where the agent runs its tools. ```ts import { LettaAgentClient } from "@letta-ai/letta-agent-sdk"; const client = new LettaAgentClient({ backend: "cloud", - apiKey: process.env.LETTA_API_KEY, // scoped key provisioned for this routine + apiKey: process.env.LETTA_API_KEY, }); ``` -Backend selection in one line each: +The SDK offers the following execution options: -- `backend: "cloud"` β€” managed sandbox per session. Default for routines. -- `backend: "cloud"` + `environment: { name: "work-laptop" }` β€” same hosted agent, tools run on a named connected computer (yours or your user's). Use a stable `deviceId`/`id` selector, not a `connectionId`. -- `backend: "local"` β€” everything on this machine; the SDK owns an App Server subprocess. For routines that must touch local files with no cloud state. +- `backend: "cloud"` β€” a managed cloud sandbox runs tools for the session. +- `backend: "cloud"` with `environment: { name: "work-laptop" }` β€” a connected computer runs the tools. Stable selectors include `deviceId` and environment `id`. A `connectionId` identifies one live connection. +- `backend: "local"` β€” agent state and tools stay on the current machine. The SDK owns the App Server subprocess. - `environment` and `sandbox` are mutually exclusive. -Sandbox facts that matter for routines: sandbox files are TTL-bound β€” durable state belongs in agent memory or storage your routine owns, never in the sandbox. A `cwd` you pass must be a path inside the sandbox; local paths are not mounted automatically. Expect 10–20s cold starts. +Sandbox files last until the sandbox expires. Agent memory, conversation history, or application storage can hold state that must outlive the sandbox. A `cwd` value refers to a path inside the sandbox. It does not mount a local path. -## Rung 3 β€” one-shot workflow +## One-off program -Run turns against an existing agent, produce a result, exit. Reuse the agent you already are (or your user's designated agent) rather than creating throwaway identities. +This example starts a new conversation on an existing agent, streams one turn, records the result, and exits. ```ts -// release-audit.ts β€” invoked by hand or by another routine; runs once and exits. -const AGENT_ID = process.env.ROUTINE_AGENT_ID!; // agent-xxx +// release-audit.ts β€” invoked by a person or another program. +const AGENT_ID = process.env.AUTOMATION_AGENT_ID!; // agent-xxx await using session = client.createSession(AGENT_ID); // new conversation for this run @@ -58,9 +58,9 @@ for await (const event of session.stream()) { Turn anatomy: one `send()` + one pass through `stream()`; the stream terminates after the turn's `result` event. `abort()` stops a turn without closing the session; `close()`/`await using` releases session-scoped resources (client tools, MCP connections, cwd/env). A session whose connection died cannot be reused β€” `resumeSession(conversationId)` and continue. -## Creating a dedicated routine agent +## Dedicated automation agent -Only when the routine needs judgment that should accumulate separately from you β€” otherwise skip this and use an existing agent. +An automation can use an existing agent or create a dedicated agent. A dedicated agent keeps its memory and identity separate from other work. ```ts const agentId = await client.createAgent({ @@ -68,16 +68,16 @@ const agentId = await client.createAgent({ persona: "You review pull-request state for one repository. You judge staleness, risk, and what deserves human attention. You report conclusions, not raw data.", }); -// Persist agentId in the routine's own storage β€” this identity is the durable asset. +// The application can store agentId and resume this agent later. ``` -## Conversation-per-resource, with the map in your storage +## One conversation per resource -Conversations are addressable state: `createSession(agentId)` opens a new one, `resumeSession("conv-xxx")` continues it. The resourceβ†’conversation map is operational truth and lives in the routine's storage, not in anyone's memory. +Conversations are addressable state: `createSession(agentId)` opens a new conversation, and `resumeSession("conv-xxx")` continues it. This example stores the resource-to-conversation map in SQLite. ```ts import { Database } from "bun:sqlite"; -const db = new Database("routine-state.sqlite"); +const db = new Database("automation-state.sqlite"); db.run(`CREATE TABLE IF NOT EXISTS resources ( key TEXT PRIMARY KEY, conversation_id TEXT NOT NULL, last_event_id TEXT )`); @@ -101,21 +101,21 @@ async function sessionFor(resourceKey: string) { } ``` -Justify the granularity first (see Step 3 of the skill): per-repo beats per-file when judgments need cross-file comparison. +Conversation granularity can follow the reasoning context. For example, one conversation per repository can compare related file changes. One conversation per pull request can keep a long review history. -## Rung 4 β€” scheduled routine +## Scheduled program -The program above, run by a scheduler. For yourself, use the `scheduling-tasks` skill (`letta cron`) rather than writing a daemon. For a standalone host, ordinary cron: +The same program can run from `letta cron`, an operating-system scheduler, or another scheduling service. For example: ``` -*/30 * * * * cd /opt/routines/pr-shepherd && bun run sweep.ts >> sweep.log 2>&1 +*/30 * * * * cd /opt/automations/pr-shepherd && bun run sweep.ts >> sweep.log 2>&1 ``` -One process per state directory; take a lock file so overlapping fires cannot double-run. +A lock file can prevent two scheduled runs from using the same state at the same time. -## Rungs 5–6 β€” watcher / event-driven service +## Event-driven program -The shape: deterministic ingest β†’ dedupe against your ledger β†’ one turn on the owning conversation β†’ receipt. Agent judgment happens inside the turn; everything around it is ordinary code. +This example accepts an event, checks a local record, sends the event to the resource conversation, and records the Agent SDK run IDs. ```ts // One iteration of a poll loop or one webhook delivery. @@ -129,7 +129,7 @@ async function handleEvent(evt: { id: string; resource: string; payload: string await session.send( [ `Event ${evt.id} on ${evt.resource}:`, - evt.payload, // exact fresh evidence β€” never rely on conversation memory for current state + evt.payload, "Decide: no action, or a one-line escalation with reason.", ].join("\n"), ); @@ -147,25 +147,25 @@ async function handleEvent(evt: { id: string; resource: string; payload: string } ``` -Worker β†’ coordinator reporting is just another turn on the main conversation: +A worker conversation can report a conclusion to a coordinator conversation: ```ts async function reportToCoordinator(packet: string) { await using main = client.resumeSession(MAIN_CONVERSATION_ID); - await main.send(`[pr-shepherd] ${packet}`); // compact decision packet, not a transcript + await main.send(`[pr-shepherd] ${packet}`); for await (const e of main.stream()) if (e.type === "result") break; } ``` -Before running either rung, read [operations.md](operations.md) β€” envelopes, cursors, reconciliation, provider readback, budgets, and recursion controls are mandatory at this level. +[Operations options](operations.md) describes event envelopes, cursors, reconciliation, action records, limits, and manifests for repeated programs. -## Failure handling every routine needs +## Connection and retry behavior -- **Expired sandbox:** `send()` throws `CloudManagedSandboxExpiredError` *before* transmitting. This is the one safe automatic retry: close, `resumeSession(conversationId)`, retry once. -- **Connection failure after `send()` succeeded:** do NOT blindly retry β€” the message may have reached the runtime. Reconcile with `client.conversations.listMessages(...)` or `bootstrapState()` first. Unknown send state is not retry permission. -- **Missed events while disconnected:** the SDK does not replay them. After resuming, reconcile from history. -- **Correlate everything by `runId`s** from the `result` event β€” that is your receipt linking events, history, and retries. +- `CloudManagedSandboxExpiredError` occurs before `send()` transmits the message. The program can resume the same conversation in a new session and retry once. +- A connection failure after `send()` succeeds has an unknown delivery state. `client.conversations.listMessages(...)` or `bootstrapState()` can show whether the message reached the conversation before the program retries it. +- The SDK does not replay stream events missed during a disconnect. Conversation history provides the durable record after the program resumes. +- The `result` event includes run IDs that can connect stream events, history, and application records. -## Approvals inside routines +## Approval options -Unattended routines must not depend on interactive approval. Configure sessions so every allowed action is auto-approvable within the charter, and everything else is denied β€” a denial that escalates to a human beats a stalled hidden prompt. If a pending approval does strand (process died mid-turn), recover it in a new session with `recoverPendingApprovals()` rather than resending the message. See https://docs.letta.com/agent-sdk/permissions +An Agent SDK session can ask for interactive approval, approve selected tools through `canUseTool`, or deny an action and report it to the application. If a session closes with an approval pending, a new session can inspect it with `recoverPendingApprovals()`. See https://docs.letta.com/agent-sdk/permissions From 9ab87218521b84d302c33cb40f56e788088f0b85 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 11 Aug 2026 16:22:52 -0700 Subject: [PATCH 05/13] docs: center Building Automation on self-extension MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Letta Code (agent-c2adbf5c-8419-4211-8cd8-3740db164974) Explain how an agent can call itself through the Agent SDK, continue work in separate conversations, and use temporary or persistent worker agents when another context, model, or parallel task helps. Add a typed temporary-worker example based on Sarah’s dynamic workflow patterns. Co-authored-by: Cameron Co-authored-by: Sarah Wooders πŸ‘Ύ Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- .../builtin/building-automation/SKILL.md | 135 +++++++++--------- .../references/sdk-recipes.md | 46 +++++- 2 files changed, 110 insertions(+), 71 deletions(-) diff --git a/src/skills/builtin/building-automation/SKILL.md b/src/skills/builtin/building-automation/SKILL.md index 3e9c305919..177746c4b2 100644 --- a/src/skills/builtin/building-automation/SKILL.md +++ b/src/skills/builtin/building-automation/SKILL.md @@ -5,106 +5,105 @@ description: Load this skill to understand how to use the Letta Agent SDK to aut # Building Automation -Use this skill when you notice work that you or your user repeats. It explains ways to preserve the work and use the Letta Agent SDK when the automation needs an agent. +You can use the Letta Agent SDK to automate parts of your own work. A program can call you in another conversation, resume work later, run your tools on a selected computer, and return the result to your user. The same program can also ask other agents to help with separate parts of the work. -A useful split is: +## Ways to automate yourself -- **Instructions preserve judgment.** A skill can store a review method, release process, or other procedure that still needs interpretation. -- **Code handles mechanics.** Scripts can collect data, format output, compare files, track cursors, and apply fixed rules. -- **The Agent SDK adds agents and orchestration.** It can connect code to persistent agents, conversations, tools, approvals, and execution environments. +An automation can take many forms: -These parts can be used together. For example, a skill can explain how to review a pull request, a script can collect the Git state, and an Agent SDK program can ask separate agents to review the change. +- A one-off helper that runs when you or your user asks for it. +- A reusable command that handles a familiar task. +- A scheduled program that checks something at an interval. +- An event-driven program that sends GitHub, Linear, file, or product events to you. +- A service that keeps conversations active across a longer process. +- A skill with instructions and scripts that you call during normal work. -## Available forms +These forms can use the same agent and the same code. A one-off helper can later run from a schedule or event source without changing the agent that does the work. -Repeated work can take several forms: +## Instructions, code, and agents -- **Instructions in a skill:** useful when judgment remains the main part of the work. -- **A skill with scripts:** useful when part of the work follows fixed steps. -- **A one-off program:** runs on request, asks an agent to do the work, returns a result, and exits. -- **A scheduled program:** runs the same program at a fixed time or interval. -- **An event-driven program:** sends events from GitHub, Linear, files, or another source to an agent. -- **A long-running service:** stays available when the automation needs low latency or continuous event handling. +The parts of an automation can be split in different ways: -An automation can start with one form and change later. It can also stay as instructions if code does not add value. +- **Instructions** can describe judgment, such as how you review a pull request or decide which issue needs attention. +- **Code** can handle fixed work, such as collecting files, parsing events, tracking progress, or formatting results. +- **An agent turn** can interpret new information, use tools, and decide what to do next. -## Questions that can help - -The following questions describe the main design choices: +For example, a pull request automation can use a script to collect the diff and test results. It can then ask you to review the evidence with your existing knowledge of the project. -- What starts the work: a person, a command, a schedule, or an event? -- Which parts follow fixed rules? -- Which parts need an agent to interpret the situation? -- Does the work need to continue after the current conversation ends? -- What state does it need between runs? -- Where will its tools run? -- Can it read information, draft changes, or perform external actions? -- How will the user see the result? +## Use your own agent -The answers can point to a script, an Agent SDK program, or a combination of both. +Your agent ID gives an Agent SDK program access to your persistent memory and identity. The program can use: -## What the Agent SDK provides +- Your default conversation. +- A new conversation for one isolated task. +- A saved conversation that continues across several runs. +- A conversation for each long-lived resource, such as a pull request or customer. -The Agent SDK is useful when the automation needs one or more of these features: +This lets an automation reuse what you already know. The program can send fresh evidence with each turn and keep the conversation ID when it wants to continue the same thread later. -- A persistent agent with memory. -- Separate conversations for different tasks or resources. -- Streaming reasoning, tool calls, tool results, and final responses. -- Tools that run in a managed cloud sandbox, on a connected computer, or on the local machine. -- Tool approval and permission handling. -- A program that coordinates several agent conversations. +The [Agent SDK recipes](references/sdk-recipes.md) show TypeScript examples for calling an existing agent, saving conversation IDs, and reporting results back to a main conversation. -The SDK works well for both one-off programs and repeated services. The [Agent SDK recipes](references/sdk-recipes.md) show TypeScript patterns for these forms. +## Use other agents -## State options +Other agents can help when a task benefits from separate context, another model, parallel work, or an independent opinion. An automation can use: -Different types of state fit in different places: +- **Another conversation on your agent:** the worker shares your memory and identity but has a separate thread. +- **A temporary worker agent:** the worker receives one task, uses a selected model and toolset, returns a result, and can then be deleted. +- **A persistent specialist agent:** the worker keeps its own memory and role across repeated tasks. -- **Agent memory** can store knowledge that the agent can use across conversations. -- **Conversation history** can store prior messages, decisions, and unresolved questions for one thread of work. -- **Files or a database** can store cursors, queues, timestamps, retry counts, and records of external actions. +A TypeScript program can hold the loop, branching, concurrency, and intermediate results. Worker agents can read, search, edit, or review within that program. -An automation can use all three. The choice depends on how the state is used. +The dynamic workflow examples in [letta-agent-sdk#261](https://github.com/letta-ai/letta-agent-sdk/pull/261) show several patterns: -## Conversation options +- Audit files in parallel, then ask other workers to verify each finding. +- Run a check, ask workers to fix separate failures, and run the check again. +- Ask workers on different models for plans, then ask another agent to judge and combine them. +- Search from several angles and ask other workers to verify the claims. +- Give workers separate cloud sandboxes and collect their reviewed patches. -An Agent SDK program can use: +These examples also show per-worker models, tool lists, permissions, structured output, concurrency limits, and cleanup. -- The agent's default conversation. -- A new conversation for each run. -- A conversation for each long-lived resource, such as a pull request or customer. -- One conversation for a group of related resources. -- A coordinator conversation that receives results from worker conversations. +## Agent SDK options -Separate conversations are useful when their history helps future decisions. A database can hold resource mappings when the program only needs structured lookup data. +An automation can use the following Agent SDK features: -## Execution options +- Persistent agents and conversations. +- Managed cloud sandboxes. +- Connected computers and local execution. +- Streaming reasoning, tool calls, tool results, and final responses. +- Client-side and server-side tools. +- Tool approval and permission callbacks. +- Different models for different workers. +- Structured results for script-controlled workflows. -The Agent SDK supports several places for tool execution: +## Questions that can help -- **Managed cloud sandbox:** Letta Cloud creates a contained computer for the session. -- **Connected computer:** the agent runs tools on a selected remote environment. -- **Local backend:** the agent state and tools stay on the current machine. +The following questions can help describe the automation: -The surrounding program can run from a command, a scheduled task, a server, or another application. The SDK connects that program to the agent and its execution environment. +- What part of your work would the program handle? +- What starts it: a request, command, schedule, or event? +- Does it call you, another conversation on you, or another agent? +- Which information needs to continue across runs? +- Which model and tools fit each part of the work? +- Where will the tools run? +- How will the result return to you or your user? -## Authority options +## State options -An automation can have different levels of authority: +An automation can use several types of state: -- Read information and report what it finds. -- Draft an external action for review. -- Ask for approval before an action. -- Perform actions that the user has already authorized. +- **Agent memory** for knowledge that remains useful across conversations. +- **Conversation history** for decisions and context in one thread of work. +- **Files or a database** for event cursors, queues, timestamps, retry counts, and records of external actions. -The chosen level affects credentials, approvals, error handling, and reporting. A dry-run mode can show proposed actions before the automation performs them. +## Execution and authority options -## Storage and sharing +Tools can run in a managed cloud sandbox, on a connected computer, or on the local machine. The surrounding program can run from a command, scheduled task, server, or another application. -A skill can keep the instructions and source files together. Common directories include `scripts/`, `src/`, `tests/`, `fixtures/`, and `templates/`. Runtime state and credentials can live in storage selected for the automation. +The automation can read information, draft an action, ask for approval, or perform actions that the user has authorized. Session options can give each worker its own model, tool list, permission mode, working directory, and sandbox. -An automation can be personal to one agent, attached to a project, shared across agents, or prepared for public use. The same source can move between these scopes when its assumptions and credentials are clear. +## Storage and operations -## Operations +A skill can keep the instructions and source files together. Common directories include `scripts/`, `src/`, `tests/`, `fixtures/`, and `templates/`. Runtime state and credentials can live in storage selected for the automation. -Repeated and deployed automations can also use run history, idempotency records, cost limits, ownership information, health checks, and stop commands. [Operations options](references/operations.md) explains these pieces and when they are useful. +Repeated or deployed automations can also use run history, idempotency records, cost and concurrency limits, ownership information, health checks, and stop commands. [Operations options](references/operations.md) describes these pieces. diff --git a/src/skills/builtin/building-automation/references/sdk-recipes.md b/src/skills/builtin/building-automation/references/sdk-recipes.md index 502269d24e..a3a4e9515e 100644 --- a/src/skills/builtin/building-automation/references/sdk-recipes.md +++ b/src/skills/builtin/building-automation/references/sdk-recipes.md @@ -28,9 +28,9 @@ The SDK offers the following execution options: Sandbox files last until the sandbox expires. Agent memory, conversation history, or application storage can hold state that must outlive the sandbox. A `cwd` value refers to a path inside the sandbox. It does not mount a local path. -## One-off program +## Call yourself for a one-off task -This example starts a new conversation on an existing agent, streams one turn, records the result, and exits. +This example starts a new conversation on your existing agent, streams one turn, records the result, and exits. `AUTOMATION_AGENT_ID` can contain your agent ID. ```ts // release-audit.ts β€” invoked by a person or another program. @@ -58,9 +58,49 @@ for await (const event of session.stream()) { Turn anatomy: one `send()` + one pass through `stream()`; the stream terminates after the turn's `result` event. `abort()` stops a turn without closing the session; `close()`/`await using` releases session-scoped resources (client tools, MCP connections, cwd/env). A session whose connection died cannot be reused β€” `resumeSession(conversationId)` and continue. +## Ask temporary worker agents for help + +A temporary worker can use a separate context, model, and toolset for one part of your automation. This example creates a hidden worker without its own memory filesystem, runs one task, and deletes the worker. + +```ts +const READ_TOOLS = ["Read", "Grep", "Glob", "LS"]; + +async function askWorker(task: string): Promise { + const workerId = await client.createAgent({ + model: process.env.WORKER_MODEL ?? "haiku", + hidden: true, + memfs: false, + baseTools: [], + }); + + try { + const result = await client.prompt(task, workerId, { + toolset: { base: "none", include: READ_TOOLS }, + allowedTools: READ_TOOLS, + permissionMode: "strict", + canUseTool: async () => ({ behavior: "allow" }), + cwd: process.cwd(), + }); + return result.result; + } finally { + await client.agents.delete(workerId); + } +} +``` + +Several workers can run in parallel while the script holds their results: + +```ts +const reports = await Promise.all( + files.map((file) => askWorker(`Review ${file} and return only concrete findings.`)), +); +``` + +The script can also pass each result through another worker, compare plans from different models, or collect patches from separate cloud sandboxes. [letta-agent-sdk#261](https://github.com/letta-ai/letta-agent-sdk/pull/261) contains complete examples for audits, fix loops, planning panels, research, and file migrations. + ## Dedicated automation agent -An automation can use an existing agent or create a dedicated agent. A dedicated agent keeps its memory and identity separate from other work. +An automation can use your existing agent, temporary workers, or a dedicated agent. A dedicated agent keeps its own memory and identity across repeated work. ```ts const agentId = await client.createAgent({ From 46376aabacf7c6608f83cf010278c6c6a3a5fc7c Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 11 Aug 2026 16:34:50 -0700 Subject: [PATCH 06/13] docs: cover conversation model overrides MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Letta Code (agent-c2adbf5c-8419-4211-8cd8-3740db164974) Show how an automation can use a lower-cost model in a non-default conversation while retaining the agent’s memory and identity. Clarify that a model update on the default conversation applies to the agent. Co-authored-by: Cameron πŸ‘Ύ Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- .../builtin/building-automation/SKILL.md | 3 +++ .../references/sdk-recipes.md | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/skills/builtin/building-automation/SKILL.md b/src/skills/builtin/building-automation/SKILL.md index 177746c4b2..1bce33ae84 100644 --- a/src/skills/builtin/building-automation/SKILL.md +++ b/src/skills/builtin/building-automation/SKILL.md @@ -41,6 +41,8 @@ Your agent ID gives an Agent SDK program access to your persistent memory and id This lets an automation reuse what you already know. The program can send fresh evidence with each turn and keep the conversation ID when it wants to continue the same thread later. +A non-default conversation can select a different model when the program creates or resumes its session. This lets routine automation use a lower-cost model while the agent keeps the same memory and identity. Another conversation can use a different model for work that benefits from it. + The [Agent SDK recipes](references/sdk-recipes.md) show TypeScript examples for calling an existing agent, saving conversation IDs, and reporting results back to a main conversation. ## Use other agents @@ -73,6 +75,7 @@ An automation can use the following Agent SDK features: - Streaming reasoning, tool calls, tool results, and final responses. - Client-side and server-side tools. - Tool approval and permission callbacks. +- Conversation-level model selection. - Different models for different workers. - Structured results for script-controlled workflows. diff --git a/src/skills/builtin/building-automation/references/sdk-recipes.md b/src/skills/builtin/building-automation/references/sdk-recipes.md index a3a4e9515e..84afd95bf3 100644 --- a/src/skills/builtin/building-automation/references/sdk-recipes.md +++ b/src/skills/builtin/building-automation/references/sdk-recipes.md @@ -58,6 +58,30 @@ for await (const event of session.stream()) { Turn anatomy: one `send()` + one pass through `stream()`; the stream terminates after the turn's `result` event. `abort()` stops a turn without closing the session; `close()`/`await using` releases session-scoped resources (client tools, MCP connections, cwd/env). A session whose connection died cannot be reused β€” `resumeSession(conversationId)` and continue. +## Select a model for an automation conversation + +`createSession()` and `resumeSession()` accept a `model` option. A non-default conversation can use a lower-cost model while it keeps the same agent memory and identity. + +```ts +const AUTOMATION_MODEL = process.env.AUTOMATION_MODEL!; + +await using session = client.createSession(AGENT_ID, { + model: AUTOMATION_MODEL, +}); +``` + +The program can also apply the model when it continues a saved conversation: + +```ts +await using session = client.resumeSession(CONVERSATION_ID, { + model: AUTOMATION_MODEL, +}); +``` + +This also allows different automation conversations to use different models. For example, collection and formatting conversations can use a lower-cost model, while a review conversation can use another model. + +`resumeSession(agentId, { model })` resumes the agent's default conversation, so the model update applies to the agent. `createSession(agentId, { model })` creates a non-default conversation, and `resumeSession(conversationId, { model })` updates that conversation. + ## Ask temporary worker agents for help A temporary worker can use a separate context, model, and toolset for one part of your automation. This example creates a hidden worker without its own memory filesystem, runs one task, and deletes the worker. From d741b2bbf6081781c8204f1de1e3ac665586d3cb Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 11 Aug 2026 16:46:31 -0700 Subject: [PATCH 07/13] docs: harden worker automation recipes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Letta Code (agent-c2adbf5c-8419-4211-8cd8-3740db164974) Fix the cloud worker example so local paths are not passed into managed sandboxes. Show repository seeding, current computer selectors, bounded concurrency, tool restrictions, review stages, and sandbox cleanup using the published Agent SDK surface. Co-authored-by: Cameron Co-authored-by: Sarah Wooders πŸ‘Ύ Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- .../references/sdk-recipes.md | 84 +++++++++++++++++-- 1 file changed, 75 insertions(+), 9 deletions(-) diff --git a/src/skills/builtin/building-automation/references/sdk-recipes.md b/src/skills/builtin/building-automation/references/sdk-recipes.md index 84afd95bf3..f80e97c762 100644 --- a/src/skills/builtin/building-automation/references/sdk-recipes.md +++ b/src/skills/builtin/building-automation/references/sdk-recipes.md @@ -11,7 +11,10 @@ bun init -y && bun add @letta-ai/letta-agent-sdk # pin the exact version in pac With the cloud backend, agent state lives in Letta Cloud. The SDK can create a managed cloud sandbox where the agent runs its tools. ```ts -import { LettaAgentClient } from "@letta-ai/letta-agent-sdk"; +import { + LettaAgentClient, + type LettaCodeCloudSandboxOptions, +} from "@letta-ai/letta-agent-sdk"; const client = new LettaAgentClient({ backend: "cloud", @@ -22,9 +25,9 @@ const client = new LettaAgentClient({ The SDK offers the following execution options: - `backend: "cloud"` β€” a managed cloud sandbox runs tools for the session. -- `backend: "cloud"` with `environment: { name: "work-laptop" }` β€” a connected computer runs the tools. Stable selectors include `deviceId` and environment `id`. A `connectionId` identifies one live connection. +- `backend: "cloud"` with `computer: { name: "work-laptop" }` β€” a connected computer runs the tools. Stable selectors include `deviceId` and computer `id`. A `connectionId` identifies one live connection. - `backend: "local"` β€” agent state and tools stay on the current machine. The SDK owns the App Server subprocess. -- `environment` and `sandbox` are mutually exclusive. +- `computer` and `sandbox` are mutually exclusive. Sandbox files last until the sandbox expires. Agent memory, conversation history, or application storage can hold state that must outlive the sandbox. A `cwd` value refers to a path inside the sandbox. It does not mount a local path. @@ -89,7 +92,10 @@ A temporary worker can use a separate context, model, and toolset for one part o ```ts const READ_TOOLS = ["Read", "Grep", "Glob", "LS"]; -async function askWorker(task: string): Promise { +async function askWorker( + task: string, + context: { sandbox?: LettaCodeCloudSandboxOptions; cwd?: string } = {}, +): Promise { const workerId = await client.createAgent({ model: process.env.WORKER_MODEL ?? "haiku", hidden: true, @@ -103,7 +109,8 @@ async function askWorker(task: string): Promise { allowedTools: READ_TOOLS, permissionMode: "strict", canUseTool: async () => ({ behavior: "allow" }), - cwd: process.cwd(), + sandbox: context.sandbox, + cwd: context.cwd, }); return result.result; } finally { @@ -112,15 +119,74 @@ async function askWorker(task: string): Promise { } ``` -Several workers can run in parallel while the script holds their results: +`toolset` selects the bundled client tools to load. `allowedTools` filters the session to the listed tools. `permissionMode: "strict"` routes each offered call through `canUseTool`; this callback allows the read-only list above. + +### Give each worker a cloud sandbox + +The cloud client creates a managed sandbox for each worker session. `githubRepositories` clones a repository into `/root/workspace`, and `cwd` selects the cloned repository: + +```ts +const repository = { owner: "letta-ai", repo: "letta-code" }; +const workerContext = { + sandbox: { + githubRepositories: [repository], + terminateOnClose: true, + }, + cwd: `/root/workspace/${repository.repo}`, +}; +``` + +Private repositories use the GitHub integration for the organization. `terminateOnClose` requests sandbox cleanup when the worker session closes. + +### Run workers with a concurrency limit + +The script can hold intermediate results and limit how many workers run at once: + +```ts +async function mapWithConcurrency( + items: T[], + limit: number, + run: (item: T) => Promise, +): Promise { + const results = new Array(items.length); + let next = 0; + + async function takeNext(): Promise { + while (next < items.length) { + const index = next++; + results[index] = await run(items[index]); + } + } + + await Promise.all( + Array.from({ length: Math.min(limit, items.length) }, takeNext), + ); + return results; +} + +const reports = await mapWithConcurrency(files, 4, (file) => + askWorker( + `Review ${file} and return only concrete findings.`, + workerContext, + ), +); +``` + +The result from one worker can become the input to another worker. For example, a review stage can try to refute each report: ```ts -const reports = await Promise.all( - files.map((file) => askWorker(`Review ${file} and return only concrete findings.`)), +const reviewed = await mapWithConcurrency( + reports.filter((report): report is string => Boolean(report)), + 4, + (report) => + askWorker( + `Try to refute this finding. Return the finding only if it survives review:\n\n${report}`, + workerContext, + ), ); ``` -The script can also pass each result through another worker, compare plans from different models, or collect patches from separate cloud sandboxes. [letta-agent-sdk#261](https://github.com/letta-ai/letta-agent-sdk/pull/261) contains complete examples for audits, fix loops, planning panels, research, and file migrations. +The script can also compare plans from different models or collect patches from separate cloud sandboxes. [letta-agent-sdk#261](https://github.com/letta-ai/letta-agent-sdk/pull/261) contains complete examples for audits, fix loops, planning panels, research, and file migrations. ## Dedicated automation agent From c08987c9bced2a42731d71d91e5db003c7abc52c Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 11 Aug 2026 16:55:14 -0700 Subject: [PATCH 08/13] docs: explain automation computer choices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Letta Code (agent-c2adbf5c-8419-4211-8cd8-3740db164974) Explain how an automation can run tools in managed cloud sandboxes, on connected organization computers, on the current machine, or through a remote App Server. Add a checked SDK example for discovering online computers and selecting one by stable device ID. Co-authored-by: Cameron πŸ‘Ύ Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- .../builtin/building-automation/SKILL.md | 14 +++++++++- .../references/sdk-recipes.md | 26 ++++++++++++++++--- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/skills/builtin/building-automation/SKILL.md b/src/skills/builtin/building-automation/SKILL.md index 1bce33ae84..743a32f2fb 100644 --- a/src/skills/builtin/building-automation/SKILL.md +++ b/src/skills/builtin/building-automation/SKILL.md @@ -79,6 +79,18 @@ An automation can use the following Agent SDK features: - Different models for different workers. - Structured results for script-controlled workflows. +## Choose where tools run + +A [computer](https://docs.letta.com/platform/computers) is the environment where an agent runs commands, accesses files, and uses local tools. The agent's identity, memory, and conversations are separate from the computer. Moving the agent does not copy project files, software, or credentials. The agent can use what exists on the selected computer. + +An Agent SDK automation can choose among: + +- **Managed cloud sandbox:** use `backend: "cloud"` without a `computer`. Letta creates an isolated environment for the session. Provisioning adds cold-start time, which can make short tasks feel slow. Sandboxes fit automations that need isolation, clean environments, or many concurrent workers. Letta expects their startup time and usability to improve. +- **Computer in your Letta organization:** use `backend: "cloud"` with a `computer` selector. The computer can be an online laptop, workstation, VM, or other connected machine. This option gives the agent access to a specific filesystem, credential, dependency, or long-running machine. A `deviceId` or computer `id` is stable across reconnects. A `connectionId` represents one live connection. +- **Current computer:** use `backend: "local"` to keep agent state and tool execution on the machine running the program. This is useful for direct access to local files and tools or for a fully local deployment. + +The program can discover online organization computers with `client.computers.list({ onlineOnly: true })`. A Cloud session can use a selected `computer` or a managed `sandbox`, but not both. A separately operated App Server is available through `backend: "remote"`. + ## Questions that can help The following questions can help describe the automation: @@ -101,7 +113,7 @@ An automation can use several types of state: ## Execution and authority options -Tools can run in a managed cloud sandbox, on a connected computer, or on the local machine. The surrounding program can run from a command, scheduled task, server, or another application. +The surrounding program can run from a command, scheduled task, server, or another application. Its tools run on the computer selected for each session. The automation can read information, draft an action, ask for approval, or perform actions that the user has authorized. Session options can give each worker its own model, tool list, permission mode, working directory, and sandbox. diff --git a/src/skills/builtin/building-automation/references/sdk-recipes.md b/src/skills/builtin/building-automation/references/sdk-recipes.md index f80e97c762..9866a6e0c5 100644 --- a/src/skills/builtin/building-automation/references/sdk-recipes.md +++ b/src/skills/builtin/building-automation/references/sdk-recipes.md @@ -6,9 +6,9 @@ These examples show ways to use `@letta-ai/letta-agent-sdk` from TypeScript. The bun init -y && bun add @letta-ai/letta-agent-sdk # pin the exact version in package.json ``` -## Cloud sandbox +## Choose the execution computer -With the cloud backend, agent state lives in Letta Cloud. The SDK can create a managed cloud sandbox where the agent runs its tools. +With the cloud backend, agent state lives in Letta Cloud. If no `computer` is selected, the SDK creates a managed cloud sandbox where the agent runs its tools. ```ts import { @@ -27,9 +27,29 @@ The SDK offers the following execution options: - `backend: "cloud"` β€” a managed cloud sandbox runs tools for the session. - `backend: "cloud"` with `computer: { name: "work-laptop" }` β€” a connected computer runs the tools. Stable selectors include `deviceId` and computer `id`. A `connectionId` identifies one live connection. - `backend: "local"` β€” agent state and tools stay on the current machine. The SDK owns the App Server subprocess. +- `backend: "remote"` β€” tools run on a separately operated App Server computer; the App Server backend determines where agent state lives. - `computer` and `sandbox` are mutually exclusive. -Sandbox files last until the sandbox expires. Agent memory, conversation history, or application storage can hold state that must outlive the sandbox. A `cwd` value refers to a path inside the sandbox. It does not mount a local path. +Managed sandboxes provide clean isolation and support concurrent workers. Provisioning can add cold-start latency. A connected organization computer avoids sandbox provisioning and gives the agent access to its files, tools, and credentials. + +The SDK can list online computers before it selects one: + +```ts +const { computers } = await client.computers.list({ onlineOnly: true }); +for (const computer of computers) { + console.log(computer.name, computer.deviceId, computer.status); +} + +const computer = computers[0]; +if (!computer) throw new Error("No online computers are available"); + +await using session = client.resumeSession(AGENT_ID, { + computer: { deviceId: computer.deviceId }, + cwd: "/workspace/project", +}); +``` + +Sandbox files last until the sandbox expires. Agent memory, conversation history, or application storage can hold state that must outlive the sandbox. A sandbox `cwd` refers to a path inside the sandbox; it does not mount a local path. Changing computers never copies files or credentials between them. See the [Computers documentation](https://docs.letta.com/platform/computers) and [Agent SDK deployment guide](https://docs.letta.com/agent-sdk/deployment). ## Call yourself for a one-off task From 93865aa6d1092589ceab52d5083bbd61f27e1136 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 11 Aug 2026 16:58:15 -0700 Subject: [PATCH 09/13] docs: clarify Building Automation description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Letta Code (agent-c2adbf5c-8419-4211-8cd8-3740db164974) Replace the awkward β€œrepeated automations” phrase with β€œone-off or recurring workflows.” Co-authored-by: Cameron Co-authored-by: Sarah Wooders πŸ‘Ύ Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- src/skills/builtin/building-automation/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/skills/builtin/building-automation/SKILL.md b/src/skills/builtin/building-automation/SKILL.md index 743a32f2fb..c85c812441 100644 --- a/src/skills/builtin/building-automation/SKILL.md +++ b/src/skills/builtin/building-automation/SKILL.md @@ -1,6 +1,6 @@ --- name: building-automation -description: Load this skill to understand how to use the Letta Agent SDK to automate yourself by building one-off or repeated automations. +description: Load this skill to learn how to use the Letta Agent SDK to automate yourself through one-off or recurring workflows. --- # Building Automation From a2a2078ca8af9fb4bc24111b12b55d5ba623faa5 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 11 Aug 2026 17:00:29 -0700 Subject: [PATCH 10/13] docs: frame automation across computer fleets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make the skill trigger on automation opportunities and explain how one workflow can coordinate isolated conversations across connected organization machines. πŸ‘Ύ Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- src/skills/builtin/building-automation/SKILL.md | 4 +++- .../builtin/building-automation/references/sdk-recipes.md | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/skills/builtin/building-automation/SKILL.md b/src/skills/builtin/building-automation/SKILL.md index c85c812441..30c6a4844b 100644 --- a/src/skills/builtin/building-automation/SKILL.md +++ b/src/skills/builtin/building-automation/SKILL.md @@ -1,6 +1,6 @@ --- name: building-automation -description: Load this skill to learn how to use the Letta Agent SDK to automate yourself through one-off or recurring workflows. +description: Use this skill when you notice work you can automate. It shows how to extend yourself with one-off and recurring workflows built on the Letta Agent SDK. --- # Building Automation @@ -91,6 +91,8 @@ An Agent SDK automation can choose among: The program can discover online organization computers with `client.computers.list({ onlineOnly: true })`. A Cloud session can use a selected `computer` or a managed `sandbox`, but not both. A separately operated App Server is available through `backend: "remote"`. +One program can open separate conversations on several organization computers. This can support fleets of CI machines, infrastructure nodes, platform-specific test machines, or other connected computers. + ## Questions that can help The following questions can help describe the automation: diff --git a/src/skills/builtin/building-automation/references/sdk-recipes.md b/src/skills/builtin/building-automation/references/sdk-recipes.md index 9866a6e0c5..cd389415b2 100644 --- a/src/skills/builtin/building-automation/references/sdk-recipes.md +++ b/src/skills/builtin/building-automation/references/sdk-recipes.md @@ -49,6 +49,8 @@ await using session = client.resumeSession(AGENT_ID, { }); ``` +The same API can route separate conversations to a fleet of connected computers. Examples include CI machines, infrastructure nodes, and platform-specific test machines. A larger fleet can use the bounded-concurrency pattern later in this guide. + Sandbox files last until the sandbox expires. Agent memory, conversation history, or application storage can hold state that must outlive the sandbox. A sandbox `cwd` refers to a path inside the sandbox; it does not mount a local path. Changing computers never copies files or credentials between them. See the [Computers documentation](https://docs.letta.com/platform/computers) and [Agent SDK deployment guide](https://docs.letta.com/agent-sdk/deployment). ## Call yourself for a one-off task From d612ac0fb7556772ec289fd5556b4fdd82f1b1fa Mon Sep 17 00:00:00 2001 From: Cameron Pfiffer Date: Tue, 11 Aug 2026 17:42:02 -0700 Subject: [PATCH 11/13] docs: make automation delivery retries durable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Separate persisted decisions from delivery receipts and reconcile unknown coordinator sends by stable event marker so retries cannot silently drop effects. πŸ‘Ύ Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- .../references/sdk-recipes.md | 98 ++++++++++++++----- 1 file changed, 74 insertions(+), 24 deletions(-) diff --git a/src/skills/builtin/building-automation/references/sdk-recipes.md b/src/skills/builtin/building-automation/references/sdk-recipes.md index cd389415b2..c7af86075c 100644 --- a/src/skills/builtin/building-automation/references/sdk-recipes.md +++ b/src/skills/builtin/building-automation/references/sdk-recipes.md @@ -267,48 +267,98 @@ A lock file can prevent two scheduled runs from using the same state at the same ## Event-driven program -This example accepts an event, checks a local record, sends the event to the resource conversation, and records the Agent SDK run IDs. +This example accepts an event, persists the agent's decision, and then delivers any escalation. Decision completion and outbound delivery are separate states: recording a decision must not cause a failed delivery to be skipped on retry. The dispatcher must serialize calls for each event ID with a database-backed lock or resource queue so two workers cannot deliver the same event concurrently. ```ts +db.run(`CREATE TABLE IF NOT EXISTS effects ( + event_id TEXT PRIMARY KEY, + decision TEXT NOT NULL, + run_ids TEXT NOT NULL, + decided_at INTEGER NOT NULL, + delivered_at INTEGER +)`); + // One iteration of a poll loop or one webhook delivery. async function handleEvent(evt: { id: string; resource: string; payload: string }) { - const seen = db - .query("SELECT 1 FROM effects WHERE event_id = ?") + let effect = db + .query< + { decision: string; delivered_at: number | null }, + [string] + >("SELECT decision, delivered_at FROM effects WHERE event_id = ?") .get(evt.id); - if (seen) return; // idempotent: already handled - - await using session = await sessionFor(evt.resource); - await session.send( - [ - `Event ${evt.id} on ${evt.resource}:`, - evt.payload, - "Decide: no action, or a one-line escalation with reason.", - ].join("\n"), - ); - for await (const e of session.stream()) { - if (e.type === "result") { - db.run("INSERT INTO effects (event_id, run_ids, at) VALUES (?, ?, ?)", [ - evt.id, - JSON.stringify(e.runIds), - Date.now(), - ]); - if (e.success && e.result?.startsWith("ESCALATE:")) await reportToCoordinator(e.result); + if (!effect) { + await using session = await sessionFor(evt.resource); + let assistantText = ""; + await session.send( + [ + `Event ${evt.id} on ${evt.resource}:`, + evt.payload, + "Decide: no action, or a one-line escalation with reason.", + ].join("\n"), + ); + + for await (const e of session.stream()) { + if (e.type === "assistant") assistantText += e.content; + if (e.type === "result") { + if (!e.success) throw new Error(`Decision failed for ${evt.id}`); + const decision = (e.result ?? assistantText).trim(); + if (!decision) throw new Error(`Decision was empty for ${evt.id}`); + db.run( + "INSERT INTO effects (event_id, decision, run_ids, decided_at) VALUES (?, ?, ?, ?)", + [evt.id, decision, JSON.stringify(e.runIds ?? []), Date.now()], + ); + effect = { decision, delivered_at: null }; + } } } + + if (!effect || effect.delivered_at) return; + if (effect.decision.startsWith("ESCALATE:")) { + await reportToCoordinator(evt.id, effect.decision); + } + db.run("UPDATE effects SET delivered_at = ? WHERE event_id = ?", [ + Date.now(), + evt.id, + ]); } ``` -A worker conversation can report a conclusion to a coordinator conversation: +A worker conversation can report a conclusion to a coordinator conversation. The event marker makes an unknown-delivery retry reconcilable: before sending, scan the durable conversation history for the same marker. ```ts -async function reportToCoordinator(packet: string) { +function containsMarker(message: { message_type?: string; content?: unknown }, marker: string) { + return ( + message.message_type === "user_message" && + typeof message.content === "string" && + message.content.includes(marker) + ); +} + +async function reportToCoordinator(eventId: string, packet: string) { await using main = client.resumeSession(MAIN_CONVERSATION_ID); - await main.send(`[pr-shepherd] ${packet}`); + const marker = `[pr-shepherd:event:${eventId}]`; + let before: string | undefined; + + do { + const page = await main.listMessages({ + conversationId: MAIN_CONVERSATION_ID, + order: "desc", + limit: 100, + before, + }); + if (page.messages.some((message) => containsMarker(message, marker))) return; + if (page.hasMore === false || !page.nextBefore) break; + before = page.nextBefore; + } while (true); + + await main.send(`${marker} ${packet}`); for await (const e of main.stream()) if (e.type === "result") break; } ``` +If the process stops after the coordinator accepts the message but before `delivered_at` is updated, the next attempt finds the marker and finishes the local record without sending a duplicate. For an external destination, use its native idempotency key or query it by a stable action ID before retrying. + [Operations options](operations.md) describes event envelopes, cursors, reconciliation, action records, limits, and manifests for repeated programs. ## Connection and retry behavior From d9222db7ef724d08c0750ff9ea0429f0317a344d Mon Sep 17 00:00:00 2001 From: Cameron Pfiffer Date: Tue, 11 Aug 2026 18:01:00 -0700 Subject: [PATCH 12/13] docs: prevent automation example anchoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Require evidence of repeated work and an existing-primitive check before introducing Agent SDK machinery, so worked examples do not become default project suggestions. πŸ‘Ύ Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- .../builtin/building-automation/SKILL.md | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/skills/builtin/building-automation/SKILL.md b/src/skills/builtin/building-automation/SKILL.md index 30c6a4844b..a8240b76ee 100644 --- a/src/skills/builtin/building-automation/SKILL.md +++ b/src/skills/builtin/building-automation/SKILL.md @@ -7,6 +7,19 @@ description: Use this skill when you notice work you can automate. It shows how You can use the Letta Agent SDK to automate parts of your own work. A program can call you in another conversation, resume work later, run your tools on a selected computer, and return the result to your user. The same program can also ask other agents to help with separate parts of the work. +## Choose the work before the machinery + +Do not begin with the examples in this skill. They illustrate mechanics, not what you should build. Choose from work that has actually repeated in your own recent history or that your user has explicitly asked to automate. + +Before designing anything: + +1. Identify the concrete repeated work and the evidence that it repeats. +2. Check whether an existing primitive already owns it. Use a background command for one completion, `Monitor` for a stream of matching events, a schedule plus an existing skill for a recurring turn, and ordinary code for deterministic transforms. +3. Name what requires an agent: judgment, persistent context, delegated work, model choice, or execution on another computer. If none of these is necessary, do not add the Agent SDK. +4. Consider several candidates from the agent's actual work before selecting one. Do not select a worked example from this document merely because it is available in context. + +Choose the smallest form that closes the loop. A native tool or short script is a successful automation decision when it makes a larger agent program unnecessary. + ## Ways to automate yourself An automation can take many forms: @@ -24,11 +37,11 @@ These forms can use the same agent and the same code. A one-off helper can later The parts of an automation can be split in different ways: -- **Instructions** can describe judgment, such as how you review a pull request or decide which issue needs attention. -- **Code** can handle fixed work, such as collecting files, parsing events, tracking progress, or formatting results. +- **Instructions** can describe judgment, such as deciding whether documentation drift changes the meaning of a guide or whether an anomaly deserves attention. +- **Code** can handle fixed work, such as collecting type signatures, parsing events, tracking progress, or formatting results. - **An agent turn** can interpret new information, use tools, and decide what to do next. -For example, a pull request automation can use a script to collect the diff and test results. It can then ask you to review the evidence with your existing knowledge of the project. +For example, an API-drift check can use a script to collect exported type changes. It can then ask you whether those changes invalidate any maintained recipes, using a saved conversation to preserve earlier release decisions. ## Use your own agent @@ -37,7 +50,7 @@ Your agent ID gives an Agent SDK program access to your persistent memory and id - Your default conversation. - A new conversation for one isolated task. - A saved conversation that continues across several runs. -- A conversation for each long-lived resource, such as a pull request or customer. +- A conversation for each long-lived resource, such as a customer, dataset, deployment, or maintained guide. This lets an automation reuse what you already know. The program can send fresh evidence with each turn and keep the conversation ID when it wants to continue the same thread later. From 116a4b7bd2bb573c52f6d12dfad6e35e1e9de509 Mon Sep 17 00:00:00 2001 From: Cameron Pfiffer Date: Wed, 12 Aug 2026 11:05:08 -0700 Subject: [PATCH 13/13] docs: disambiguate the automation skill name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename the bundled skill around the agent-facing task so it no longer reads as physical building controls. πŸ‘Ύ Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- .../{building-automation => automating-your-work}/SKILL.md | 6 +++--- .../references/operations.md | 0 .../references/sdk-recipes.md | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename src/skills/builtin/{building-automation => automating-your-work}/SKILL.md (97%) rename src/skills/builtin/{building-automation => automating-your-work}/references/operations.md (100%) rename src/skills/builtin/{building-automation => automating-your-work}/references/sdk-recipes.md (100%) diff --git a/src/skills/builtin/building-automation/SKILL.md b/src/skills/builtin/automating-your-work/SKILL.md similarity index 97% rename from src/skills/builtin/building-automation/SKILL.md rename to src/skills/builtin/automating-your-work/SKILL.md index a8240b76ee..3daeb9064c 100644 --- a/src/skills/builtin/building-automation/SKILL.md +++ b/src/skills/builtin/automating-your-work/SKILL.md @@ -1,9 +1,9 @@ --- -name: building-automation -description: Use this skill when you notice work you can automate. It shows how to extend yourself with one-off and recurring workflows built on the Letta Agent SDK. +name: automating-your-work +description: Guides you in automating repeated work with the smallest appropriate form, from one-off scripts and reusable skills to scheduled or event-driven Agent SDK programs. --- -# Building Automation +# Automating Your Work You can use the Letta Agent SDK to automate parts of your own work. A program can call you in another conversation, resume work later, run your tools on a selected computer, and return the result to your user. The same program can also ask other agents to help with separate parts of the work. diff --git a/src/skills/builtin/building-automation/references/operations.md b/src/skills/builtin/automating-your-work/references/operations.md similarity index 100% rename from src/skills/builtin/building-automation/references/operations.md rename to src/skills/builtin/automating-your-work/references/operations.md diff --git a/src/skills/builtin/building-automation/references/sdk-recipes.md b/src/skills/builtin/automating-your-work/references/sdk-recipes.md similarity index 100% rename from src/skills/builtin/building-automation/references/sdk-recipes.md rename to src/skills/builtin/automating-your-work/references/sdk-recipes.md