Remember things
Store rows that survive restarts and read them back.
State is a set of typed, keyed collections the extension owns. Shiitake stores
them durably next to the Session, so they survive a restart. This page turns
bookmarks.check into bookmarks.add, which saves the link, and lists the
saved links in the prompt.
Declare a collection
Section titled “Declare a collection”import { Mycelium } from "@fungi.computer/mycelium";import { defineCollection, defineExtension,} from "@fungi.computer/shiitake/extensions";import * as Schema from "effect/Schema";
const Link = Schema.Struct({ url: Schema.String, title: Schema.String });const Bookmark = Schema.Struct({ ...Link.fields, sessionId: Schema.String });
/** The model saves links; the next prompt lists what this Session saved. */export const bookmarks = defineExtension("bookmarks") .state({ bookmarks: defineCollection(Bookmark, { key: (b) => JSON.stringify([b.sessionId, b.url]), index: (b) => b.sessionId, }), }) .define({ prompt: (ctx, run) => { const saved = ctx.state.read((s) => s.bookmarks.page({ index: run.sessionId, limit: 32 }), ); const titles = saved.map((b) => b.title).join(", "); return [{ name: "saved", text: `Saved links: ${titles || "none"}` }]; }, tools: (ctx, run) => Mycelium.module({ id: "example.bookmarks", name: "bookmarks", operations: { add: Mycelium.operation({ replay: "safe", description: "Save a link; saving the same URL replaces it.", input: Link, execute: (link) => Promise.resolve( ctx.state.update((s) => { s.bookmarks.put({ ...link, sessionId: run.sessionId }); return null; }), ), }), }, }), });.state({ bookmarks: defineCollection(...) }) declares one collection. Its
schema is the only decoder for stored rows. key names a row; saving the same
key again replaces it. index groups rows so the prompt can read one Session’s
links without scanning everyone’s.
Write and read
Section titled “Write and read”The tool writes through ctx.state.update, one short synchronous transaction.
The prompt reads through ctx.state.read. Both are plain functions: no promises
inside, and no network calls while the transaction is open.
Run it
Section titled “Run it”import { callTool, echoSystemPrompt, memoryHost,} from "@fungi.computer/shiitake/testing";import { bookmarks } from "./extension.js";
/** The model saves a link; its next request shows the saved list. */export async function storedStateRun() { const host = await memoryHost({ extensions: [bookmarks], script: [ callTool("bookmarks.add", { url: "https://shiitake.shiit.app/", title: "Shiitake docs", }), echoSystemPrompt(), ], }); try { await host.agent .session("reading") .send("Save the Shiitake docs.", { requestId: "save" }); return await host.settle("reading"); } finally { await host.close(); }}import { storedStateRun } from "./run.js";
const snapshot = await storedStateRun();const reply = snapshot.messages.at(-1)?.value;if (reply?.role === "assistant") for (const part of reply.content) if (part.type === "text") console.log(part.text);node --import tsx main.tsThe model saves the link, and its next request already lists it:
Complete this test run.
[extension:bookmarks.saved]Saved links: Shiitake docsNext: Show it to a client.