From be26168c7e9147cebc1ed713a11a743033209037 Mon Sep 17 00:00:00 2001 From: Kris Braun Date: Thu, 20 Aug 2026 10:14:48 -0400 Subject: [PATCH] feat(twister): restore the per-focus memory and status-brief surface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restores `Plot.getMemory()`, `Plot.setMemory()` and `Plot.getBriefs()`, along with the `FocusMemoryEntry` and `FocusBrief` types. All three require `FocusAccess.Full`. These were withdrawn because the platform did not implement them yet — a twist declaring `FocusAccess.Full` and calling one would type-check against the SDK and then fail at runtime. That is no longer the case: the platform support lands alongside this, so the declared surface again describes what the platform actually answers. The shape is unchanged from the withdrawn version, as promised at the time. No published version was ever affected — the methods were added after 0.92.0 and their changeset was never consumed by a release — so this reads as an addition rather than a restoration from a consumer's point of view, and the changeset is worded that way. --- .changeset/priority-memory-surface.md | 5 ++ twister/src/tools/plot.ts | 86 +++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 .changeset/priority-memory-surface.md diff --git a/.changeset/priority-memory-surface.md b/.changeset/priority-memory-surface.md new file mode 100644 index 00000000..27ce2eca --- /dev/null +++ b/.changeset/priority-memory-surface.md @@ -0,0 +1,5 @@ +--- +"@plotday/twister": minor +--- + +Added: `Plot.getMemory` / `Plot.setMemory` maintain a per-focus agent memory (short freeform entries with optional absolute `YYYY-MM-DD` dates), and `Plot.getBriefs` reads each focus's current status brief. All three require `FocusAccess.Full`. diff --git a/twister/src/tools/plot.ts b/twister/src/tools/plot.ts index 4a1c712d..7e283058 100644 --- a/twister/src/tools/plot.ts +++ b/twister/src/tools/plot.ts @@ -164,6 +164,46 @@ export type SearchOptions = { focusId?: string; }; +/** + * One entry of a focus's agent memory: something the user told Plot about + * what they are trying to achieve in that focus, or standing context worth + * remembering. + */ +export type FocusMemoryEntry = { + /** The remembered fact, as a single short statement. */ + text: string; + /** + * The absolute date (`YYYY-MM-DD`) this entry is tied to, or `null` when it + * is not tied to a date. Relative wording ("next Friday") is resolved to an + * absolute date when the entry is written. + */ + date: string | null; + /** ISO timestamp of when the entry was added. */ + addedAt: string; +}; + +/** + * A focus's current status brief: a short generated summary of where that + * focus stands right now. + */ +export type FocusBrief = { + /** The focus this brief describes. */ + focusId: Uuid; + /** One-line summary of the focus, or `null` when there is nothing to say. */ + headline: string | null; + /** + * Supporting lines, each labelled by what it represents: + * - `needs_you` — waiting on the user + * - `new` — arrived since the previous brief + * - `next` — coming up + */ + bullets: { label: "needs_you" | "new" | "next"; text: string }[]; + /** True when the focus has nothing worth surfacing right now. */ + quiet: boolean; + /** ISO timestamp of when this brief was generated. */ + generatedAt: string; +}; + /** * Built-in tool for interacting with the core Plot data layer. * @@ -692,6 +732,52 @@ export abstract class Plot extends ITool { // eslint-disable-next-line @typescript-eslint/no-unused-vars abstract archiveGoal(id: Uuid): Promise; + /** + * Reads a focus's agent memory — the facts Plot has been told about that + * focus. Entries whose date is long past are excluded; a recently overdue + * entry is still returned, since an objective past its date may simply be + * unfinished. Undated entries never expire. + * + * Requires `FocusAccess.Full`. + * + * @param focusId - The focus whose memory to read + * @returns Promise resolving to the focus's current memory entries + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + abstract getMemory(focusId: Uuid): Promise; + + /** + * Replaces a focus's agent memory with the given entries. This is a + * whole-list rewrite rather than an append: read the current memory first, + * merge in the new fact, drop anything it supersedes, and write the result. + * + * Each entry's `text` is expected to be one short fact — keep entries + * granular rather than writing paragraphs, as very long entries may be + * shortened. Dates must be absolute `YYYY-MM-DD`, so resolve relative + * wording ("next Friday") before writing. The platform prunes entries whose + * date has passed and caps how many entries a focus keeps. + * + * Requires `FocusAccess.Full`. + * + * @param focusId - The focus whose memory to replace + * @param entries - The complete new memory list + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + abstract setMemory( + focusId: Uuid, + entries: { text: string; date?: string | null }[] + ): Promise; + + /** + * Current status briefs for all of the twist owner's non-archived focuses. + * Focuses without a brief yet are omitted. + * + * Requires `FocusAccess.Full`. + * + * @returns Promise resolving to the current briefs (may be empty) + */ + abstract getBriefs(): Promise; + /** * Lists the user's current Today snapshot: all non-archived items for the * latest generated day, ordered by section ("priorities" first) then rank.