From e16897d3af4e1d2f9866b0d69908754be9ed146e Mon Sep 17 00:00:00 2001 From: Cameron Pfiffer Date: Wed, 12 Aug 2026 11:51:24 +0000 Subject: [PATCH] Make landing interpretation prove its custody. Fail closed when the retained specialist or selected computer cannot be exhaustively shown to preserve the observer trust boundary. 👾 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- wiki/log/2026-08-12-landing-observer-trust-boundary.md | 27 ++++++++++++++++++++------- wiki/process/repository-skills.md | 20 ++++++++++++++------ wiki/process/workflows.md | 26 +++++++++++++++++--------- .agents/skills/observing-misaligned-landings/SKILL.md | 41 +++++++++++++++++++++++++---------------- wiki/log/decisions/2026-08-12.md | 19 ++++++++++++++----- .agents/skills/observing-misaligned-landings/scripts/interpret-anomalies.mjs | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------ .agents/skills/observing-misaligned-landings/tests/interpret-anomalies.test.mjs | 332 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------- 7 file(s) changed, 504 insertion(s)(+), 108 deletion(s)(-) diff --git a/wiki/log/2026-08-12-landing-observer-trust-boundary.md b/wiki/log/2026-08-12-landing-observer-trust-boundary.md --- a/wiki/log/2026-08-12-landing-observer-trust-boundary.md +++ b/wiki/log/2026-08-12-landing-observer-trust-boundary.md @@ -39,6 +39,16 @@ API name lookup still reuses the retained identity. Empty memory, disabled MemFS, no server or client tools, no skills, strict permission mode, stateless fresh conversations, and minimized evidence remain binding. +- Exact-name lookup now exhausts every API page and fails closed when pagination is + incomplete or does not advance. Reused specialists and the exact post-create re-read + must prove the expected agent type, name, hidden state, model, system prompt, sole + MemFS-disabled origin tag, empty blocks and sources, and absent tools. A retained + name can no longer conceal authority drift. +- The interpreter no longer relies on ambient SDK computer selection. An explicit + `LETTA_COMPUTER_NAME` goes through named ambiguity/offline checks; otherwise one + complete online listing must contain exactly one computer, and `prompt()` receives + its stable device id. Zero, multiple, malformed, offline, or incompletely enumerated + candidates fail closed before a conversation starts. - A hermetic local HTTP/WebSocket fixture now drives the real pinned `@letta-ai/letta-agent-sdk`. It observes conversation creation, explicit computer resolution, separate control and stream sockets, strict stateless runtime startup, @@ -55,10 +65,12 @@ Focused Python fixtures cover literal and malformed status/diff paths, rename/copy ownership, component-aware ancestor overlap, non-UTF-8 path bytes, pre-landing primary -dirt, completed-landing descendants, and complete Telegram receipt states/message ids. Node fixtures cover -hidden provisioning plus the installed SDK's actual REST/WebSocket boundary and -terminal result. The generated corpus indexes and the exact project landing gate -verify the reconciled candidate. +dirt, completed-landing descendants, and complete Telegram receipt states/message +ids. Node fixtures cover hidden provisioning, exhaustive paginated lookup, every +retained-state authority field, post-create re-read, computer ambiguity and exact +device selection, plus the installed SDK's actual REST/WebSocket boundary and terminal +result. The generated corpus indexes and the exact project landing gate verify the +reconciled candidate. ## Not done @@ -67,6 +79,7 @@ schedule, or clean anything it reads. **Defense:** unknown is not absent, and a label is not a receipt. Exact path bytes, -distinct channel states, hidden zero-authority provisioning, and the real pinned -transport each prove only what they can actually reach. Deterministic landing law -remains outside the specialist and outside this observer. +distinct channel states, exhaustive zero-authority identity validation, explicit +computer custody, and the real pinned transport each prove only what they can actually +reach. Deterministic landing law remains outside the specialist and outside this +observer. diff --git a/wiki/process/repository-skills.md b/wiki/process/repository-skills.md --- a/wiki/process/repository-skills.md +++ b/wiki/process/repository-skills.md @@ -40,7 +40,7 @@ | `design-companion` | `.agents/skills/design-companion/SKILL.md` | Explore and explain unsettled game-design choices in conversation without mutating the repository; hand adopted choices to `design-session`. | | `design-session` | `.agents/skills/design-session/SKILL.md`
`.claude/skills/design-session/SKILL.md` | Capture affirmed design decisions into their owning law/spec pages, decision history, and session trace. | | `playtesting-misaligned` | `.agents/skills/playtesting-misaligned/SKILL.md` | Run evidence-bearing naive and informed playtests against the current player surface and corpus. | -| `observing-misaligned-landings` | `.agents/skills/observing-misaligned-landings/SKILL.md` | Read one exact candidate across isolated remote snapshots, exact dirty-path ownership, public/site and project-operation evidence, classify it without mutation, and optionally ask one retained hidden exact-name, zero-tool Letta specialist in a fresh conversation to interpret only non-benign anomalies. | +| `observing-misaligned-landings` | `.agents/skills/observing-misaligned-landings/SKILL.md` | Read one exact candidate across isolated remote snapshots, exact dirty-path ownership, public/site and project-operation evidence, classify it without mutation, and optionally ask one exhaustively revalidated, hidden, zero-tool Letta specialist on one explicitly selected computer to interpret only non-benign anomalies in a fresh conversation. | | `session-wrap` | `.agents/skills/session-wrap/SKILL.md`
`.claude/skills/session-wrap/SKILL.md` | Finish or preserve owned work, read exact current project state from live authorities, and report one bounded next step without maintaining a parallel checked-in handoff ledger. | | `tick` | `.agents/skills/tick/SKILL.md`
`.claude/skills/tick/SKILL.md` | Invoke the project's bounded stewardship heartbeat: audit one corpus slice, act on one finding, and leave a trace. | @@ -78,10 +78,16 @@ 7. Optional agent interpretation cannot override deterministic state. It reuses exactly one retained `Misaligned Landing Observer` with empty memory, MemFS and tools disabled, hidden from ordinary agent surfaces, opens a fresh conversation - over minimized evidence for each observation, provisions only when absent, and - fails closed on duplicate exact names. The pinned SDK transport is exercised - end-to-end against a hermetic fake HTTP/WebSocket service after a frozen install; - missing dependencies cannot skip that gate. + over minimized evidence for each observation, and provisions only when absent. + Exact-name lookup exhausts every page; reuse and post-create re-read validate the + exact agent type, identity, hidden/model/system/tag state, empty blocks and sources, + and absent tools. Duplicate names, specialist drift, incomplete pagination, or a + non-advancing cursor fail closed. Computer selection is explicit: a configured + `LETTA_COMPUTER_NAME` uses the SDK's ambiguity- and offline-checking named resolver; + otherwise a complete online listing must contain exactly one computer and the + prompt binds its stable device id. The pinned SDK transport is exercised end-to-end + against a hermetic fake HTTP/WebSocket service after a frozen install; missing + dependencies cannot skip that gate. ## Defense @@ -93,4 +99,6 @@ comparison, complete channel status/message-id semantics, benign historical-publication handling, hidden provisioning, and real pinned-transport coverage close the remaining places where an observer could claim more certainty or authority than its instruments -earned. +earned. Revalidating retained authority and selecting an explicit computer prevent +ambient organization state from silently changing which identity or machine performs +the supposedly bounded read. diff --git a/wiki/process/workflows.md b/wiki/process/workflows.md --- a/wiki/process/workflows.md +++ b/wiki/process/workflows.md @@ -292,10 +292,16 @@ rather than retroactively making that older landing inconsistent. Optional Letta interpretation is subordinate and cannot change the state. It reuses one retained exact-name, hidden, empty-memory, zero-tool specialist but opens a fresh conversation -for each observation; duplicate exact names fail closed. Before a landing, NUL-safe -status and diff evidence compares both literal sides of renames and copies: any dirty -primary checkout blocks the existing path until its owner resolves it, and overlap -with the candidate is named exactly rather than guessed unrelated. The candidate delta +for each observation. Exact-name lookup exhausts all pages; each reuse and post-create +read must still prove the exact agent type, name, hidden/model/system/tag state, empty +blocks and sources, and absent tools. Duplicate names, retained-state drift, incomplete +pagination, or a non-advancing cursor fail closed. The prompt names one computer +explicitly: `LETTA_COMPUTER_NAME` uses SDK name resolution, otherwise a complete online +listing must contain exactly one computer and its stable device id is used. Ambiguous, +offline, malformed, or incomplete computer evidence fails closed. Before a landing, +NUL-safe status and diff evidence compares both literal sides of renames and copies: +any dirty primary checkout blocks the existing path until its owner resolves it, and +overlap with the candidate is named exactly rather than guessed unrelated. The candidate delta is explicitly scoped from merge base to candidate while the candidate remains unlanded; once current main contains it, the original delta is unknown rather than mislabeled empty. Missing or different scope cannot prove disjoint ownership. Non-UTF-8 path bytes survive through surrogate escape, every @@ -308,9 +314,10 @@ The project gate installs the skill lockfile with its pinned pnpm `10.20.0` before running Node fixtures. Those fixtures drive the real pinned Agent SDK through a hermetic fake HTTP/WebSocket transport and prove conversation creation, explicit -computer resolution, dual sockets, strict stateless startup without skills, -post-start synchronization, empty client-tool authority, interactive-tool exclusion, -and a terminal result. +computer discovery and device-id resolution, exhaustive specialist lookup and state +validation, dual sockets, strict stateless startup without skills, post-start +synchronization, empty client-tool authority, interactive-tool exclusion, and a +terminal result. Do not poll the edge merely because its one public read still serves an older source. Starlight is the documentation renderer. Its sync copies root `DESIGN.md` to @@ -436,5 +443,6 @@ scope, or public evidence from becoming a false green. Completed historical landings and their benign current-publication lag remain local deterministic facts rather than unnecessary agent prompts. Keeping agent interpretation hidden, -zero-authority, transport-tested, optional, and unable to override the four-state -classifier preserves the executable workflow as the only landing authority. +zero-authority, exhaustively revalidated, bound to one explicit computer, +transport-tested, optional, and unable to override the four-state classifier preserves +the executable workflow as the only landing authority. diff --git a/.agents/skills/observing-misaligned-landings/SKILL.md b/.agents/skills/observing-misaligned-landings/SKILL.md --- a/.agents/skills/observing-misaligned-landings/SKILL.md +++ b/.agents/skills/observing-misaligned-landings/SKILL.md @@ -100,21 +100,29 @@ node "$SKILL/scripts/interpret-anomalies.mjs" /tmp/misaligned-landing.json ``` -The interpreter uses `@letta-ai/letta-agent-sdk` to find exactly one retained -`letta/auto` specialist named `Misaligned Landing Observer`, provisioning it only when -absent. More than one exact-name match fails closed. The specialist is hidden from -ordinary agent surfaces but remains available to exact-name API lookup, with empty -memory, MemFS disabled, no tools or skills, and a strict zero-action prompt. +The interpreter uses `@letta-ai/letta-agent-sdk` to exhaust the exact-name API lookup +and find exactly one retained `letta/auto` specialist named +`Misaligned Landing Observer`, provisioning it only when absent. More than one +exact-name match, incomplete pagination, or pagination that does not advance fails +closed. Every reuse validates the returned agent's exact name and type, hidden state, +pinned model, zero-action system prompt, sole MemFS-disabled origin tag, empty memory, +and absence of attached sources or tools. A newly created specialist is re-read and +must satisfy the same complete state before its identity is trusted. The specialist +remains hidden from ordinary agent surfaces but available to exact-name API lookup. + Each observation uses SDK `prompt()` with `stateless: true`, which opens a fresh conversation for that retained agent rather than resuming an earlier receipt and -loads no persisted MemFS state. Only minimized evidence -crosses the repository boundary: local paths, remote URLs, task paths, and raw read -errors are removed. Interpretation may explain likely convergence, stale evidence, or -project-operation inconsistency, but cannot alter the receipt or its deterministic -state. The pinned transport contract resolves one explicit organization computer, -opens separate control and stream sockets, starts a strict stateless runtime with no -skills, synchronizes it, and sends the turn with empty client-tool authority and -interactive tools excluded. +loads no persisted MemFS state. Only minimized evidence crosses the repository +boundary: local paths, remote URLs, task paths, and raw read errors are removed. +Interpretation may explain likely convergence, stale evidence, or project-operation +inconsistency, but cannot alter the receipt or its deterministic state. Computer +selection is explicit: `LETTA_COMPUTER_NAME` selects one named computer through the +SDK's ambiguity- and offline-checking resolver; without it, discovery must completely +enumerate exactly one online organization computer and binds the prompt to that +computer's stable device id. Zero, multiple, offline, malformed, or incompletely +enumerated candidates fail closed. The pinned transport then opens separate control +and stream sockets, starts a strict stateless runtime with no skills, synchronizes it, +and sends the turn with empty client-tool authority and interactive tools excluded. If no non-benign anomaly remains, the interpreter returns `status: not-needed` without loading the SDK or contacting Letta. If credentials, SDK installation, exact @@ -131,8 +139,9 @@ The fixture proves byte-stable clean receipts, exact anomaly codes and dirty-path ownership, complete Telegram status/message-id semantics, a clean descendant after -candidate cleanup, optional-SDK fail-closed behavior, the real pinned SDK's -REST/WebSocket request shape through a hermetic fake transport, and that observing -leaves the target repository's Git bytes unchanged. The project gate performs the frozen install before +candidate cleanup, optional-SDK fail-closed behavior, exhaustive and state-validating +specialist lookup, explicit computer selection, the real pinned SDK's REST/WebSocket +request shape through a hermetic fake transport, and that observing leaves the target +repository's Git bytes unchanged. The project gate performs the frozen install before the Node suite; missing dependencies cannot silently turn transport coverage into a skip. diff --git a/wiki/log/decisions/2026-08-12.md b/wiki/log/decisions/2026-08-12.md --- a/wiki/log/decisions/2026-08-12.md +++ b/wiki/log/decisions/2026-08-12.md @@ -41,11 +41,16 @@ message id; only `delivered` carries one positive decimal message id. - The retained specialist is hidden from ordinary agent surfaces. The project gate performs a frozen install with pnpm `10.20.0`, then drives the real pinned SDK - through a hermetic HTTP/WebSocket service to prove conversation creation, explicit - computer resolution, dual sockets, strict stateless no-skill startup, synchronization, - empty client-tool authority, interactive-tool exclusion, and terminal completion. - Clean descendants after owned cleanup preserve historical landing proof, while their - benign current-publication lag does not invoke the specialist by itself. + through a hermetic HTTP/WebSocket service to prove conversation creation, exhaustive + specialist lookup, exact retained-state and post-create validation, explicit + computer discovery and device-id resolution, dual sockets, strict stateless no-skill + startup, synchronization, empty client-tool authority, interactive-tool exclusion, + and terminal completion. Exact-name duplicates, incomplete or stalled pagination, + retained-state drift, and ambiguous computer selection fail closed. A configured + `LETTA_COMPUTER_NAME` selects through the SDK's named resolver; otherwise exactly one + completely enumerated online computer is required. Clean descendants after owned + cleanup preserve historical landing proof, while their benign current-publication + lag does not invoke the specialist by itself. ### REJECTED @@ -56,6 +61,10 @@ fresh and stateless. - **Reuse the specialist's default conversation.** Prior evidence must not leak into the next exact read. +- **Trust a retained specialist because its name still matches, or trust whichever + computer the SDK reaches implicitly.** Retained authority can drift after creation, + and ambient organization machines are not an identity proof. Revalidate the whole + bounded state and bind one explicit computer every time. - **Fetch into, repair, deploy, notify from, schedule through, or clean the observed repository.** Observation has no mutation doorway. - **Poll until the public edge agrees.** Remote publication and edge convergence are diff --git a/.agents/skills/observing-misaligned-landings/scripts/interpret-anomalies.mjs b/.agents/skills/observing-misaligned-landings/scripts/interpret-anomalies.mjs --- a/.agents/skills/observing-misaligned-landings/scripts/interpret-anomalies.mjs +++ b/.agents/skills/observing-misaligned-landings/scripts/interpret-anomalies.mjs @@ -9,16 +9,22 @@ "network.comind.misaligned.landing-interpretation/v1"; export const MODEL = "letta/auto"; export const SPECIALIST_NAME = "Misaligned Landing Observer"; +export const SPECIALIST_SYSTEM_PROMPT = [ + "You are the retained Misaligned Landing Observer interpreting exactly one minimized deterministic landing receipt in a fresh stateless conversation.", + "You have no tools, skills, repository access, or authority to mutate anything.", + "The supplied classifier state is authoritative and immutable; explain anomalies without changing that state, acting, or promising action.", +].join(" "); const MAX_RECEIPT_BYTES = 256 * 1024; const BENIGN_ANOMALY_CODES = new Set([ "current_main_unpublished", "public_edge_converging", ]); -const INTERPRETER_SYSTEM_PROMPT = [ - "You are the retained Misaligned Landing Observer interpreting exactly one minimized deterministic landing receipt in a fresh stateless conversation.", - "You have no tools, skills, repository access, or authority to mutate anything.", - "The supplied classifier state is authoritative and immutable; explain anomalies without changing that state, acting, or promising action.", -].join(" "); +const SPECIALIST_LIST_QUERY = { + include: ["agent.blocks", "agent.sources", "agent.tags", "agent.tools"], + limit: 100, + name: SPECIALIST_NAME, + show_hidden_agents: true, +}; function validateFacts(value, field) { if (!Array.isArray(value)) { @@ -208,31 +214,83 @@ } function agentId(agent) { - return agent && typeof agent.id === "string" && agent.id.length > 0 + return agent && + typeof agent.id === "string" && + agent.id.length > 0 && + agent.id === agent.id.trim() ? agent.id : null; } -export async function findOrCreateSpecialist(client, lookup = client.agents) { - const page = await lookup.list({ - limit: 100, - name: SPECIALIST_NAME, - show_hidden_agents: true, - }); - const listed = Array.isArray(page) ? page : page?.items; - if (!Array.isArray(listed)) { +function pageItems(page) { + const items = page?.items; + if ( + Array.isArray(page) || + !Array.isArray(items) || + typeof page?.hasNextPage !== "function" || + typeof page?.getNextPage !== "function" + ) { throw new Error("Letta agent lookup returned an unsupported result"); } - const exact = listed.filter((agent) => agent?.name === SPECIALIST_NAME); - if (exact.length > 1) { - throw new Error(`more than one Letta agent is named ${SPECIALIST_NAME}`); + return items; +} + +export function validateSpecialistState(agent) { + const id = agentId(agent); + const tags = Array.isArray(agent?.tags) ? [...agent.tags].sort() : null; + const failures = []; + if (!id) failures.push("usable agent id"); + if (agent?.name !== SPECIALIST_NAME) failures.push("exact name"); + if (agent?.agent_type !== "letta_v1_agent") failures.push("Letta Code agent type"); + if (agent?.hidden !== true) failures.push("hidden state"); + if (agent?.model !== MODEL) failures.push("pinned model"); + if (agent?.system !== SPECIALIST_SYSTEM_PROMPT) failures.push("zero-authority system prompt"); + if (!Array.isArray(agent?.blocks) || agent.blocks.length !== 0) { + failures.push("empty memory blocks"); } - if (exact.length === 1) { - const existingId = agentId(exact[0]); - if (!existingId) { - throw new Error("the retained landing observer has no usable agent id"); + if (!Array.isArray(agent?.sources) || agent.sources.length !== 0) { + failures.push("no attached sources"); + } + if (!Array.isArray(agent?.tools) || agent.tools.length !== 0) { + failures.push("no attached tools"); + } + if (!tags || tags.length !== 1 || tags[0] !== "origin:letta-code") { + failures.push("MemFS-disabled origin tags"); + } + if (failures.length > 0) { + throw new Error( + `the retained landing observer does not satisfy: ${failures.join(", ")}`, + ); + } + return agent; +} + +async function exactSpecialists(lookup) { + let page = await lookup.list(SPECIALIST_LIST_QUERY); + const exact = []; + const pageCursors = new Set(); + while (true) { + const items = pageItems(page); + exact.push(...items.filter((agent) => agent?.name === SPECIALIST_NAME)); + if (exact.length > 1) { + throw new Error(`more than one Letta agent is named ${SPECIALIST_NAME}`); } - return { agentId: existingId, created: false }; + if (!page.hasNextPage()) break; + const cursor = agentId(items.at(-1)); + if (!cursor || pageCursors.has(cursor)) { + throw new Error("Letta agent lookup pagination did not advance"); + } + pageCursors.add(cursor); + page = await page.getNextPage(); + } + return exact; +} + +export async function findOrCreateSpecialist(client, lookup = client.agents) { + const exact = await exactSpecialists(lookup); + if (exact.length === 1) { + const existing = validateSpecialistState(exact[0]); + return { agentId: agentId(existing), created: false }; } const createdId = await client.createAgent({ baseTools: [], @@ -243,12 +301,51 @@ memory: [], model: MODEL, name: SPECIALIST_NAME, - systemPrompt: INTERPRETER_SYSTEM_PROMPT, + systemPrompt: SPECIALIST_SYSTEM_PROMPT, }); - if (typeof createdId !== "string" || createdId.length === 0) { + if ( + typeof createdId !== "string" || + createdId.length === 0 || + createdId !== createdId.trim() + ) { throw new Error("creating the retained landing observer returned no usable agent id"); } + const afterCreate = await exactSpecialists(lookup); + if (afterCreate.length !== 1 || agentId(afterCreate[0]) !== createdId) { + throw new Error("the retained landing observer could not be uniquely re-read after creation"); + } + validateSpecialistState(afterCreate[0]); return { agentId: createdId, created: true }; +} + +export async function selectComputer(client, env = process.env) { + const configuredName = env.LETTA_COMPUTER_NAME?.trim(); + if (configuredName) { + return { name: configuredName }; + } + const result = await client.computers.list({ limit: 100, onlineOnly: true }); + if ( + !result || + !Array.isArray(result.computers) || + result.hasNextPage !== false || + result.computers.some((computer) => computer?.status !== "online") + ) { + throw new Error("online Letta computer discovery returned incomplete evidence"); + } + if (result.computers.length !== 1) { + throw new Error( + `expected exactly one online Letta computer, found ${result.computers.length}; set LETTA_COMPUTER_NAME to select one explicitly`, + ); + } + const deviceId = result.computers[0]?.deviceId; + if ( + typeof deviceId !== "string" || + deviceId.length === 0 || + deviceId !== deviceId.trim() + ) { + throw new Error("the selected online Letta computer has no stable device id"); + } + return { deviceId }; } export async function interpret( @@ -287,9 +384,11 @@ ...(apiKey ? { apiKey } : {}), ...(env.LETTA_BASE_URL ? { baseURL: env.LETTA_BASE_URL } : {}), }); + const computer = await selectComputer(client, env); const specialist = await findOrCreateSpecialist(client, lookup.agents); const result = await client.prompt(buildPrompt(validated), specialist.agentId, { allowedTools: [], + computer, permissionMode: "strict", skillSources: [], stateless: true, diff --git a/.agents/skills/observing-misaligned-landings/tests/interpret-anomalies.test.mjs b/.agents/skills/observing-misaligned-landings/tests/interpret-anomalies.test.mjs --- a/.agents/skills/observing-misaligned-landings/tests/interpret-anomalies.test.mjs +++ b/.agents/skills/observing-misaligned-landings/tests/interpret-anomalies.test.mjs @@ -6,13 +6,45 @@ INTERPRETATION_SCHEMA, MODEL, SPECIALIST_NAME, + SPECIALIST_SYSTEM_PROMPT, buildPrompt, findOrCreateSpecialist, interpretationReceipt, interpret, needsInterpretation, + selectComputer, + validateSpecialistState, validateReceipt, } from "../scripts/interpret-anomalies.mjs"; + +function specialistState(overrides = {}) { + return { + agent_type: "letta_v1_agent", + blocks: [], + hidden: true, + id: "agent-observer", + model: MODEL, + name: SPECIALIST_NAME, + sources: [], + system: SPECIALIST_SYSTEM_PROMPT, + tags: ["origin:letta-code"], + tools: [], + ...overrides, + }; +} + +function agentPage(items, next = null) { + return { + items, + hasNextPage() { + return next !== null; + }, + async getNextPage() { + assert.ok(next); + return next; + }, + }; +} function receipt(overrides = {}) { return { @@ -131,10 +163,10 @@ agents: { async list(query) { calls.push(["list", query]); - return [ + return agentPage([ { id: "agent-near", name: `${SPECIALIST_NAME} copy` }, - { id: "agent-exact", name: SPECIALIST_NAME }, - ]; + specialistState({ id: "agent-exact" }), + ]); }, }, async createAgent() { @@ -149,18 +181,29 @@ assert.deepEqual(calls, [ [ "list", - { limit: 100, name: SPECIALIST_NAME, show_hidden_agents: true }, + { + include: ["agent.blocks", "agent.sources", "agent.tags", "agent.tools"], + limit: 100, + name: SPECIALIST_NAME, + show_hidden_agents: true, + }, ], ]); }); test("specialist creation is empty-memory, zero-tool, and persistent", async () => { const calls = []; + let lookupCount = 0; const client = { agents: { async list(query) { calls.push(["list", query]); - return []; + lookupCount += 1; + return agentPage( + lookupCount === 1 + ? [] + : [specialistState({ id: "agent-created" })], + ); }, }, async createAgent(options) { @@ -190,6 +233,167 @@ "systemPrompt", ]); assert.equal(calls.some(([kind]) => kind === "delete"), false); + assert.equal(calls.filter(([kind]) => kind === "list").length, 2); +}); + +test("specialist reuse fails closed when retained state gains authority", () => { + for (const [field, value] of [ + ["id", ""], + ["id", " "], + ["name", "Another observer"], + ["agent_type", "memgpt_agent"], + ["hidden", false], + ["model", "letta/another-model"], + ["blocks", [{ id: "block-one" }]], + ["sources", [{ id: "folder-one" }]], + ["tools", [{ id: "tool-one" }]], + ["tags", ["git-memory-enabled", "origin:letta-code"]], + ["system", "A different prompt"], + ]) { + assert.throws( + () => validateSpecialistState(specialistState({ [field]: value })), + /retained landing observer does not satisfy/, + field, + ); + } +}); + +test("specialist lookup enumerates every filtered page before reuse", async () => { + const second = agentPage([specialistState({ id: "agent-second" })]); + const first = agentPage( + [{ id: "agent-first", name: `${SPECIALIST_NAME} copy` }], + second, + ); + const result = await findOrCreateSpecialist({ + agents: { async list() { return first; } }, + }); + assert.deepEqual(result, { agentId: "agent-second", created: false }); + const duplicateSecond = agentPage([ + specialistState({ id: "agent-second" }), + ]); + const duplicateFirst = agentPage( + [specialistState({ id: "agent-first" })], + duplicateSecond, + ); + await assert.rejects( + findOrCreateSpecialist({ + agents: { async list() { return duplicateFirst; } }, + }), + /more than one Letta agent is named/, + ); + await assert.rejects( + findOrCreateSpecialist({ agents: { async list() { return []; } } }), + /unsupported result/, + ); + await assert.rejects( + findOrCreateSpecialist({ + agents: { + async list() { + return { + items: [{ id: "cursor-one", name: "near match one" }], + hasNextPage() { + return true; + }, + async getNextPage() { + return { + items: [{ id: "cursor-one", name: "near match two" }], + hasNextPage() { + return true; + }, + async getNextPage() { + throw new Error("must not request a stalled third page"); + }, + }; + }, + }; + }, + }, + }), + /pagination did not advance/, + ); + await assert.rejects( + findOrCreateSpecialist({ + agents: { + async list() { + return { + items: [], + hasNextPage() { + return true; + }, + async getNextPage() { + throw new Error("must not request an unprovable next page"); + }, + }; + }, + }, + }), + /pagination did not advance/, + ); +}); + +test("computer selection is explicit and fails closed when ambiguous", async () => { + const calls = []; + const client = { + computers: { + async list(query) { + calls.push(query); + return { + computers: [ + { + deviceId: "device-observer", + name: "observer-computer", + status: "online", + }, + ], + hasNextPage: false, + }; + }, + }, + }; + assert.deepEqual(await selectComputer(client, {}), { + deviceId: "device-observer", + }); + assert.deepEqual(calls, [{ limit: 100, onlineOnly: true }]); + assert.deepEqual( + await selectComputer(client, { LETTA_COMPUTER_NAME: "chosen-computer" }), + { name: "chosen-computer" }, + ); + assert.equal(calls.length, 1); + await assert.rejects( + selectComputer( + { + computers: { + async list() { + return { + computers: [ + { deviceId: "one", status: "online" }, + { deviceId: "two", status: "online" }, + ], + hasNextPage: false, + }; + }, + }, + }, + {}, + ), + /expected exactly one online Letta computer, found 2/, + ); + await assert.rejects( + selectComputer( + { + computers: { + async list() { + return { + computers: [{ deviceId: " ", status: "online" }], + hasNextPage: false, + }; + }, + }, + }, + {}, + ), + /no stable device id/, + ); }); test("installed SDK finds hidden specialists and translates zero-tool creation", async () => { @@ -199,6 +403,7 @@ ]); const observedRequests = []; + let agentCreated = false; const server = http.createServer((request, response) => { const chunks = []; request.on("data", (chunk) => chunks.push(chunk)); @@ -212,9 +417,17 @@ }); response.writeHead(200, { "content-type": "application/json" }); if (request.method === "GET") { - response.end("[]"); + const url = new URL(request.url, "http://127.0.0.1"); + response.end( + JSON.stringify( + agentCreated && !url.searchParams.has("after") + ? [specialistState({ id: "agent-sdk-contract" })] + : [], + ), + ); return; } + agentCreated = true; response.end(JSON.stringify({ id: "agent-sdk-contract", name: SPECIALIST_NAME })); }); }); @@ -238,14 +451,21 @@ await new Promise((resolve) => server.close(resolve)); } - assert.equal(observedRequests.length, 2); - const [lookupRequest, createRequest] = observedRequests; + assert.equal(observedRequests.length, 4); + const [lookupRequest, createRequest, confirmRequest, paginationRequest] = + observedRequests; assert.equal(lookupRequest.method, "GET"); const lookupUrl = new URL(lookupRequest.url, "http://127.0.0.1"); assert.equal(lookupUrl.pathname, "/v1/agents/"); assert.equal(lookupUrl.searchParams.get("limit"), "100"); assert.equal(lookupUrl.searchParams.get("name"), SPECIALIST_NAME); assert.equal(lookupUrl.searchParams.get("show_hidden_agents"), "true"); + assert.deepEqual(lookupUrl.searchParams.getAll("include"), [ + "agent.blocks", + "agent.sources", + "agent.tags", + "agent.tools", + ]); assert.equal(createRequest.method, "POST"); assert.equal(createRequest.url, "/v1/agents/"); assert.equal(createRequest.body.name, SPECIALIST_NAME); @@ -257,6 +477,11 @@ assert.equal(createRequest.body.include_base_tool_rules, false); assert.deepEqual(createRequest.body.tags, ["origin:letta-code"]); assert.match(createRequest.body.system, /no tools, skills, repository access/); + assert.equal(confirmRequest.method, "GET"); + assert.equal(confirmRequest.url, lookupRequest.url); + const paginationUrl = new URL(paginationRequest.url, "http://127.0.0.1"); + assert.equal(paginationRequest.method, "GET"); + assert.equal(paginationUrl.searchParams.get("after"), "agent-sdk-contract"); for (const request of observedRequests) { assert.equal(request.authorization, "Bearer hermetic-contract-key"); } @@ -275,6 +500,18 @@ const runtime = { agent_id: agentId, conversation_id: conversationId, + }; + const computerEnvironment = { + id: "environment-sdk-transport", + connectedAt: 1, + connectionId, + connectionName, + deviceId: "device-sdk-transport", + firstSeenAt: 1, + lastHeartbeat: 1, + lastSeenAt: 1, + organizationId: "organization-sdk-transport", + podId: "pod-sdk-transport", }; const restRequests = []; const socketRequests = []; @@ -299,38 +536,23 @@ response.end(JSON.stringify({ id: conversationId, agent_id: agentId })); return; } - if (request.method === "GET" && request.url === "/v1/environments?limit=100") { + if ( + request.method === "GET" && + request.url === "/v1/environments?limit=100&onlineOnly=true" + ) { response.end( JSON.stringify({ - connections: [ - { - id: "environment-decoy", - connectedAt: 1, - connectionId: "connection-decoy", - connectionName: "another-computer", - deviceId: "device-decoy", - firstSeenAt: 1, - lastHeartbeat: 1, - lastSeenAt: 1, - organizationId: "organization-sdk-transport", - podId: "pod-decoy", - }, - { - id: "environment-sdk-transport", - connectedAt: 1, - connectionId, - connectionName, - deviceId: "device-sdk-transport", - firstSeenAt: 1, - lastHeartbeat: 1, - lastSeenAt: 1, - organizationId: "organization-sdk-transport", - podId: "pod-sdk-transport", - }, - ], + connections: [computerEnvironment], hasNextPage: false, }), ); + return; + } + if ( + request.method === "GET" && + request.url === `/v1/environments/${computerEnvironment.deviceId}` + ) { + response.end(JSON.stringify(computerEnvironment)); return; } response.statusCode = 404; @@ -409,11 +631,13 @@ apiBaseUrl: `http://127.0.0.1:${address.port}`, apiKey: "hermetic-transport-key", backend: "cloud", - computer: { name: connectionName }, requestTimeoutMs: 5_000, }); + const computer = await selectComputer(client, {}); + assert.deepEqual(computer, { deviceId: computerEnvironment.deviceId }); result = await client.prompt("Interpret this minimized receipt.", agentId, { allowedTools: [], + computer, permissionMode: "strict", skillSources: [], stateless: true, @@ -433,8 +657,9 @@ assert.deepEqual( restRequests.map(({ method, url }) => [method, url]), [ + ["GET", "/v1/environments?limit=100&onlineOnly=true"], ["POST", `/v1/conversations/?agent_id=${agentId}`], - ["GET", "/v1/environments?limit=100"], + ["GET", `/v1/environments/${computerEnvironment.deviceId}`], ], ); assert.equal(socketRequests.length, 2); @@ -478,10 +703,10 @@ const client = { agents: { async list() { - return [ + return agentPage([ { id: "one", name: SPECIALIST_NAME }, { id: "two", name: SPECIALIST_NAME }, - ]; + ]); }, }, }; @@ -527,6 +752,21 @@ class FakeClient { constructor(options) { calls.push(["construct", options]); + this.computers = { + list: async (query) => { + calls.push(["computers", query]); + return { + computers: [ + { + deviceId: "device-observer", + name: "observer-computer", + status: "online", + }, + ], + hasNextPage: false, + }; + }, + }; } async prompt(prompt, agentId, options) { @@ -545,7 +785,7 @@ this.agents = { list: async (query) => { calls.push(["list", query]); - return { items: [{ id: "agent-observer", name: SPECIALIST_NAME }] }; + return agentPage([specialistState()]); }, }; } @@ -574,12 +814,22 @@ ]); assert.deepEqual(calls.find(([kind]) => kind === "list"), [ "list", - { limit: 100, name: SPECIALIST_NAME, show_hidden_agents: true }, + { + include: ["agent.blocks", "agent.sources", "agent.tags", "agent.tools"], + limit: 100, + name: SPECIALIST_NAME, + show_hidden_agents: true, + }, + ]); + assert.deepEqual(calls.find(([kind]) => kind === "computers"), [ + "computers", + { limit: 100, onlineOnly: true }, ]); const promptCall = calls.find(([kind]) => kind === "prompt"); assert.ok(promptCall); assert.equal(promptCall[2], "agent-observer"); assert.deepEqual(promptCall[3].allowedTools, []); + assert.deepEqual(promptCall[3].computer, { deviceId: "device-observer" }); assert.deepEqual(promptCall[3].skillSources, []); assert.deepEqual(promptCall[3].tools, []); assert.deepEqual(promptCall[3].toolset, { base: "none" }); -- tangled.sh