From 1eb29764f6c9a0a9e21e196187cd8214b565daf3 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 16:03:04 -0400 Subject: [PATCH 01/17] docs(spec): composable, lightweight cloud agents design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Design for a new docs/12-composable-agents.md page reframing the tutorial's Lambda as one instance of a hierarchical, heartbeat-driven, delegate-and-message-back agent pattern. Conceptual only — no code. Co-Authored-By: Claude Sonnet 5 --- .../2026-08-11-composable-agents-design.md | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-11-composable-agents-design.md diff --git a/docs/superpowers/specs/2026-08-11-composable-agents-design.md b/docs/superpowers/specs/2026-08-11-composable-agents-design.md new file mode 100644 index 0000000..d4d5019 --- /dev/null +++ b/docs/superpowers/specs/2026-08-11-composable-agents-design.md @@ -0,0 +1,143 @@ +# Design: `docs/12-composable-agents.md` — composable, lightweight cloud agents + +## Status + +Approved — ready for writing-plans. + +## Summary + +Add a new documentation page, `docs/12-composable-agents.md`, that reframes this +tutorial's single Lambda as one concrete instance of a general pattern: a +**lightweight, composable cloud agent** — something that spins up, does a few +minutes (or seconds) of work, and goes away. The doc is conceptual only. It adds +no code, no new infrastructure, and no new tables to this repo. Its job is to +name a pattern the reader has already built, then walk it — rung by rung — toward +the fuller shape that pattern can take: a to-do list, delegated sub-agents, +hierarchy, intents flowing back through a message queue, follow-up tasks, and an +EC2/Fargate escape hatch for genuinely long-running work. + +This is documentation-only work. No implementation plan beyond writing the page +itself is expected; `writing-plans` here produces a short plan for drafting and +committing one markdown file, not a code change. + +## Placement + +New file: `docs/12-composable-agents.md`, numbered to sit alongside the existing +`01`–`11` docs. Add it to whatever doc index the README maintains (the same way +`10ba1c7` added `11-aws-bedrock-setup.md` to that index) — check the README's doc +list and add a corresponding line/entry, following the existing entries' +format and tone. + +## Audience and thesis + +Primary teaching goal: **"you already built one of these."** The reader has +just finished (or is working through) a tutorial that ships a single Lambda +function on a 5-minute EventBridge schedule. This doc's job is to name that +Lambda as an instance of a broader, well-known shape — the lightweight +composable cloud agent — and then generalize outward from the concrete thing +they already have, rather than presenting an abstract architecture first and +retrofitting the tutorial onto it second. + +## Structure: a ladder from the existing Lambda to full hierarchical orchestration + +The doc is organized as six sections, each one rung up from the last. Every +rung beyond the first is explicitly marked as **not implemented in this repo** — +conceptual, not a call to action — and cross-links back to the existing docs +(`01-architecture.md`, `10-concurrency.md`, `05-from-tutorial-to-prod.md`) that +already contain the pieces a real implementation would draw on. + +### 1. Intro: naming the pattern + +Opens by naming the pattern in the first paragraph: a lightweight, composable +cloud agent is something that spins up, does a few minutes of work, and goes +away — no persistent process, no long-lived state held in memory. States +immediately that this tutorial already built one. The rest of the page is a +ladder from that one concrete instance up to the general pattern. + +### 2. Rung 1 — the fetch tick as a lightweight agent + +Maps the existing pieces onto the pattern's vocabulary with no new concepts +introduced: + +- **Heartbeat** = the EventBridge 5-minute schedule described in + `01-architecture.md` (`{"op":"fetch"}` as the literal `Input`). +- **Spin up, work, go away** = one Lambda invocation: hydrate from S3, call + Bedrock twice, post to Discord, conditional-write back, exit. +- **Statelessness between runs** = `/tmp` is disposable; all durable state + lives in the S3-backed SQLite file, not in the agent process. +- **Why this is "composable"**: because the agent carries no in-memory state + across invocations, any number of these can exist — different schedules, + different triggers — as long as they agree on the storage contract, which is + exactly what the conditional-write invariant in `10-concurrency.md` already + provides. + +This rung is the load-bearing one: it makes the doc's central claim concrete +before generalizing. "Lightweight cloud agent" is not a new abstraction being +introduced — it's a name for what is already running. + +### 3. Rung 2 — a to-do list instead of one fixed job + +Today the orchestrator's task is hardcoded: every tick does the same fetch. +This rung generalizes that to a to-do list the orchestrator consults each +heartbeat — conceptually, a table of pending work items (could live in the +same SQLite file, consistent with the "one file" philosophy in +`01-architecture.md`) instead of one implicit job. The orchestrator's loop +becomes: wake on heartbeat → read to-do list → decide what's due → act. +Explicitly marked as not implemented here, the same way `10-concurrency.md` +frames the single-writer-queue as a next step rather than something the +tutorial builds. + +### 4. Rung 3 — delegation and hierarchy + +Instead of the orchestrator doing the work itself, it spins up a sub-agent +invocation (another Lambda call) scoped to one to-do item — and that sub-agent +can itself spin up further sub-agents for pieces of its own task, recursively, +not just one level of fan-out. Names the forcing constraint explicitly: +Lambda's ~15-minute runtime ceiling means any unit of work that might run long +must be decomposable into smaller delegated pieces rather than done inline. +Ties back to the single-writer invariant from `10-concurrency.md`: delegation +multiplies *invocations*, not *writers* — sub-agents don't each get their own +S3 conditional-write; that's rung 4. + +### 5. Rung 4 — intents through a message queue, not direct writes + +Once there's a hierarchy, sub-agents can't all conditional-write to the shared +S3 file directly — that's exactly the contention problem `10-concurrency.md`'s +"High contention: the single-writer queue" section already diagrams and +solves. This rung names that diagram as the answer, framed as direct reuse +rather than a new pattern: sub-agents finish their scoped work and emit an +**intent** — a message describing what changed, not a rewritten file — onto a +queue; a single coordinator drains the queue and is the only thing that +touches the master S3 object. Includes an explicit cross-link to that section. + +### 6. Rung 5 — follow-up tasks and the EC2 escape hatch + +Closes the ladder. When a sub-agent's work doesn't finish within its own +invocation, it doesn't retry-loop inside Lambda — it writes a follow-up item +back to the to-do list (rung 2's table) for the orchestrator to pick up and +sequence on a later heartbeat. When a task's *nature* is long-running rather +than just large — something that must hold a connection open, or genuinely +runs past any reasonable Lambda budget — the same message-queue contract from +rung 4 lets an EC2 or Fargate worker participate as a peer: it consumes from +the same queue and emits the same kind of intent. The orchestrator doesn't +need to know or care which compute produced it. + +### 7. Closing note + +Short closing paragraph: this repo implements rung 1 only. Rungs 2–5 are +conceptual. Points the reader to `10-concurrency.md` (single-writer queue) and +`05-from-tutorial-to-prod.md` (exits from SQLite-on-S3) as the docs to read +next for the concrete pieces a real implementation would draw on. + +## Non-goals + +- No new code, tables, or infrastructure in this repo. +- No Step Functions / SQS / DynamoDB task-table design — this was explicitly + scoped out in favor of a conceptual-only page. +- No new terminology beyond what's needed to name the pattern; reuses + "conditional write," "single-writer," "intent," and "coordinator" from the + existing docs rather than inventing parallel vocabulary. + +## Open questions + +None — all sections were reviewed and approved during brainstorming. From 81bb75851f25333576f715b7435b5ecba9f4b6fe Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 16:07:23 -0400 Subject: [PATCH 02/17] docs(spec): add tiered-memory/knowledge-graph rung via core-llm-wiki Rung 6 maps rung 4's "central memory" onto core-llm-wiki's entityId namespacing, tierWeights, per-entity ontology, and scoped access as an illustrative (non-adopted) extension. Co-Authored-By: Claude Sonnet 5 --- .../2026-08-11-composable-agents-design.md | 54 ++++++++++++++++--- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/docs/superpowers/specs/2026-08-11-composable-agents-design.md b/docs/superpowers/specs/2026-08-11-composable-agents-design.md index d4d5019..368ca81 100644 --- a/docs/superpowers/specs/2026-08-11-composable-agents-design.md +++ b/docs/superpowers/specs/2026-08-11-composable-agents-design.md @@ -13,8 +13,10 @@ minutes (or seconds) of work, and goes away. The doc is conceptual only. It adds no code, no new infrastructure, and no new tables to this repo. Its job is to name a pattern the reader has already built, then walk it — rung by rung — toward the fuller shape that pattern can take: a to-do list, delegated sub-agents, -hierarchy, intents flowing back through a message queue, follow-up tasks, and an -EC2/Fargate escape hatch for genuinely long-running work. +hierarchy, intents flowing back through a message queue, follow-up tasks, an +EC2/Fargate escape hatch for genuinely long-running work, and — as an +illustrative extension — a tiered, ontology-aware "central memory" with +scoped, multi-agent permissions. This is documentation-only work. No implementation plan beyond writing the page itself is expected; `writing-plans` here produces a short plan for drafting and @@ -122,12 +124,48 @@ rung 4 lets an EC2 or Fargate worker participate as a peer: it consumes from the same queue and emits the same kind of intent. The orchestrator doesn't need to know or care which compute produced it. -### 7. Closing note - -Short closing paragraph: this repo implements rung 1 only. Rungs 2–5 are -conceptual. Points the reader to `10-concurrency.md` (single-writer queue) and -`05-from-tutorial-to-prod.md` (exits from SQLite-on-S3) as the docs to read -next for the concrete pieces a real implementation would draw on. +### 7. Rung 6 — tiered memory, a basic knowledge graph, and scoped permissions + +Rung 4 named "central memory" as the thing a coordinator writes intents into, +without saying what that memory is shaped like. This rung points to a concrete +answer: [`@equationalapplications/core-llm-wiki`](https://github.com/equationalapplications/expo-llm-wiki/blob/main/packages/core/README.md) +(specifically `packages/core/README.md` in that repo), a platform-agnostic +TypeScript memory engine already built for hybrid LLM memory over SQLite. The +mapping is conceptual, not a dependency this repo takes on: + +- **Multi-agent namespacing.** `WikiMemory`'s `entityId` is exactly the + identifier a hierarchy of agents needs: each orchestrator, sub-agent, or + task line could read/write its own `entityId` namespace, or a coordinator + could read across several in one call (`read([entityIdA, entityIdB], …)`). +- **Tiered memory.** `tierWeights` (e.g. `tier_wisdom`, `tier_fact`, + `tier_working`) gives the "central memory" from rung 4 actual tiers — + durable curated knowledge weighted high, working/session-scoped context + from an in-flight sub-agent weighted low or excluded — instead of one flat + fact table. +- **Basic knowledge graph.** The per-entity seeded ontology (`strict` / + `emergent` / `off` modes, `node_types`/`edge_types`, typed facts with + inline `edges`) is a lightweight graph layer: intents coming back from + sub-agents can carry typed relationships (e.g. `task --produced_by--> + sub_agent_run`) rather than opaque text blobs. +- **Scoped permissions.** Because retrieval and writes are already + partitioned by `entityId`, restricting a given sub-agent's coordinator + access to specific entity namespaces (or specific tiers within one) is a + natural enforcement point for "this sub-agent may only see/write its own + scope" — the same boundary a hierarchy needs to keep a low-trust leaf agent + from reading or corrupting a sibling's or the orchestrator's memory. + +This rung is explicitly the most speculative: it names a specific external +package as an illustration of what a fuller "central memory" could look like, +not a recommendation to adopt it in this tutorial. No code or dependency +changes are implied. + +### 8. Closing note + +Short closing paragraph: this repo implements rung 1 only. Rungs 2–6 are +conceptual. Points the reader to `10-concurrency.md` (single-writer queue), +`05-from-tutorial-to-prod.md` (exits from SQLite-on-S3), and +`core-llm-wiki`'s README (tiered memory, ontology, scoped permissions) as the +docs to read next for the concrete pieces a real implementation would draw on. ## Non-goals From e07ec4395a1de8f42aff613dc9b3728645c208c9 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 16:19:25 -0400 Subject: [PATCH 03/17] docs(spec): address review of composable-agents design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six fixes from spec review, all clarifications rather than scope changes; Status stays "Approved — ready for writing-plans". - Correct section count: six sections -> eight (intro, six rungs, closing). - Rung 4 now explicitly introduces the term "central memory", which rung 6 had been calling back to without it ever being named. - Rewrite the rung 1 statelessness bullet: /tmp is not simply disposable. 01-architecture.md:10 leans on warm-container reuse for the status reader and 02-rehydration.md:91 notes it survives until redeploy, so the claim is that no *durable* state lives there, not that it vanishes. - Reword the "no new terminology" non-goal to "no *parallel* vocabulary", since the ladder does legitimately introduce heartbeat/rung/to-do list/ sub-agent/central memory. - Expand Placement: index table only (leave prose links alone), row format, and that the 12- row goes above the unnumbered bedrock-model-comparison row. - Add a Length and format section (120-180 lines, diagrams sparingly). Also add a "verify before drafting" note to rung 6: it cites a lot of external core-llm-wiki API surface and links a repo name that differs from the package name, so that README needs fetching before the doc is written. Co-Authored-By: Claude Opus 5 --- .../2026-08-11-composable-agents-design.md | 68 +++++++++++++++---- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/docs/superpowers/specs/2026-08-11-composable-agents-design.md b/docs/superpowers/specs/2026-08-11-composable-agents-design.md index 368ca81..e7351ed 100644 --- a/docs/superpowers/specs/2026-08-11-composable-agents-design.md +++ b/docs/superpowers/specs/2026-08-11-composable-agents-design.md @@ -25,10 +25,27 @@ committing one markdown file, not a code change. ## Placement New file: `docs/12-composable-agents.md`, numbered to sit alongside the existing -`01`–`11` docs. Add it to whatever doc index the README maintains (the same way -`10ba1c7` added `11-aws-bedrock-setup.md` to that index) — check the README's doc -list and add a corresponding line/entry, following the existing entries' -format and tone. +`01`–`11` docs. Add it to the doc index table the README maintains (the same way +`10ba1c7` added `11-aws-bedrock-setup.md` to that index), following the existing +entries' format and tone: `| [docs/NN-name.md](docs/NN-name.md) | one-line +description |`. Two details: + +- The index table is the only place to add an entry. The README links to + individual docs in prose elsewhere; leave those alone. +- `bedrock-model-comparison.md` sits last in the table as the only unnumbered + row, so the `12-` row goes immediately above it, not at the bottom. + +## Length and format + +Target roughly 120–180 lines — in the range of the existing conceptual docs +(`01`–`08`, `10` run 49–157 lines) rather than the two long procedural ones +(`09` and `11`, 431 and 476). A six-rung ladder can easily outgrow both if each +rung is allowed to sprawl; keep each rung to a few tight paragraphs. + +ASCII diagrams are optional and should be used sparingly if at all. +`10-concurrency.md` uses two fenced blocks; `01-architecture.md` uses none. +Rung 4 is the one place a diagram might earn its keep — and it can instead just +cross-link the diagram `10-concurrency.md` already has. ## Audience and thesis @@ -42,7 +59,8 @@ retrofitting the tutorial onto it second. ## Structure: a ladder from the existing Lambda to full hierarchical orchestration -The doc is organized as six sections, each one rung up from the last. Every +The doc is organized as eight sections — an intro, six rungs, and a closing +note — each rung one step up from the last. Every rung beyond the first is explicitly marked as **not implemented in this repo** — conceptual, not a call to action — and cross-links back to the existing docs (`01-architecture.md`, `10-concurrency.md`, `05-from-tutorial-to-prod.md`) that @@ -65,8 +83,13 @@ introduced: `01-architecture.md` (`{"op":"fetch"}` as the literal `Input`). - **Spin up, work, go away** = one Lambda invocation: hydrate from S3, call Bedrock twice, post to Discord, conditional-write back, exit. -- **Statelessness between runs** = `/tmp` is disposable; all durable state - lives in the S3-backed SQLite file, not in the agent process. +- **Statelessness between runs** = no *durable* state is held in `/tmp`. Warm + containers may reuse it — `01-architecture.md` leans on exactly that to let + the status reader work, and `02-rehydration.md` notes it survives until a + redeploy — but correctness never depends on it. Everything that has to + outlive an invocation lives in the S3-backed SQLite file, not in the agent + process. State the nuance rather than claiming `/tmp` is simply disposable; + a reader who has finished doc 01 will know better. - **Why this is "composable"**: because the agent carries no in-memory state across invocations, any number of these can exist — different schedules, different triggers — as long as they agree on the storage contract, which is @@ -112,6 +135,11 @@ rather than a new pattern: sub-agents finish their scoped work and emit an queue; a single coordinator drains the queue and is the only thing that touches the master S3 object. Includes an explicit cross-link to that section. +This rung must also introduce the term **central memory** for the master S3 +object as seen from the hierarchy's point of view — the one shared thing every +agent's intents eventually land in. Rung 6 builds directly on that term, so it +has to be named here rather than appearing for the first time later. + ### 6. Rung 5 — follow-up tasks and the EC2 escape hatch Closes the ladder. When a sub-agent's work doesn't finish within its own @@ -155,9 +183,19 @@ mapping is conceptual, not a dependency this repo takes on: from reading or corrupting a sibling's or the orchestrator's memory. This rung is explicitly the most speculative: it names a specific external -package as an illustration of what a fuller "central memory" could look like, -not a recommendation to adopt it in this tutorial. No code or dependency -changes are implied. +package as an illustration of what the central memory from rung 4 could look +like in a fuller form, not a recommendation to adopt it in this tutorial. No +code or dependency changes are implied. + +**Verify before drafting.** This rung cites a lot of external API surface +(`WikiMemory`, `entityId`, `tierWeights`, the `tier_wisdom`/`tier_fact`/ +`tier_working` tiers, `node_types`/`edge_types`, the `strict`/`emergent`/`off` +ontology modes, the `read([entityIdA, entityIdB], …)` signature), and the link +points at repo `expo-llm-wiki` while the package is named +`@equationalapplications/core-llm-wiki` — plausible for a monorepo, but +unconfirmed. Fetch that README first and confirm both the URL and every cited +name. This is the section most likely to have rotted; drop or soften any +specific that no longer matches rather than guessing. ### 8. Closing note @@ -172,9 +210,13 @@ docs to read next for the concrete pieces a real implementation would draw on. - No new code, tables, or infrastructure in this repo. - No Step Functions / SQS / DynamoDB task-table design — this was explicitly scoped out in favor of a conceptual-only page. -- No new terminology beyond what's needed to name the pattern; reuses - "conditional write," "single-writer," "intent," and "coordinator" from the - existing docs rather than inventing parallel vocabulary. +- No *parallel* vocabulary for concepts the existing docs already name. Where + a doc already has a word for something — "conditional write," "single-writer," + "intent," "coordinator" — reuse that word rather than coining a synonym. The + ladder does introduce terms of its own where nothing existing covers the + concept ("heartbeat," "rung," "to-do list," "sub-agent," "central memory"); + that is expected. The rule is no duplicate names for the same idea, not zero + new names. ## Open questions From 19af220aee66e568506176862fc09d65a62b915d Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 16:24:47 -0400 Subject: [PATCH 04/17] docs(plan): composable agents doc implementation plan --- .../plans/2026-08-11-composable-agents-doc.md | 522 ++++++++++++++++++ 1 file changed, 522 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-11-composable-agents-doc.md diff --git a/docs/superpowers/plans/2026-08-11-composable-agents-doc.md b/docs/superpowers/plans/2026-08-11-composable-agents-doc.md new file mode 100644 index 0000000..0a786c2 --- /dev/null +++ b/docs/superpowers/plans/2026-08-11-composable-agents-doc.md @@ -0,0 +1,522 @@ +# Composable Agents Doc Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Create `docs/12-composable-agents.md` — a conceptual page that names this tutorial's Lambda as a lightweight composable cloud agent, then climbs a six-rung ladder from it to hierarchical orchestration with tiered memory — and add its row to the README doc index. + +**Architecture:** Documentation only. One new markdown file built up over four commits (intro + rung 1; rungs 2–3; rungs 4–5; rung 6 + closing), then a one-line README table edit, then a verification pass. Every rung past the first opens with a blockquote marking it as not implemented in this repo, and cross-links back to `01-architecture.md`, `10-concurrency.md`, or `05-from-tutorial-to-prod.md` for the pieces a real implementation would draw on. + +**Tech Stack:** Markdown only. No changes to `src/`, `infra/`, `tests/`, `package.json`, `Dockerfile`, or any config file. No new dependencies — the external package named in rung 6 is cited as an illustration, never installed. + +**Spec:** `docs/superpowers/specs/2026-08-11-composable-agents-design.md` + +--- + +## Context the implementing engineer needs + +You have not seen this repo. It is a tutorial: one AWS Lambda container-image function on a 5-minute EventBridge schedule fetches a couple of data sources, calls Bedrock twice (a chat model to format a message, Titan to embed it), posts to a Discord webhook, and writes a single SQLite file back to one S3 object using a conditional write. That's the whole system. + +Read these three docs before Task 1 — they are short, and the new page cross-links all of them: + +- `docs/01-architecture.md` (73 lines) — **the style template.** Match it: sentence-case `##` headings, prose wrapped at roughly 90 columns, links written as `[docs/NN-name.md](NN-name.md)` (repo-relative label, sibling-relative target — that is the existing convention inside `docs/`), no bullet-heavy structure where a paragraph will do. +- `docs/10-concurrency.md` (157 lines) — rungs 4 and 5 lean on its `## High contention: the single-writer queue` section (line 107) and its ASCII diagram (lines 114–127). Do not redraw that diagram; link to it. +- `docs/05-from-tutorial-to-prod.md` (49 lines) — named in the closing note as the exits from SQLite-on-S3. + +Facts verified against this repo — do not restate them differently: + +| Fact | Source | +|---|---| +| The schedule's EventBridge `Input` is the literal string `{"op":"fetch"}` | `docs/01-architecture.md:66-73` | +| The function has two ops read from `event.op`: `fetch` (writer, scheduled) and `status` (reader, Function URL with `authType: AWS_IAM`) | `docs/01-architecture.md:3-9` | +| Warm invocations share `/tmp`; the status reader deliberately reuses its cached handle, and `/tmp` survives until a redeploy | `docs/01-architecture.md:10-12`, `docs/02-rehydration.md:89-91` | +| `reservedConcurrentExecutions: 1` is the single-writer invariant; the conditional write is a second line of defense | `docs/01-architecture.md:44-52` | +| The high-contention answer is: read-only agents, writes serialized as SQS messages, one pinned coordinator with concurrency 1, batched apply | `docs/10-concurrency.md:107-142` | +| Docs 10 already uses the words **intent** and **coordinator** for exactly these roles | `docs/10-concurrency.md:131-134` | +| The README doc index table runs `docs/01…docs/11`, then `bedrock-model-comparison.md` last as the only unnumbered row | `README.md:176-187` | + +External API surface cited in rung 6 was **verified on 2026-08-11** against +`https://raw.githubusercontent.com/equationalapplications/expo-llm-wiki/main/packages/core/README.md`. Confirmed verbatim: package name `@equationalapplications/core-llm-wiki`; class `WikiMemory`; `entityId`; `write(entityId, { event_type, summary })`; `read()` accepting one entity id or an array; `tierWeights`; ontology modes `'strict'` / `'emergent'` / `'off'` (default `off`); `node_types` / `edge_types` in a seed manifest; facts carrying inline `edges` with `edge_type` / `target_title`. + +**One correction to the spec.** The spec describes `tier_wisdom` / `tier_fact` / `tier_working` as tier names. They are not: in that README they are **entity ids** — ordinary namespaces that happen to be named after tiers — and `tierWeights` assigns each a weight. The verified example is: + +```typescript +const memory = await wikiMemory.read( + ['tier_wisdom', 'tier_fact', 'tier_working'], + 'Which source should I trust?', + { maxResults: 8, tierWeights: { tier_wisdom: 2, tier_fact: 1, tier_working: 0.25 } }, +); +``` + +The drafted prose in Task 4 already reflects this. Write it as drafted; do not "fix" it back toward the spec's wording. + +--- + +## File Structure + +- **Create** `docs/12-composable-agents.md` — the only new file. Built over Tasks 1–4, one commit per section group, so every commit leaves a readable document. Target 120–180 lines total; the drafted content lands at roughly 155. +- **Modify** `README.md` — insert one row in the doc index table, immediately above the `bedrock-model-comparison.md` row. Task 5. + +Not touched: every other file in the repo, including the other files in `docs/`. Historical records under `docs/superpowers/plans/` and `docs/superpowers/specs/` are never rewritten. + +--- + +## Task 0: Branch check + +**Files:** none + +- [ ] **Step 1: Confirm the working tree is clean and you are on the right branch** + +```bash +git rev-parse --abbrev-ref HEAD +git status --porcelain +``` + +Expected: branch `docs/composable-agents-spec-revisions`, and empty output from `git status --porcelain`. + +The approved spec is already committed on this branch (`e07ec43`), so stay on it — do not create a new branch. If `git rev-parse` prints `main` instead, the spec commits are missing; stop and ask, rather than branching from a tree that lacks the spec. + +--- + +## Task 1: Intro and rung 1 + +**Files:** +- Create: `docs/12-composable-agents.md` + +- [ ] **Step 1: Write the opener and rung 1** + +Create `docs/12-composable-agents.md` with exactly this content: + +````markdown +# Composable agents + +A **lightweight, composable cloud agent** is a thing that spins up, does a few minutes — +or a few seconds — of work, and goes away. No process stays resident. No state is held in +memory between runs. Whatever has to outlive one run gets written down somewhere durable +before the agent exits. + +You already built one. The `fetch` tick in this repo *is* an agent of exactly that shape, +and the rest of this page is a ladder: rung 1 is the thing you have, and each rung above +it is one step toward the fuller shape this pattern takes when there is more than one +agent and more than one job. + +Only rung 1 is implemented here. Everything from rung 2 up is conceptual — no code, no +tables, and no infrastructure in this repo correspond to it. Each rung says so, and points +at the existing doc that already holds the piece a real implementation would start from. + +## Rung 1: the fetch tick is already one + +Nothing new is introduced at this rung. It is the same system described in +[docs/01-architecture.md](01-architecture.md), renamed in the vocabulary the rest of this +page uses. + +**Heartbeat.** The EventBridge 5-minute schedule, whose `Input` is the literal string +`{"op":"fetch"}`. Something outside the agent decides when it runs; the agent itself has +no timer, no loop, and no opinion about the clock. + +**Spin up, work, go away.** One Lambda invocation is the entire lifetime: hydrate the +SQLite file from S3, call Bedrock twice, post to Discord, conditional-write the file back, +exit. There is no "between" for the agent to exist in. + +**Statelessness between runs.** Careful here, because the honest version is more +interesting than the slogan. `/tmp` is not wiped between invocations — warm containers +reuse it, and this repo leans on that: the status reader keeps a cached database handle +across invocations precisely because `/tmp` persists, and +[docs/02-rehydration.md](02-rehydration.md) notes it survives until a redeploy. The +guarantee is not that `/tmp` is empty. The guarantee is that **correctness never depends +on what is in it.** Everything that must outlive an invocation lives in the S3 object; the +local file is a cache that a cold start is free to throw away. + +**Why "composable."** Because no state is carried in the agent process, there is nothing +to coordinate except the storage. Any number of these can exist — different schedules, +different triggers, different jobs — provided they agree on the storage contract. This +repo's contract is the conditional write on the S3 object, guarded by +`reservedConcurrentExecutions: 1` and described in +[docs/10-concurrency.md](10-concurrency.md). That contract, not any shared runtime, is +what makes a second agent possible. + +This is the load-bearing rung. "Lightweight cloud agent" is not an abstraction being +introduced — it is a name for the thing already running on your schedule. +```` + +- [ ] **Step 2: Verify the file renders and the links resolve** + +```bash +wc -l docs/12-composable-agents.md +for f in 01-architecture.md 02-rehydration.md 10-concurrency.md; do test -f "docs/$f" && echo "ok $f"; done +``` + +Expected: about 55 lines, and three `ok` lines. + +- [ ] **Step 3: Commit** + +```bash +git add docs/12-composable-agents.md +git commit -m "docs(agents): name the fetch tick as a lightweight composable agent" +``` + +--- + +## Task 2: Rungs 2 and 3 + +**Files:** +- Modify: `docs/12-composable-agents.md` (append) + +- [ ] **Step 1: Append rungs 2 and 3** + +Append exactly this to the end of `docs/12-composable-agents.md`: + +````markdown + +## Rung 2: a to-do list instead of one fixed job + +> **Not implemented in this repo.** Conceptual from here on. + +Today the job is hardcoded: every heartbeat does the same fetch, because the fetch *is* +the schedule's payload. An agent that only ever does one thing does not need to be told +what to do. + +The first generalization is to stop hardcoding it. Give the agent a **to-do list** — a +table of pending work items, each with enough to decide whether it is due — and the loop +becomes: wake on heartbeat, read the list, decide what is due, act. The heartbeat stops +meaning "do the fetch" and starts meaning "check whether there is anything to do." + +The natural home for that table is the same SQLite file the agent already hydrates, which +keeps the one-file philosophy from [docs/01-architecture.md](01-architecture.md) intact — +the work queue and the work product live in the same object, committed by the same +conditional write. Nothing new has to exist for this rung; it is a table and a `WHERE` +clause. + +At this rung the agent that reads the list is also the agent that does the work. That is +the constraint rung 3 removes. + +## Rung 3: delegation and hierarchy + +> **Not implemented in this repo.** + +Instead of doing a to-do item itself, the agent invokes another Lambda scoped to that one +item, and moves on. The first agent becomes an **orchestrator**: its job is deciding what +runs, not running it. The invoked one is a **sub-agent**. + +This is recursive, not one level of fan-out. A sub-agent handed "summarize this week's +readings" can decide that is still too big, split it into seven days, and invoke seven +sub-agents of its own. Depth is a property of the work, not of the topology. + +The forcing constraint is Lambda's roughly 15-minute runtime ceiling. Any unit of work +that might exceed it cannot be done inline — it has to be decomposable into pieces that +each fit, or moved off Lambda entirely (rung 5). Delegation is not primarily an elegance +argument; it is how work outgrows a single invocation without outgrowing the platform. + +One thing delegation does **not** change: the single-writer invariant from +[docs/10-concurrency.md](10-concurrency.md). Fanning out multiplies *invocations*, not +*writers*. Sub-agents do not each get their own conditional write to the shared S3 object +— thirty agents contending on one ETag is the failure mode that doc describes, not a +design. Where their results actually go is rung 4. +```` + +- [ ] **Step 2: Verify** + +```bash +wc -l docs/12-composable-agents.md +grep -c "Not implemented in this repo" docs/12-composable-agents.md +``` + +Expected: about 100 lines; `grep -c` prints `2` — one per new rung. The intro paragraph +says the same thing in prose and does not contain that literal phrase, so it is not +counted. + +- [ ] **Step 3: Commit** + +```bash +git add docs/12-composable-agents.md +git commit -m "docs(agents): add to-do list and delegation rungs" +``` + +--- + +## Task 3: Rungs 4 and 5 + +**Files:** +- Modify: `docs/12-composable-agents.md` (append) + +- [ ] **Step 1: Append rungs 4 and 5** + +Append exactly this to the end of `docs/12-composable-agents.md`: + +````markdown + +## Rung 4: intents through a queue, not direct writes + +> **Not implemented in this repo.** + +Once there is a hierarchy, the question rung 3 deferred comes due: how do a dozen +sub-agents get their results into one S3 object? Not by each conditional-writing it. That +is the contention problem — every attempt invalidating someone else's ETag until the fleet +thrashes instead of committing — and +[docs/10-concurrency.md](10-concurrency.md#high-contention-the-single-writer-queue) +already diagrams the answer. + +Reuse it exactly as written. Sub-agents become read-only: they hydrate sub-copies, do +their scoped work, and emit an **intent** — a message describing what changed, not a +rewritten file — onto a queue. One pinned coordinator, concurrency 1, drains the queue in +batches and is the only thing that touches the master object. Collisions stop being +detected and start being impossible, and the S3 write rate drops to one per batch rather +than one per agent. The costs are the ones that doc already names: writes become +asynchronous, and at-least-once delivery means the coordinator's apply step has to be +idempotent. + +This rung also gives the master object a name it will need later. Seen from inside the +hierarchy, it is the **central memory**: the one shared thing every agent's intents +eventually land in, and the only place where the system's state is authoritative. Rung 6 +is about what that memory could be shaped like. + +## Rung 5: follow-up tasks and the EC2 escape hatch + +> **Not implemented in this repo.** + +Two things break the ladder if left unaddressed, and both are answered by pieces already +on it. + +**Work that doesn't finish.** A sub-agent approaching its timeout does not retry-loop +inside Lambda, and does not silently drop what it was doing. It writes a **follow-up +item** back to the to-do list from rung 2 — "resume from here" — and exits cleanly. The +orchestrator picks it up on a later heartbeat and sequences it like any other item. +Progress is durable because it was written down, not because a process stayed alive. + +**Work that is long-running by nature.** Some tasks are not merely large: they hold a +connection open, or stream for an hour, or genuinely run past any Lambda budget you could +justify. Decomposition does not help there. The escape hatch is that rung 4's contract +does not mention Lambda anywhere — it says *consume from the queue, emit an intent*. An +EC2 instance or a Fargate task can satisfy that contract as a peer. It reads the same +queue and emits the same kind of intent, and the coordinator cannot tell, and does not +need to, which compute produced it. + +That is the payoff for making the queue the seam rather than the function: the choice of +compute becomes a per-task decision instead of an architectural one. +```` + +- [ ] **Step 2: Verify the anchor link is correct** + +The link `10-concurrency.md#high-contention-the-single-writer-queue` must match GitHub's +slug for that heading. Confirm the heading text: + +```bash +grep -n "^## High contention" docs/10-concurrency.md +``` + +Expected: `107:## High contention: the single-writer queue`. GitHub slugifies that to +`high-contention-the-single-writer-queue` (lowercased, spaces to hyphens, colon dropped). +If the heading text differs from the above, regenerate the anchor by the same rule rather +than keeping the drafted one. + +```bash +wc -l docs/12-composable-agents.md +``` + +Expected: about 145 lines. + +- [ ] **Step 3: Commit** + +```bash +git add docs/12-composable-agents.md +git commit -m "docs(agents): add intent-queue and follow-up/escape-hatch rungs" +``` + +--- + +## Task 4: Rung 6 and the closing note + +**Files:** +- Modify: `docs/12-composable-agents.md` (append) + +Every identifier below was verified on 2026-08-11 against the upstream README (see the +context section). Write it as drafted. If you re-check the README and something has since +changed, drop or soften that specific rather than guessing at a replacement — this rung is +an illustration, and a wrong API name is worse than a vaguer sentence. + +- [ ] **Step 1: Append rung 6 and the closing note** + +Append exactly this to the end of `docs/12-composable-agents.md`: + +````markdown + +## Rung 6: tiered memory, a small knowledge graph, and scoped permissions + +> **Not implemented in this repo, and the most speculative rung on the ladder.** It names +> a specific external package as an illustration of what rung 4's central memory could +> look like in a fuller form. Nothing here is a recommendation to adopt it in this +> tutorial, and no dependency is implied. + +Rung 4 named the central memory without saying what it is shaped like. In this repo it is +two flat tables. A hierarchy of agents wants more than that, and +[`@equationalapplications/core-llm-wiki`](https://github.com/equationalapplications/expo-llm-wiki/blob/main/packages/core/README.md) +— a platform-agnostic TypeScript memory engine built for hybrid LLM memory over SQLite — +happens to be organized around four things a hierarchy needs. + +**Namespacing.** Its `entityId` is the identifier a hierarchy is already missing: each +orchestrator, sub-agent, or task line reads and writes its own namespace via +`write(entityId, { event_type, summary })`. A coordinator can read across several at once, +because `read()` accepts either one entity id or an array of them. + +**Tiering.** Those namespaces can be weighted rather than merely merged. The README's own +example reads `['tier_wisdom', 'tier_fact', 'tier_working']` with `tierWeights` of `2`, +`1`, and `0.25` — durable curated knowledge dominating, an in-flight sub-agent's working +context present but nearly discounted. The tiers are just entity ids with a naming +convention and different weights, which is why the same mechanism serves both purposes. + +**A small knowledge graph.** A per-entity seeded ontology (`node_types` and `edge_types`, +under a `'strict'`, `'emergent'`, or `'off'` mode — `off` by default) lets stored facts +carry typed `edges` rather than opaque text. An intent coming back from a sub-agent can +say *this artifact was produced by that run* as a typed relationship instead of a sentence +someone has to re-parse later. + +**Scoped permissions.** Because both reads and writes are already partitioned by +`entityId`, that partition is the natural enforcement point: restrict a low-trust leaf +agent to its own namespaces, or to specific tiers within one, and it cannot read or +corrupt a sibling's memory or the orchestrator's. The permission boundary a hierarchy +needs turns out to be the same boundary the storage layer already draws. + +## Where this repo stops + +Rung 1, and nothing above it. The `fetch` tick is a real lightweight composable agent; +rungs 2 through 6 are a sketch of what it grows into, not a backlog. + +For the concrete pieces a real implementation would draw on: +[docs/10-concurrency.md](10-concurrency.md) for the single-writer queue that rungs 4 and 5 +are built on, [docs/05-from-tutorial-to-prod.md](05-from-tutorial-to-prod.md) for the +exits from SQLite-on-S3 once you need transactional consistency across agents, and +`core-llm-wiki`'s README for tiered memory, ontology, and scoped permissions. +```` + +- [ ] **Step 2: Verify length and the external link** + +```bash +wc -l docs/12-composable-agents.md +``` + +Expected: 150–175 lines. The spec's target band is 120–180; if you are over 180, tighten +prose rather than dropping a rung. + +```bash +curl -sI https://raw.githubusercontent.com/equationalapplications/expo-llm-wiki/main/packages/core/README.md | head -1 +``` + +Expected: `HTTP/2 200`. A 404 means the repo or path moved — fix the link in the doc +before committing. + +- [ ] **Step 3: Commit** + +```bash +git add docs/12-composable-agents.md +git commit -m "docs(agents): add tiered-memory rung and closing note" +``` + +--- + +## Task 5: README doc index row + +**Files:** +- Modify: `README.md` (the doc index table, around line 186) + +- [ ] **Step 1: Locate the insertion point** + +```bash +grep -n "bedrock-model-comparison.md) | Why" README.md +``` + +Expected: one hit, around line 187. The new row goes **immediately above** it — the +comparison doc is the only unnumbered row and stays last. + +- [ ] **Step 2: Insert the row** + +Add this line directly above that `bedrock-model-comparison.md` row: + +```markdown +| [docs/12-composable-agents.md](docs/12-composable-agents.md) | The fetch tick as a lightweight composable agent, and the ladder up from it | +``` + +Note the link form: rows in this table use the full `docs/…` path in both label and +target, unlike links *inside* `docs/`, which are sibling-relative. Match the table. + +- [ ] **Step 3: Verify the table order** + +```bash +grep -n "^| \[docs/" README.md +``` + +Expected: rows `01` through `11` in order, then the new `12` row, then +`bedrock-model-comparison.md` last. + +- [ ] **Step 4: Commit** + +```bash +git add README.md +git commit -m "docs: add composable agents to the doc index" +``` + +--- + +## Task 6: Final verification + +**Files:** none + +- [ ] **Step 1: Check every relative link in the new doc resolves** + +```bash +grep -oE "\]\([0-9a-z-]+\.md" docs/12-composable-agents.md | sed 's/](//' | sort -u | while read -r f; do + test -f "docs/$f" && echo "ok $f" || echo "BROKEN $f" +done +``` + +Expected: `ok` for `01-architecture.md`, `02-rehydration.md`, `05-from-tutorial-to-prod.md`, +and `10-concurrency.md`. Any `BROKEN` line must be fixed before finishing. + +- [ ] **Step 2: Confirm the doc is conceptual-only — no code or infra changed** + +```bash +git diff --stat main...HEAD -- src infra tests package.json package-lock.json Dockerfile +``` + +Expected: empty output. Anything listed here violates the spec's non-goals; revert it. + +- [ ] **Step 3: Confirm every rung past the first is marked** + +```bash +grep -c "Not implemented in this repo" docs/12-composable-agents.md +``` + +Expected: `5` — one blockquote per rung, rungs 2 through 6. + +- [ ] **Step 4: Confirm the whole spec is covered** + +```bash +grep -n "^#" docs/12-composable-agents.md +``` + +Expected eight headings in this order: `# Composable agents`, then +`## Rung 1: the fetch tick is already one`, `## Rung 2: a to-do list instead of one fixed +job`, `## Rung 3: delegation and hierarchy`, `## Rung 4: intents through a queue, not +direct writes`, `## Rung 5: follow-up tasks and the EC2 escape hatch`, `## Rung 6: tiered +memory, a small knowledge graph, and scoped permissions`, `## Where this repo stops`. + +- [ ] **Step 5: Mark the spec implemented** + +Edit `docs/superpowers/specs/2026-08-11-composable-agents-design.md` line 5, replacing: + +```markdown +Approved — ready for writing-plans. +``` + +with: + +```markdown +Implemented — `docs/12-composable-agents.md`. +``` + +- [ ] **Step 6: Commit** + +```bash +git add docs/superpowers/specs/2026-08-11-composable-agents-design.md +git commit -m "docs(spec): mark composable agents design implemented" +``` + +Then hand off to `superpowers:finishing-a-development-branch` for the PR. From 225c391f004cf12e7a93ca87bf970126c6359a0b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 16:25:51 -0400 Subject: [PATCH 05/17] docs(agents): name the fetch tick as a lightweight composable agent --- docs/12-composable-agents.md | 49 ++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 docs/12-composable-agents.md diff --git a/docs/12-composable-agents.md b/docs/12-composable-agents.md new file mode 100644 index 0000000..f959e38 --- /dev/null +++ b/docs/12-composable-agents.md @@ -0,0 +1,49 @@ +# Composable agents + +A **lightweight, composable cloud agent** is a thing that spins up, does a few minutes — +or a few seconds — of work, and goes away. No process stays resident. No state is held in +memory between runs. Whatever has to outlive one run gets written down somewhere durable +before the agent exits. + +You already built one. The `fetch` tick in this repo *is* an agent of exactly that shape, +and the rest of this page is a ladder: rung 1 is the thing you have, and each rung above +it is one step toward the fuller shape this pattern takes when there is more than one +agent and more than one job. + +Only rung 1 is implemented here. Everything from rung 2 up is conceptual — no code, no +tables, and no infrastructure in this repo correspond to it. Each rung says so, and points +at the existing doc that already holds the piece a real implementation would start from. + +## Rung 1: the fetch tick is already one + +Nothing new is introduced at this rung. It is the same system described in +[docs/01-architecture.md](01-architecture.md), renamed in the vocabulary the rest of this +page uses. + +**Heartbeat.** The EventBridge 5-minute schedule, whose `Input` is the literal string +`{"op":"fetch"}`. Something outside the agent decides when it runs; the agent itself has +no timer, no loop, and no opinion about the clock. + +**Spin up, work, go away.** One Lambda invocation is the entire lifetime: hydrate the +SQLite file from S3, call Bedrock twice, post to Discord, conditional-write the file back, +exit. There is no "between" for the agent to exist in. + +**Statelessness between runs.** Careful here, because the honest version is more +interesting than the slogan. `/tmp` is not wiped between invocations — warm containers +reuse it, and this repo leans on that: the status reader keeps a cached database handle +across invocations precisely because `/tmp` persists, and +[docs/02-rehydration.md](02-rehydration.md) notes it survives until a redeploy. The +guarantee is not that `/tmp` is empty. The guarantee is that **correctness never depends +on what is in it.** Everything that must outlive an invocation lives in the S3 object; the +local file is a cache that a cold start is free to throw away. + +**Why "composable."** Because no state is carried in the agent process, there is nothing +to coordinate except the storage. Any number of these can exist — different schedules, +different triggers, different jobs — provided they agree on the storage contract. This +repo's contract is the conditional write on the S3 object, guarded by +`reservedConcurrentExecutions: 1` and described in +[docs/10-concurrency.md](10-concurrency.md). That contract, not any shared runtime, is +what makes a second agent possible. + +This is the load-bearing rung. "Lightweight cloud agent" is not an abstraction being +introduced — it is a name for the thing already running on your schedule. From 844975c38247cd1081ceb2d23a24c8555ea25e6a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 16:26:33 -0400 Subject: [PATCH 06/17] docs(agents): add to-do list and delegation rungs --- docs/12-composable-agents.md | 45 ++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/docs/12-composable-agents.md b/docs/12-composable-agents.md index f959e38..e9bbf15 100644 --- a/docs/12-composable-agents.md +++ b/docs/12-composable-agents.md @@ -47,3 +47,48 @@ what makes a second agent possible. This is the load-bearing rung. "Lightweight cloud agent" is not an abstraction being introduced — it is a name for the thing already running on your schedule. + +## Rung 2: a to-do list instead of one fixed job + +> **Not implemented in this repo.** Conceptual from here on. + +Today the job is hardcoded: every heartbeat does the same fetch, because the fetch *is* +the schedule's payload. An agent that only ever does one thing does not need to be told +what to do. + +The first generalization is to stop hardcoding it. Give the agent a **to-do list** — a +table of pending work items, each with enough to decide whether it is due — and the loop +becomes: wake on heartbeat, read the list, decide what is due, act. The heartbeat stops +meaning "do the fetch" and starts meaning "check whether there is anything to do." + +The natural home for that table is the same SQLite file the agent already hydrates, which +keeps the one-file philosophy from [docs/01-architecture.md](01-architecture.md) intact — +the work queue and the work product live in the same object, committed by the same +conditional write. Nothing new has to exist for this rung; it is a table and a `WHERE` +clause. + +At this rung the agent that reads the list is also the agent that does the work. That is +the constraint rung 3 removes. + +## Rung 3: delegation and hierarchy + +> **Not implemented in this repo.** + +Instead of doing a to-do item itself, the agent invokes another Lambda scoped to that one +item, and moves on. The first agent becomes an **orchestrator**: its job is deciding what +runs, not running it. The invoked one is a **sub-agent**. + +This is recursive, not one level of fan-out. A sub-agent handed "summarize this week's +readings" can decide that is still too big, split it into seven days, and invoke seven +sub-agents of its own. Depth is a property of the work, not of the topology. + +The forcing constraint is Lambda's roughly 15-minute runtime ceiling. Any unit of work +that might exceed it cannot be done inline — it has to be decomposable into pieces that +each fit, or moved off Lambda entirely (rung 5). Delegation is not primarily an elegance +argument; it is how work outgrows a single invocation without outgrowing the platform. + +One thing delegation does **not** change: the single-writer invariant from +[docs/10-concurrency.md](10-concurrency.md). Fanning out multiplies *invocations*, not +*writers*. Sub-agents do not each get their own conditional write to the shared S3 object +— thirty agents contending on one ETag is the failure mode that doc describes, not a +design. Where their results actually go is rung 4. From 47719152cbeb57775811511b537de423f1897d6e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 16:27:15 -0400 Subject: [PATCH 07/17] docs(agents): add intent-queue and follow-up/escape-hatch rungs --- docs/12-composable-agents.md | 49 ++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/docs/12-composable-agents.md b/docs/12-composable-agents.md index e9bbf15..217ddee 100644 --- a/docs/12-composable-agents.md +++ b/docs/12-composable-agents.md @@ -92,3 +92,52 @@ One thing delegation does **not** change: the single-writer invariant from *writers*. Sub-agents do not each get their own conditional write to the shared S3 object — thirty agents contending on one ETag is the failure mode that doc describes, not a design. Where their results actually go is rung 4. + +## Rung 4: intents through a queue, not direct writes + +> **Not implemented in this repo.** + +Once there is a hierarchy, the question rung 3 deferred comes due: how do a dozen +sub-agents get their results into one S3 object? Not by each conditional-writing it. That +is the contention problem — every attempt invalidating someone else's ETag until the fleet +thrashes instead of committing — and +[docs/10-concurrency.md](10-concurrency.md#high-contention-the-single-writer-queue) +already diagrams the answer. + +Reuse it exactly as written. Sub-agents become read-only: they hydrate sub-copies, do +their scoped work, and emit an **intent** — a message describing what changed, not a +rewritten file — onto a queue. One pinned coordinator, concurrency 1, drains the queue in +batches and is the only thing that touches the master object. Collisions stop being +detected and start being impossible, and the S3 write rate drops to one per batch rather +than one per agent. The costs are the ones that doc already names: writes become +asynchronous, and at-least-once delivery means the coordinator's apply step has to be +idempotent. + +This rung also gives the master object a name it will need later. Seen from inside the +hierarchy, it is the **central memory**: the one shared thing every agent's intents +eventually land in, and the only place where the system's state is authoritative. Rung 6 +is about what that memory could be shaped like. + +## Rung 5: follow-up tasks and the EC2 escape hatch + +> **Not implemented in this repo.** + +Two things break the ladder if left unaddressed, and both are answered by pieces already +on it. + +**Work that doesn't finish.** A sub-agent approaching its timeout does not retry-loop +inside Lambda, and does not silently drop what it was doing. It writes a **follow-up +item** back to the to-do list from rung 2 — "resume from here" — and exits cleanly. The +orchestrator picks it up on a later heartbeat and sequences it like any other item. +Progress is durable because it was written down, not because a process stayed alive. + +**Work that is long-running by nature.** Some tasks are not merely large: they hold a +connection open, or stream for an hour, or genuinely run past any Lambda budget you could +justify. Decomposition does not help there. The escape hatch is that rung 4's contract +does not mention Lambda anywhere — it says *consume from the queue, emit an intent*. An +EC2 instance or a Fargate task can satisfy that contract as a peer. It reads the same +queue and emits the same kind of intent, and the coordinator cannot tell, and does not +need to, which compute produced it. + +That is the payoff for making the queue the seam rather than the function: the choice of +compute becomes a per-task decision instead of an architectural one. From 354c0d0e75605025c6c8da6b0e2b7f1aff05caa6 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 16:28:08 -0400 Subject: [PATCH 08/17] docs(agents): add tiered-memory rung and closing note --- docs/12-composable-agents.md | 47 ++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/docs/12-composable-agents.md b/docs/12-composable-agents.md index 217ddee..8461cb7 100644 --- a/docs/12-composable-agents.md +++ b/docs/12-composable-agents.md @@ -141,3 +141,50 @@ need to, which compute produced it. That is the payoff for making the queue the seam rather than the function: the choice of compute becomes a per-task decision instead of an architectural one. + +## Rung 6: tiered memory, a small knowledge graph, and scoped permissions + +> **Not implemented in this repo, and the most speculative rung on the ladder.** It names +> a specific external package as an illustration of what rung 4's central memory could +> look like in a fuller form. Nothing here is a recommendation to adopt it in this +> tutorial, and no dependency is implied. + +Rung 4 named the central memory without saying what it is shaped like. In this repo it is +two flat tables. A hierarchy of agents wants more than that, and +[`@equationalapplications/core-llm-wiki`](https://github.com/equationalapplications/expo-llm-wiki/blob/main/packages/core/README.md) +— a platform-agnostic TypeScript memory engine built for hybrid LLM memory over SQLite — +happens to be organized around four things a hierarchy needs. + +**Namespacing.** Its `entityId` is the identifier a hierarchy is already missing: each +orchestrator, sub-agent, or task line reads and writes its own namespace via +`write(entityId, { event_type, summary })`. A coordinator can read across several at once, +because `read()` accepts either one entity id or an array of them. + +**Tiering.** Those namespaces can be weighted rather than merely merged. The README's own +example reads `['tier_wisdom', 'tier_fact', 'tier_working']` with `tierWeights` of `2`, +`1`, and `0.25` — durable curated knowledge dominating, an in-flight sub-agent's working +context present but nearly discounted. The tiers are just entity ids with a naming +convention and different weights, which is why the same mechanism serves both purposes. + +**A small knowledge graph.** A per-entity seeded ontology (`node_types` and `edge_types`, +under a `'strict'`, `'emergent'`, or `'off'` mode — `off` by default) lets stored facts +carry typed `edges` rather than opaque text. An intent coming back from a sub-agent can +say *this artifact was produced by that run* as a typed relationship instead of a sentence +someone has to re-parse later. + +**Scoped permissions.** Because both reads and writes are already partitioned by +`entityId`, that partition is the natural enforcement point: restrict a low-trust leaf +agent to its own namespaces, or to specific tiers within one, and it cannot read or +corrupt a sibling's memory or the orchestrator's. The permission boundary a hierarchy +needs turns out to be the same boundary the storage layer already draws. + +## Where this repo stops + +Rung 1, and nothing above it. The `fetch` tick is a real lightweight composable agent; +rungs 2 through 6 are a sketch of what it grows into, not a backlog. + +For the concrete pieces a real implementation would draw on: +[docs/10-concurrency.md](10-concurrency.md) for the single-writer queue that rungs 4 and 5 +are built on, [docs/05-from-tutorial-to-prod.md](05-from-tutorial-to-prod.md) for the +exits from SQLite-on-S3 once you need transactional consistency across agents, and +`core-llm-wiki`'s README for tiered memory, ontology, and scoped permissions. From d37c7b8129d9891179842e01754378450baed961 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 16:29:36 -0400 Subject: [PATCH 09/17] docs(agents): tighten prose into the 120-180 line target band --- docs/12-composable-agents.md | 85 ++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 47 deletions(-) diff --git a/docs/12-composable-agents.md b/docs/12-composable-agents.md index 8461cb7..f04e448 100644 --- a/docs/12-composable-agents.md +++ b/docs/12-composable-agents.md @@ -6,13 +6,12 @@ memory between runs. Whatever has to outlive one run gets written down somewhere before the agent exits. You already built one. The `fetch` tick in this repo *is* an agent of exactly that shape, -and the rest of this page is a ladder: rung 1 is the thing you have, and each rung above -it is one step toward the fuller shape this pattern takes when there is more than one -agent and more than one job. +and the rest of this page is a ladder: rung 1 is the thing you have, and each rung above it +is one step toward the fuller shape this pattern takes with more agents and more jobs. -Only rung 1 is implemented here. Everything from rung 2 up is conceptual — no code, no -tables, and no infrastructure in this repo correspond to it. Each rung says so, and points -at the existing doc that already holds the piece a real implementation would start from. +Only rung 1 is implemented here. Everything from rung 2 up is conceptual — no code, +tables, or infrastructure in this repo correspond to it — and each rung points at the +existing doc holding the piece a real implementation would start from. ## Rung 1: the fetch tick is already one @@ -28,14 +27,13 @@ no timer, no loop, and no opinion about the clock. SQLite file from S3, call Bedrock twice, post to Discord, conditional-write the file back, exit. There is no "between" for the agent to exist in. -**Statelessness between runs.** Careful here, because the honest version is more -interesting than the slogan. `/tmp` is not wiped between invocations — warm containers -reuse it, and this repo leans on that: the status reader keeps a cached database handle -across invocations precisely because `/tmp` persists, and -[docs/02-rehydration.md](02-rehydration.md) notes it survives until a redeploy. The -guarantee is not that `/tmp` is empty. The guarantee is that **correctness never depends -on what is in it.** Everything that must outlive an invocation lives in the S3 object; the -local file is a cache that a cold start is free to throw away. +**Statelessness between runs.** The honest version is more interesting than the slogan. +`/tmp` is not wiped between invocations — warm containers reuse it, and this repo leans on +that: the status reader keeps a cached database handle precisely because `/tmp` persists, +and [docs/02-rehydration.md](02-rehydration.md) notes it survives until a redeploy. The +guarantee is not that `/tmp` is empty; it is that **correctness never depends on what is +in it.** Everything that must outlive an invocation lives in the S3 object, and the local +file is a cache a cold start is free to throw away. **Why "composable."** Because no state is carried in the agent process, there is nothing to coordinate except the storage. Any number of these can exist — different schedules, @@ -52,9 +50,8 @@ introduced — it is a name for the thing already running on your schedule. > **Not implemented in this repo.** Conceptual from here on. -Today the job is hardcoded: every heartbeat does the same fetch, because the fetch *is* -the schedule's payload. An agent that only ever does one thing does not need to be told -what to do. +Today the job is hardcoded: every heartbeat does the same fetch, because the fetch *is* the +schedule's payload. An agent that only ever does one thing needs no instructions. The first generalization is to stop hardcoding it. Give the agent a **to-do list** — a table of pending work items, each with enough to decide whether it is due — and the loop @@ -65,10 +62,8 @@ The natural home for that table is the same SQLite file the agent already hydrat keeps the one-file philosophy from [docs/01-architecture.md](01-architecture.md) intact — the work queue and the work product live in the same object, committed by the same conditional write. Nothing new has to exist for this rung; it is a table and a `WHERE` -clause. - -At this rung the agent that reads the list is also the agent that does the work. That is -the constraint rung 3 removes. +clause. The agent that reads the list is still the agent that does the work — the +constraint rung 3 removes. ## Rung 3: delegation and hierarchy @@ -79,8 +74,8 @@ item, and moves on. The first agent becomes an **orchestrator**: its job is deci runs, not running it. The invoked one is a **sub-agent**. This is recursive, not one level of fan-out. A sub-agent handed "summarize this week's -readings" can decide that is still too big, split it into seven days, and invoke seven -sub-agents of its own. Depth is a property of the work, not of the topology. +readings" can split it into seven days and invoke seven sub-agents of its own. Depth is a +property of the work, not of the topology. The forcing constraint is Lambda's roughly 15-minute runtime ceiling. Any unit of work that might exceed it cannot be done inline — it has to be decomposable into pieces that @@ -89,9 +84,9 @@ argument; it is how work outgrows a single invocation without outgrowing the pla One thing delegation does **not** change: the single-writer invariant from [docs/10-concurrency.md](10-concurrency.md). Fanning out multiplies *invocations*, not -*writers*. Sub-agents do not each get their own conditional write to the shared S3 object -— thirty agents contending on one ETag is the failure mode that doc describes, not a -design. Where their results actually go is rung 4. +*writers*. Sub-agents do not each get their own conditional write — thirty agents +contending on one ETag is that doc's failure mode, not a design. Rung 4 is where their +results go. ## Rung 4: intents through a queue, not direct writes @@ -110,20 +105,18 @@ rewritten file — onto a queue. One pinned coordinator, concurrency 1, drains t batches and is the only thing that touches the master object. Collisions stop being detected and start being impossible, and the S3 write rate drops to one per batch rather than one per agent. The costs are the ones that doc already names: writes become -asynchronous, and at-least-once delivery means the coordinator's apply step has to be -idempotent. +asynchronous, and at-least-once delivery means the apply step has to be idempotent. This rung also gives the master object a name it will need later. Seen from inside the -hierarchy, it is the **central memory**: the one shared thing every agent's intents -eventually land in, and the only place where the system's state is authoritative. Rung 6 -is about what that memory could be shaped like. +hierarchy, it is the **central memory**: the one shared thing every agent's intents land +in, and the only place the system's state is authoritative. Rung 6 is about its shape. ## Rung 5: follow-up tasks and the EC2 escape hatch > **Not implemented in this repo.** -Two things break the ladder if left unaddressed, and both are answered by pieces already -on it. +Two things would break the ladder if left unaddressed, and both are answered by pieces +already on it. **Work that doesn't finish.** A sub-agent approaching its timeout does not retry-loop inside Lambda, and does not silently drop what it was doing. It writes a **follow-up @@ -161,22 +154,20 @@ orchestrator, sub-agent, or task line reads and writes its own namespace via because `read()` accepts either one entity id or an array of them. **Tiering.** Those namespaces can be weighted rather than merely merged. The README's own -example reads `['tier_wisdom', 'tier_fact', 'tier_working']` with `tierWeights` of `2`, -`1`, and `0.25` — durable curated knowledge dominating, an in-flight sub-agent's working -context present but nearly discounted. The tiers are just entity ids with a naming -convention and different weights, which is why the same mechanism serves both purposes. +example reads `['tier_wisdom', 'tier_fact', 'tier_working']` with `tierWeights` of `2`, `1`, +and `0.25` — durable curated knowledge dominating, an in-flight sub-agent's working context +present but nearly discounted. Tiers are just entity ids with a naming convention. **A small knowledge graph.** A per-entity seeded ontology (`node_types` and `edge_types`, under a `'strict'`, `'emergent'`, or `'off'` mode — `off` by default) lets stored facts -carry typed `edges` rather than opaque text. An intent coming back from a sub-agent can -say *this artifact was produced by that run* as a typed relationship instead of a sentence -someone has to re-parse later. +carry typed `edges` rather than opaque text. An intent coming back from a sub-agent can say +*this artifact was produced by that run* as a relationship, not a sentence to re-parse. **Scoped permissions.** Because both reads and writes are already partitioned by `entityId`, that partition is the natural enforcement point: restrict a low-trust leaf -agent to its own namespaces, or to specific tiers within one, and it cannot read or -corrupt a sibling's memory or the orchestrator's. The permission boundary a hierarchy -needs turns out to be the same boundary the storage layer already draws. +agent to its own namespaces, or to specific tiers within one, and it cannot read or corrupt +a sibling's memory or the orchestrator's. The permission boundary a hierarchy needs is the +one the storage layer already draws. ## Where this repo stops @@ -184,7 +175,7 @@ Rung 1, and nothing above it. The `fetch` tick is a real lightweight composable rungs 2 through 6 are a sketch of what it grows into, not a backlog. For the concrete pieces a real implementation would draw on: -[docs/10-concurrency.md](10-concurrency.md) for the single-writer queue that rungs 4 and 5 -are built on, [docs/05-from-tutorial-to-prod.md](05-from-tutorial-to-prod.md) for the -exits from SQLite-on-S3 once you need transactional consistency across agents, and -`core-llm-wiki`'s README for tiered memory, ontology, and scoped permissions. +[docs/10-concurrency.md](10-concurrency.md) for the single-writer queue rungs 4 and 5 are +built on, [docs/05-from-tutorial-to-prod.md](05-from-tutorial-to-prod.md) for the exits from +SQLite-on-S3 once you need transactional consistency across agents, and `core-llm-wiki`'s +README for tiered memory, ontology, and scoped permissions. From 33affcea50ad89d3749172064fb4c79c279bad84 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 16:30:12 -0400 Subject: [PATCH 10/17] docs(agents): trim to the 180-line ceiling --- docs/12-composable-agents.md | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/docs/12-composable-agents.md b/docs/12-composable-agents.md index f04e448..e046ffe 100644 --- a/docs/12-composable-agents.md +++ b/docs/12-composable-agents.md @@ -130,10 +130,8 @@ justify. Decomposition does not help there. The escape hatch is that rung 4's co does not mention Lambda anywhere — it says *consume from the queue, emit an intent*. An EC2 instance or a Fargate task can satisfy that contract as a peer. It reads the same queue and emits the same kind of intent, and the coordinator cannot tell, and does not -need to, which compute produced it. - -That is the payoff for making the queue the seam rather than the function: the choice of -compute becomes a per-task decision instead of an architectural one. +need to, which compute produced it. That is the payoff for making the queue the seam +rather than the function: compute becomes a per-task choice, not an architectural one. ## Rung 6: tiered memory, a small knowledge graph, and scoped permissions @@ -142,21 +140,21 @@ compute becomes a per-task decision instead of an architectural one. > look like in a fuller form. Nothing here is a recommendation to adopt it in this > tutorial, and no dependency is implied. -Rung 4 named the central memory without saying what it is shaped like. In this repo it is -two flat tables. A hierarchy of agents wants more than that, and +Rung 4 named the central memory without saying what it is shaped like; in this repo it is +two flat tables. A hierarchy wants more than that, and [`@equationalapplications/core-llm-wiki`](https://github.com/equationalapplications/expo-llm-wiki/blob/main/packages/core/README.md) — a platform-agnostic TypeScript memory engine built for hybrid LLM memory over SQLite — happens to be organized around four things a hierarchy needs. **Namespacing.** Its `entityId` is the identifier a hierarchy is already missing: each orchestrator, sub-agent, or task line reads and writes its own namespace via -`write(entityId, { event_type, summary })`. A coordinator can read across several at once, -because `read()` accepts either one entity id or an array of them. +`write(entityId, { event_type, summary })`, and a coordinator can read across several at +once, because `read()` accepts one entity id or an array of them. **Tiering.** Those namespaces can be weighted rather than merely merged. The README's own -example reads `['tier_wisdom', 'tier_fact', 'tier_working']` with `tierWeights` of `2`, `1`, -and `0.25` — durable curated knowledge dominating, an in-flight sub-agent's working context -present but nearly discounted. Tiers are just entity ids with a naming convention. +example reads `['tier_wisdom', 'tier_fact', 'tier_working']` with `tierWeights` of `2`, +`1`, and `0.25` — durable curated knowledge dominating, an in-flight sub-agent's working +context nearly discounted. Tiers are just entity ids with a naming convention. **A small knowledge graph.** A per-entity seeded ontology (`node_types` and `edge_types`, under a `'strict'`, `'emergent'`, or `'off'` mode — `off` by default) lets stored facts From 2cf54597c5d63597d8c080550dcf1b62e27ce1a4 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 16:30:34 -0400 Subject: [PATCH 11/17] docs: add composable agents to the doc index --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9ff0ab4..d5b51e1 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,7 @@ multi-writer escape hatch it looks like. | [docs/09-lesson-script.md](docs/09-lesson-script.md) | A 10-lesson script for teaching the RAG extension (frame, check-in questions, expected reasoning) | | [docs/10-concurrency.md](docs/10-concurrency.md) | Optimistic S3 rehydration, 412 handling, rebase-and-retry, the single-writer queue | | [docs/11-aws-bedrock-setup.md](docs/11-aws-bedrock-setup.md) | Account type, deployer IAM, Marketplace subscription, Region, EULA, first-deploy smoke | +| [docs/12-composable-agents.md](docs/12-composable-agents.md) | The fetch tick as a lightweight composable agent, and the ladder up from it | | [docs/bedrock-model-comparison.md](docs/bedrock-model-comparison.md) | Why `zai.glm-4.7-flash` is the default, and alternatives | ## Cost From 69d92186432fe20a508e47b02ed60cf8ec321d0a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 16:30:55 -0400 Subject: [PATCH 12/17] docs(spec): mark composable agents design implemented --- docs/superpowers/specs/2026-08-11-composable-agents-design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/superpowers/specs/2026-08-11-composable-agents-design.md b/docs/superpowers/specs/2026-08-11-composable-agents-design.md index e7351ed..82ea027 100644 --- a/docs/superpowers/specs/2026-08-11-composable-agents-design.md +++ b/docs/superpowers/specs/2026-08-11-composable-agents-design.md @@ -2,7 +2,7 @@ ## Status -Approved — ready for writing-plans. +Implemented — `docs/12-composable-agents.md`. ## Summary From f85e4ca5a74cd03f413b927fdafa50cee4bcc8ef Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 16:34:57 -0400 Subject: [PATCH 13/17] =?UTF-8?q?docs(agents):=20address=20quality=20revie?= =?UTF-8?q?w=20=E2=80=94=20table=20count,=20ETag=20fleet,=20layering,=20es?= =?UTF-8?q?cape-hatch=20clarity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/12-composable-agents.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/12-composable-agents.md b/docs/12-composable-agents.md index e046ffe..0322e1a 100644 --- a/docs/12-composable-agents.md +++ b/docs/12-composable-agents.md @@ -1,8 +1,8 @@ # Composable agents -A **lightweight, composable cloud agent** is a thing that spins up, does a few minutes — -or a few seconds — of work, and goes away. No process stays resident. No state is held in -memory between runs. Whatever has to outlive one run gets written down somewhere durable +A **lightweight, composable cloud agent** spins up, works for a few minutes — or a few +seconds — and goes away. No process stays resident. No state is held in memory between +runs. Whatever has to outlive one run gets written down somewhere durable before the agent exits. You already built one. The `fetch` tick in this repo *is* an agent of exactly that shape, @@ -38,10 +38,10 @@ file is a cache a cold start is free to throw away. **Why "composable."** Because no state is carried in the agent process, there is nothing to coordinate except the storage. Any number of these can exist — different schedules, different triggers, different jobs — provided they agree on the storage contract. This -repo's contract is the conditional write on the S3 object, guarded by -`reservedConcurrentExecutions: 1` and described in -[docs/10-concurrency.md](10-concurrency.md). That contract, not any shared runtime, is -what makes a second agent possible. +repo's contract is the conditional write on the S3 object, backed at the platform layer by +`reservedConcurrentExecutions: 1` — belt and suspenders, as +[docs/10-concurrency.md](10-concurrency.md) puts it. That contract, not any shared runtime, +is what makes a second agent possible. This is the load-bearing rung. "Lightweight cloud agent" is not an abstraction being introduced — it is a name for the thing already running on your schedule. @@ -84,9 +84,8 @@ argument; it is how work outgrows a single invocation without outgrowing the pla One thing delegation does **not** change: the single-writer invariant from [docs/10-concurrency.md](10-concurrency.md). Fanning out multiplies *invocations*, not -*writers*. Sub-agents do not each get their own conditional write — thirty agents -contending on one ETag is that doc's failure mode, not a design. Rung 4 is where their -results go. +*writers*. Sub-agents do not each get their own conditional write — a fleet contending on +one ETag is that doc's failure mode, not a design. Rung 4 is where their results go. ## Rung 4: intents through a queue, not direct writes @@ -127,8 +126,9 @@ Progress is durable because it was written down, not because a process stayed al **Work that is long-running by nature.** Some tasks are not merely large: they hold a connection open, or stream for an hour, or genuinely run past any Lambda budget you could justify. Decomposition does not help there. The escape hatch is that rung 4's contract -does not mention Lambda anywhere — it says *consume from the queue, emit an intent*. An -EC2 instance or a Fargate task can satisfy that contract as a peer. It reads the same +never mentions Lambda — [docs/10-concurrency.md](10-concurrency.md) happens to draw it +with Lambdas, but the contract says only *consume from the queue, emit an intent*. An EC2 +instance or a Fargate task can satisfy it as a peer. It reads the same queue and emits the same kind of intent, and the coordinator cannot tell, and does not need to, which compute produced it. That is the payoff for making the queue the seam rather than the function: compute becomes a per-task choice, not an architectural one. @@ -140,8 +140,8 @@ rather than the function: compute becomes a per-task choice, not an architectura > look like in a fuller form. Nothing here is a recommendation to adopt it in this > tutorial, and no dependency is implied. -Rung 4 named the central memory without saying what it is shaped like; in this repo it is -two flat tables. A hierarchy wants more than that, and +Rung 4 named the central memory without saying what it is shaped like; here it is four +tables, one of them a `sqlite-vec` index. A hierarchy wants more than that, and [`@equationalapplications/core-llm-wiki`](https://github.com/equationalapplications/expo-llm-wiki/blob/main/packages/core/README.md) — a platform-agnostic TypeScript memory engine built for hybrid LLM memory over SQLite — happens to be organized around four things a hierarchy needs. From 4571c03321b922813badf0ddaa2ba77c7f445c80 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 16:35:58 -0400 Subject: [PATCH 14/17] docs(agents): fix belt-and-suspenders attribution, reflow two paragraphs --- docs/12-composable-agents.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/12-composable-agents.md b/docs/12-composable-agents.md index 0322e1a..aeeb0b7 100644 --- a/docs/12-composable-agents.md +++ b/docs/12-composable-agents.md @@ -2,8 +2,8 @@ A **lightweight, composable cloud agent** spins up, works for a few minutes — or a few seconds — and goes away. No process stays resident. No state is held in memory between -runs. Whatever has to outlive one run gets written down somewhere durable -before the agent exits. +runs. Whatever has to outlive one run gets written down somewhere durable before the agent +exits. You already built one. The `fetch` tick in this repo *is* an agent of exactly that shape, and the rest of this page is a ladder: rung 1 is the thing you have, and each rung above it @@ -38,10 +38,11 @@ file is a cache a cold start is free to throw away. **Why "composable."** Because no state is carried in the agent process, there is nothing to coordinate except the storage. Any number of these can exist — different schedules, different triggers, different jobs — provided they agree on the storage contract. This -repo's contract is the conditional write on the S3 object, backed at the platform layer by +repo's contract is the conditional write on the S3 object described in +[docs/10-concurrency.md](10-concurrency.md), backed at the platform layer by `reservedConcurrentExecutions: 1` — belt and suspenders, as -[docs/10-concurrency.md](10-concurrency.md) puts it. That contract, not any shared runtime, -is what makes a second agent possible. +[docs/01-architecture.md](01-architecture.md) puts it. That contract, not any shared +runtime, is what makes a second agent possible. This is the load-bearing rung. "Lightweight cloud agent" is not an abstraction being introduced — it is a name for the thing already running on your schedule. @@ -128,9 +129,9 @@ connection open, or stream for an hour, or genuinely run past any Lambda budget justify. Decomposition does not help there. The escape hatch is that rung 4's contract never mentions Lambda — [docs/10-concurrency.md](10-concurrency.md) happens to draw it with Lambdas, but the contract says only *consume from the queue, emit an intent*. An EC2 -instance or a Fargate task can satisfy it as a peer. It reads the same -queue and emits the same kind of intent, and the coordinator cannot tell, and does not -need to, which compute produced it. That is the payoff for making the queue the seam +instance or a Fargate task can satisfy it as a peer: it reads the same queue and emits the +same kind of intent, and the coordinator cannot tell, and does not need to, which compute +produced it. That is the payoff for making the queue the seam rather than the function: compute becomes a per-task choice, not an architectural one. ## Rung 6: tiered memory, a small knowledge graph, and scoped permissions From bde54196c0e089de91cd99a6528be73943a563bc Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 16:48:19 -0400 Subject: [PATCH 15/17] docs(agents): address CodeRabbit review on composable-agents PR Six actionable findings plus one nitpick, all docs-only: - Statelessness: opener now reads "carries no state in memory that has to outlive an invocation" instead of the absolute "no state is held in memory between runs"; aligned across docs/12, the spec, and the plan. - Composability: "any number of these can exist" replaced with the storage- only guarantee. Documented that the conditional write does not serialize separate Lambda functions or make pre-publish Bedrock/Discord side effects idempotent, and that reservedConcurrentExecutions: 1 is the tutorial default, function-scoped and overridable via RESERVED_CONCURRENCY. - Rung 5 follow-ups: sub-agent now emits an intent and the coordinator applies it to create the to-do item, matching Rung 4's read-only sub-agent contract. - Spec Rung 5: "Closes the ladder" replaced with "handles work that exceeds a single invocation; rung 6 follows immediately." - Tiered memory: tier_wisdom / tier_fact / tier_working reframed as ordinary entity ids with naming-convention tiers; tierWeights described as a per-entity multiplier. - Plan "Not touched" rule: explicit exception added for the one-line Status flip this plan makes in Task 6. - Spec "Verify before drafting" block: replaced with a 2026-08-11 verification record so an Implemented spec no longer reads as unconfirmed. Co-Authored-By: Claude --- docs/12-composable-agents.md | 38 ++++++---- .../plans/2026-08-11-composable-agents-doc.md | 20 ++--- .../2026-08-11-composable-agents-design.md | 75 ++++++++++--------- 3 files changed, 75 insertions(+), 58 deletions(-) diff --git a/docs/12-composable-agents.md b/docs/12-composable-agents.md index aeeb0b7..676d122 100644 --- a/docs/12-composable-agents.md +++ b/docs/12-composable-agents.md @@ -1,9 +1,9 @@ # Composable agents A **lightweight, composable cloud agent** spins up, works for a few minutes — or a few -seconds — and goes away. No process stays resident. No state is held in memory between -runs. Whatever has to outlive one run gets written down somewhere durable before the agent -exits. +seconds — and goes away. No process stays resident. The agent carries no state in memory +that has to outlive an invocation; whatever has to outlive one run gets written down +somewhere durable before the agent exits. You already built one. The `fetch` tick in this repo *is* an agent of exactly that shape, and the rest of this page is a ladder: rung 1 is the thing you have, and each rung above it @@ -35,14 +35,20 @@ guarantee is not that `/tmp` is empty; it is that **correctness never depends on in it.** Everything that must outlive an invocation lives in the S3 object, and the local file is a cache a cold start is free to throw away. -**Why "composable."** Because no state is carried in the agent process, there is nothing -to coordinate except the storage. Any number of these can exist — different schedules, -different triggers, different jobs — provided they agree on the storage contract. This -repo's contract is the conditional write on the S3 object described in -[docs/10-concurrency.md](10-concurrency.md), backed at the platform layer by -`reservedConcurrentExecutions: 1` — belt and suspenders, as -[docs/01-architecture.md](01-architecture.md) puts it. That contract, not any shared -runtime, is what makes a second agent possible. +**Why "composable."** Because no durable state is carried in the agent process, there is +nothing to coordinate except the storage. The conditional write on the S3 object described +in [docs/10-concurrency.md](10-concurrency.md) is what makes a second agent possible — +without it, two invocations landing at once would silently clobber each other. + +That protection is scoped to the S3 object. The conditional write stops the file from +being corrupted; it does not serialize separate Lambda functions, and the pre-publish +Bedrock calls and Discord posts are not made idempotent by it. Two agents running the same +job at once can still post the same Discord message twice before either write commits — +that is the duplicate-side-effect hazard rung 4's queue exists to eliminate. + +This repo's `reservedConcurrentExecutions: 1` is the tutorial default, function-scoped and +overridable via `RESERVED_CONCURRENCY`. It is a second line of defense, not a general +multi-agent coordination primitive. This is the load-bearing rung. "Lightweight cloud agent" is not an abstraction being introduced — it is a name for the thing already running on your schedule. @@ -119,10 +125,12 @@ Two things would break the ladder if left unaddressed, and both are answered by already on it. **Work that doesn't finish.** A sub-agent approaching its timeout does not retry-loop -inside Lambda, and does not silently drop what it was doing. It writes a **follow-up -item** back to the to-do list from rung 2 — "resume from here" — and exits cleanly. The -orchestrator picks it up on a later heartbeat and sequences it like any other item. -Progress is durable because it was written down, not because a process stayed alive. +inside Lambda, and does not silently drop what it was doing. It emits an **intent** +describing a **follow-up item** for the to-do list from rung 2 — "resume from here" — +and exits cleanly. The coordinator applies the intent on its next drain, the follow-up +shows up in the to-do list like any other item, and the orchestrator picks it up on a +later heartbeat. Progress is durable because it was written down through the same queue +rung 4 already defines, not because a process stayed alive. **Work that is long-running by nature.** Some tasks are not merely large: they hold a connection open, or stream for an hour, or genuinely run past any Lambda budget you could diff --git a/docs/superpowers/plans/2026-08-11-composable-agents-doc.md b/docs/superpowers/plans/2026-08-11-composable-agents-doc.md index 0a786c2..e276ca1 100644 --- a/docs/superpowers/plans/2026-08-11-composable-agents-doc.md +++ b/docs/superpowers/plans/2026-08-11-composable-agents-doc.md @@ -29,7 +29,7 @@ Facts verified against this repo — do not restate them differently: | The schedule's EventBridge `Input` is the literal string `{"op":"fetch"}` | `docs/01-architecture.md:66-73` | | The function has two ops read from `event.op`: `fetch` (writer, scheduled) and `status` (reader, Function URL with `authType: AWS_IAM`) | `docs/01-architecture.md:3-9` | | Warm invocations share `/tmp`; the status reader deliberately reuses its cached handle, and `/tmp` survives until a redeploy | `docs/01-architecture.md:10-12`, `docs/02-rehydration.md:89-91` | -| `reservedConcurrentExecutions: 1` is the single-writer invariant; the conditional write is a second line of defense | `docs/01-architecture.md:44-52` | +| `reservedConcurrentExecutions: 1` is the tutorial default for this one Lambda function (overridable via `RESERVED_CONCURRENCY`); the conditional write is a second line of defense scoped to the S3 object | `docs/01-architecture.md:44-52` | | The high-contention answer is: read-only agents, writes serialized as SQS messages, one pinned coordinator with concurrency 1, batched apply | `docs/10-concurrency.md:107-142` | | Docs 10 already uses the words **intent** and **coordinator** for exactly these roles | `docs/10-concurrency.md:131-134` | | The README doc index table runs `docs/01…docs/11`, then `bedrock-model-comparison.md` last as the only unnumbered row | `README.md:176-187` | @@ -56,7 +56,7 @@ The drafted prose in Task 4 already reflects this. Write it as drafted; do not " - **Create** `docs/12-composable-agents.md` — the only new file. Built over Tasks 1–4, one commit per section group, so every commit leaves a readable document. Target 120–180 lines total; the drafted content lands at roughly 155. - **Modify** `README.md` — insert one row in the doc index table, immediately above the `bedrock-model-comparison.md` row. Task 5. -Not touched: every other file in the repo, including the other files in `docs/`. Historical records under `docs/superpowers/plans/` and `docs/superpowers/specs/` are never rewritten. +Not touched: every other file in the repo, including the other files in `docs/`. Historical records under `docs/superpowers/plans/` and `docs/superpowers/specs/` are never rewritten, with one exception: the single `## Status` line of this approved spec flips from `Approved` to `Implemented` in Task 6, and that one-line edit is the only change made to a spec file by this plan. --- @@ -90,9 +90,9 @@ Create `docs/12-composable-agents.md` with exactly this content: # Composable agents A **lightweight, composable cloud agent** is a thing that spins up, does a few minutes — -or a few seconds — of work, and goes away. No process stays resident. No state is held in -memory between runs. Whatever has to outlive one run gets written down somewhere durable -before the agent exits. +or a few seconds — of work, and goes away. No process stays resident. The agent carries +no state in memory that has to outlive an invocation; whatever has to outlive one run +gets written down somewhere durable before the agent exits. You already built one. The `fetch` tick in this repo *is* an agent of exactly that shape, and the rest of this page is a ladder: rung 1 is the thing you have, and each rung above @@ -277,10 +277,12 @@ Two things break the ladder if left unaddressed, and both are answered by pieces on it. **Work that doesn't finish.** A sub-agent approaching its timeout does not retry-loop -inside Lambda, and does not silently drop what it was doing. It writes a **follow-up -item** back to the to-do list from rung 2 — "resume from here" — and exits cleanly. The -orchestrator picks it up on a later heartbeat and sequences it like any other item. -Progress is durable because it was written down, not because a process stayed alive. +inside Lambda, and does not silently drop what it was doing. It emits an **intent** +describing a **follow-up item** for the to-do list from rung 2 — "resume from here" — +and exits cleanly. The coordinator applies the intent on its next drain, the follow-up +shows up in the to-do list like any other item, and the orchestrator picks it up on a +later heartbeat. Progress is durable because it was written down through the same queue +rung 4 already defines, not because a process stayed alive. **Work that is long-running by nature.** Some tasks are not merely large: they hold a connection open, or stream for an hour, or genuinely run past any Lambda budget you could diff --git a/docs/superpowers/specs/2026-08-11-composable-agents-design.md b/docs/superpowers/specs/2026-08-11-composable-agents-design.md index 82ea027..71a2a22 100644 --- a/docs/superpowers/specs/2026-08-11-composable-agents-design.md +++ b/docs/superpowers/specs/2026-08-11-composable-agents-design.md @@ -70,8 +70,8 @@ already contain the pieces a real implementation would draw on. Opens by naming the pattern in the first paragraph: a lightweight, composable cloud agent is something that spins up, does a few minutes of work, and goes -away — no persistent process, no long-lived state held in memory. States -immediately that this tutorial already built one. The rest of the page is a +away — no persistent process, and no state in memory that correctness depends on +holding. States immediately that this tutorial already built one. The rest of the page is a ladder from that one concrete instance up to the general pattern. ### 2. Rung 1 — the fetch tick as a lightweight agent @@ -90,11 +90,13 @@ introduced: outlive an invocation lives in the S3-backed SQLite file, not in the agent process. State the nuance rather than claiming `/tmp` is simply disposable; a reader who has finished doc 01 will know better. -- **Why this is "composable"**: because the agent carries no in-memory state - across invocations, any number of these can exist — different schedules, - different triggers — as long as they agree on the storage contract, which is - exactly what the conditional-write invariant in `10-concurrency.md` already - provides. +- **Why this is "composable"**: because the agent carries no durable in-memory + state across invocations, the conditional-write invariant in `10-concurrency.md` + is what makes a second agent possible — it prevents two invocations from + silently clobbering the S3 object. That protection is scoped to the S3 object: + it does not serialize separate Lambda functions, and the pre-publish Bedrock + calls and Discord posts are not made idempotent by it. The single-writer queue + (rung 4) is the seam that does both. This rung is the load-bearing one: it makes the doc's central claim concrete before generalizing. "Lightweight cloud agent" is not a new abstraction being @@ -142,15 +144,17 @@ has to be named here rather than appearing for the first time later. ### 6. Rung 5 — follow-up tasks and the EC2 escape hatch -Closes the ladder. When a sub-agent's work doesn't finish within its own -invocation, it doesn't retry-loop inside Lambda — it writes a follow-up item -back to the to-do list (rung 2's table) for the orchestrator to pick up and -sequence on a later heartbeat. When a task's *nature* is long-running rather -than just large — something that must hold a connection open, or genuinely -runs past any reasonable Lambda budget — the same message-queue contract from -rung 4 lets an EC2 or Fargate worker participate as a peer: it consumes from -the same queue and emits the same kind of intent. The orchestrator doesn't -need to know or care which compute produced it. +Handles work that exceeds a single invocation; rung 6 follows immediately. +When a sub-agent's work doesn't finish within its own invocation, it doesn't +retry-loop inside Lambda — it emits an intent describing a follow-up item for +the to-do list (rung 2's table). The coordinator applies the intent on its +next drain, and the orchestrator picks the item up on a later heartbeat. When +a task's *nature* is long-running rather than just large — something that must +hold a connection open, or genuinely runs past any reasonable Lambda budget +— the same message-queue contract from rung 4 lets an EC2 or Fargate worker +participate as a peer: it consumes from the same queue and emits the same +kind of intent. The orchestrator doesn't need to know or care which compute +produced it. ### 7. Rung 6 — tiered memory, a basic knowledge graph, and scoped permissions @@ -161,15 +165,17 @@ answer: [`@equationalapplications/core-llm-wiki`](https://github.com/equationala TypeScript memory engine already built for hybrid LLM memory over SQLite. The mapping is conceptual, not a dependency this repo takes on: -- **Multi-agent namespacing.** `WikiMemory`'s `entityId` is exactly the - identifier a hierarchy of agents needs: each orchestrator, sub-agent, or - task line could read/write its own `entityId` namespace, or a coordinator - could read across several in one call (`read([entityIdA, entityIdB], …)`). -- **Tiered memory.** `tierWeights` (e.g. `tier_wisdom`, `tier_fact`, - `tier_working`) gives the "central memory" from rung 4 actual tiers — - durable curated knowledge weighted high, working/session-scoped context - from an in-flight sub-agent weighted low or excluded — instead of one flat - fact table. +- **Multi-agent namespacing.** `WikiMemory`'s `entityId` is the identifier a + hierarchy of agents needs: each orchestrator, sub-agent, or task line + reads/writes its own `entityId` namespace, or a coordinator reads across + several in one call (`read([entityIdA, entityIdB], …)`). +- **Tiered memory.** `tierWeights` is a per-entity multiplier applied to + retrieval scores — a hierarchy can group its entity ids by convention + (e.g. `tier_wisdom` for durable curated knowledge, `tier_fact` for + established facts, `tier_working` for in-flight sub-agent context) and + weight each group as a "tier" without any of those names being built in. + `tier_wisdom` / `tier_fact` / `tier_working` are ordinary entity ids; the + tiers they denote live in the naming convention and the weight map. - **Basic knowledge graph.** The per-entity seeded ontology (`strict` / `emergent` / `off` modes, `node_types`/`edge_types`, typed facts with inline `edges`) is a lightweight graph layer: intents coming back from @@ -187,15 +193,16 @@ package as an illustration of what the central memory from rung 4 could look like in a fuller form, not a recommendation to adopt it in this tutorial. No code or dependency changes are implied. -**Verify before drafting.** This rung cites a lot of external API surface -(`WikiMemory`, `entityId`, `tierWeights`, the `tier_wisdom`/`tier_fact`/ -`tier_working` tiers, `node_types`/`edge_types`, the `strict`/`emergent`/`off` -ontology modes, the `read([entityIdA, entityIdB], …)` signature), and the link -points at repo `expo-llm-wiki` while the package is named -`@equationalapplications/core-llm-wiki` — plausible for a monorepo, but -unconfirmed. Fetch that README first and confirm both the URL and every cited -name. This is the section most likely to have rotted; drop or soften any -specific that no longer matches rather than guessing. +**Verification (2026-08-11).** All external API surface cited above — +`WikiMemory`, `entityId`, `tierWeights`, the `tier_wisdom` / `tier_fact` / +`tier_working` namespaces, `node_types` / `edge_types`, the `strict` / +`emergent` / `off` ontology modes, the `read([entityIdA, entityIdB], …)` +signature — and the repo / package URL (`equationalapplications/expo-llm-wiki`, +`@equationalapplications/core-llm-wiki`) were verified against the upstream +README on 2026-08-11, prior to drafting. The implementation plan also records +the corrected entity-id semantics: `tier_wisdom` / `tier_fact` / `tier_working` +are ordinary entity ids, with `tierWeights` assigning each a weight. Re-verify +before relying on any specific that may have rotted since. ### 8. Closing note From c0856a217a9cf50d6e5acd80079c8f88601262e2 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 16:49:31 -0400 Subject: [PATCH 16/17] =?UTF-8?q?docs(agents):=20generalize=20Thread=202?= =?UTF-8?q?=20wording=20=E2=80=94=20drop=20Discord/Bedrock=20specifics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per a follow-up note that the doc is conceptual, not about a Discord bot. My new "Why composable" paragraph in docs/12 and the matching spec text spoke in terms of "Bedrock calls and Discord posts"; replaced with general language about side-effecting operations. Pre-existing rung 1 paragraph still mentions Bedrock/Discord as concrete examples of the fetch tick — left in place; will check with the author on whether those should be generalized too. Co-Authored-By: Claude --- docs/12-composable-agents.md | 9 +++++---- .../specs/2026-08-11-composable-agents-design.md | 6 +++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/12-composable-agents.md b/docs/12-composable-agents.md index 676d122..05bdf55 100644 --- a/docs/12-composable-agents.md +++ b/docs/12-composable-agents.md @@ -41,10 +41,11 @@ in [docs/10-concurrency.md](10-concurrency.md) is what makes a second agent poss without it, two invocations landing at once would silently clobber each other. That protection is scoped to the S3 object. The conditional write stops the file from -being corrupted; it does not serialize separate Lambda functions, and the pre-publish -Bedrock calls and Discord posts are not made idempotent by it. Two agents running the same -job at once can still post the same Discord message twice before either write commits — -that is the duplicate-side-effect hazard rung 4's queue exists to eliminate. +being corrupted; it does not serialize separate Lambda functions, and any side-effecting +operations the agent performs before the write — outbound notifications, downstream API +calls — are not made idempotent by it. Two agents running the same job at once can still +fire those side effects twice before either write commits. That is the duplicate-side- +effect hazard rung 4's queue exists to eliminate. This repo's `reservedConcurrentExecutions: 1` is the tutorial default, function-scoped and overridable via `RESERVED_CONCURRENCY`. It is a second line of defense, not a general diff --git a/docs/superpowers/specs/2026-08-11-composable-agents-design.md b/docs/superpowers/specs/2026-08-11-composable-agents-design.md index 71a2a22..a556770 100644 --- a/docs/superpowers/specs/2026-08-11-composable-agents-design.md +++ b/docs/superpowers/specs/2026-08-11-composable-agents-design.md @@ -94,9 +94,9 @@ introduced: state across invocations, the conditional-write invariant in `10-concurrency.md` is what makes a second agent possible — it prevents two invocations from silently clobbering the S3 object. That protection is scoped to the S3 object: - it does not serialize separate Lambda functions, and the pre-publish Bedrock - calls and Discord posts are not made idempotent by it. The single-writer queue - (rung 4) is the seam that does both. + it does not serialize separate Lambda functions, and any side-effecting + operations the agent performs before the write are not made idempotent by it. + The single-writer queue (rung 4) is the seam that does both. This rung is the load-bearing one: it makes the doc's central claim concrete before generalizing. "Lightweight cloud agent" is not a new abstraction being From 0ec1f9d2a7f2372a19f33bdacf3369d62dd6d933 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 16:50:10 -0400 Subject: [PATCH 17/17] =?UTF-8?q?docs(agents):=20generalize=20rung=201=20?= =?UTF-8?q?=E2=80=94=20drop=20Bedrock/Discord=20from=20concrete=20example?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per follow-up that the doc is conceptual, not about a Discord bot. Rung 1's "Spin up, work, go away" paragraph and the spec's matching description referred to "call Bedrock twice, post to Discord" specifically; replaced with "do the agent's scoped work". The doc and spec no longer name a specific external service anywhere; the conceptual pattern stands on its own. Co-Authored-By: Claude --- docs/12-composable-agents.md | 4 ++-- docs/superpowers/specs/2026-08-11-composable-agents-design.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/12-composable-agents.md b/docs/12-composable-agents.md index 05bdf55..0d12bd6 100644 --- a/docs/12-composable-agents.md +++ b/docs/12-composable-agents.md @@ -24,8 +24,8 @@ page uses. no timer, no loop, and no opinion about the clock. **Spin up, work, go away.** One Lambda invocation is the entire lifetime: hydrate the -SQLite file from S3, call Bedrock twice, post to Discord, conditional-write the file back, -exit. There is no "between" for the agent to exist in. +SQLite file from S3, do the agent's scoped work, conditional-write the file back, exit. +There is no "between" for the agent to exist in. **Statelessness between runs.** The honest version is more interesting than the slogan. `/tmp` is not wiped between invocations — warm containers reuse it, and this repo leans on diff --git a/docs/superpowers/specs/2026-08-11-composable-agents-design.md b/docs/superpowers/specs/2026-08-11-composable-agents-design.md index a556770..91aea60 100644 --- a/docs/superpowers/specs/2026-08-11-composable-agents-design.md +++ b/docs/superpowers/specs/2026-08-11-composable-agents-design.md @@ -81,8 +81,8 @@ introduced: - **Heartbeat** = the EventBridge 5-minute schedule described in `01-architecture.md` (`{"op":"fetch"}` as the literal `Input`). -- **Spin up, work, go away** = one Lambda invocation: hydrate from S3, call - Bedrock twice, post to Discord, conditional-write back, exit. +- **Spin up, work, go away** = one Lambda invocation: hydrate from S3, do + the agent's scoped work, conditional-write back, exit. - **Statelessness between runs** = no *durable* state is held in `/tmp`. Warm containers may reuse it — `01-architecture.md` leans on exactly that to let the status reader work, and `02-rehydration.md` notes it survives until a