# Session Memento

> For agents: start with the [agent guide](https://pi-tui.ratstack.sh/llms.txt). Every page here has a Markdown twin, and every pattern has a JSON record.

Lifecycle / Lifecycle and mounting · `session-memento` · [HTML](https://pi-tui.ratstack.sh/patterns/session-memento/) · [JSON](https://pi-tui.ratstack.sh/patterns/session-memento.json) · [all patterns](https://pi-tui.ratstack.sh/patterns.md)

Also known as Session-saved view.

## Intent

Reconstruct deliberate view state from typed custom session entries.

## Motivation

Nico's arcade overlays load typed saved entries, while anycopy validates saved fold IDs before restoring them.

## Applicability

- Use this when a mini-app or tree should resume after its interaction closes.

## Structure

```text
typed snapshot -> appendEntry
session entries -> validate -> view
```

## Participants

- [`pi.appendEntry`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#state-management): Persists typed custom data outside model context.
- [`ctx.sessionManager.getBranch`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#state-management): Reads entries along the active or requested branch.
- [`ctx.sessionManager.getEntries`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#state-management): Reads session-file entries for deliberate reconstruction.
- `Typed snapshot`: Stores deliberate state with explicit compatibility checks.

Pi screen APIs: [`pi.appendEntry`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#state-management), [`ctx.sessionManager.getBranch`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#state-management), [`ctx.sessionManager.getEntries`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#state-management), `SessionManager.inMemory`, `SessionManager.appendCustomEntry`, `SessionManager.getBranch`, `setWidget`

## Consequences

- Deliberate view state can survive closing and resuming.
- Branch selection and snapshot compatibility need explicit checks.

## Implementation

- Choose the active branch for branch-sensitive state.
- Validate restored identifiers and snapshot compatibility.
- Do not persist every animation frame.

## States

Each state was rendered at 40, 60, 80 and 120 columns in Pi's dark and light themes. Each frame links one WebP. An animated frame links its first image, then the animation.

### New view

`initial`: Show a synthetic counter and a compact view preference.

Checks at every width and theme:

- width: ✓ pass
- style-leak: ✓ pass
- hard-coded-colour: ✓ pass
- height: ✓ pass

Frames:

- 40 columns: ![Session Memento, New view, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/session-memento/initial-40-dark.3d9a29e4ef07.webp) ![Session Memento, New view, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/session-memento/initial-40-light.cf34e90af5b7.webp)
- 60 columns: ![Session Memento, New view, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/session-memento/initial-60-dark.c9244207268f.webp) ![Session Memento, New view, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/session-memento/initial-60-light.6d09033ac92d.webp)
- 80 columns: ![Session Memento, New view, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/session-memento/initial-80-dark.5056dced87b9.webp) ![Session Memento, New view, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/session-memento/initial-80-light.89f396761dce.webp)
- 120 columns: ![Session Memento, New view, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/session-memento/initial-120-dark.ce7df1d40e71.webp) ![Session Memento, New view, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/session-memento/initial-120-light.c826998c929e.webp)

### Saved snapshot

`saved`: Close and append a deliberately typed state snapshot.

Checks at every width and theme:

- width: ✓ pass
- style-leak: ✓ pass
- hard-coded-colour: ✓ pass
- height: ✓ pass

Frames:

- 40 columns: ![Session Memento, Saved snapshot, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/session-memento/saved-40-dark.ed81cb59cb35.webp) ![Session Memento, Saved snapshot, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/session-memento/saved-40-light.d3d4e9897b42.webp)
- 60 columns: ![Session Memento, Saved snapshot, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/session-memento/saved-60-dark.dff036ebe07d.webp) ![Session Memento, Saved snapshot, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/session-memento/saved-60-light.2652d3a7df25.webp)
- 80 columns: ![Session Memento, Saved snapshot, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/session-memento/saved-80-dark.016f5691db6a.webp) ![Session Memento, Saved snapshot, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/session-memento/saved-80-light.6c0906aa67ca.webp)
- 120 columns: ![Session Memento, Saved snapshot, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/session-memento/saved-120-dark.301e7a0d69e1.webp) ![Session Memento, Saved snapshot, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/session-memento/saved-120-light.5b473d4613fd.webp)

### Resumed view

`restored`: Reopen with compatible saved state and the same view preference.

Checks at every width and theme:

- width: ✓ pass
- style-leak: ✓ pass
- hard-coded-colour: ✓ pass
- height: ✓ pass

Frames:

- 40 columns: ![Session Memento, Resumed view, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/session-memento/restored-40-dark.aff9c6732d57.webp) ![Session Memento, Resumed view, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/session-memento/restored-40-light.22bde47f2954.webp)
- 60 columns: ![Session Memento, Resumed view, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/session-memento/restored-60-dark.1f24957a9f56.webp) ![Session Memento, Resumed view, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/session-memento/restored-60-light.d3ce5fe9959d.webp)
- 80 columns: ![Session Memento, Resumed view, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/session-memento/restored-80-dark.1fdf97c2de78.webp) ![Session Memento, Resumed view, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/session-memento/restored-80-light.ed671b50c22a.webp)
- 120 columns: ![Session Memento, Resumed view, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/session-memento/restored-120-dark.2c738af46013.webp) ![Session Memento, Resumed view, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/session-memento/restored-120-light.e1dde4288d3a.webp)

## Sample code

`stories/patterns/lifecycle/session-memento.ts`, the story the frames above were rendered from.

```ts
// Session Memento: restore deliberate view state from compatible custom entries.
import { SessionManager } from "@earendil-works/pi-coding-agent";
import type { PatternStory } from "../../../src/pattern.ts";

type Snapshot = { version: 1; counter: number; view: "compact" | "expanded" };
function compatible(value: unknown): value is Snapshot {
  return typeof value === "object" && value !== null && "version" in value && value.version === 1 &&
    "counter" in value && typeof value.counter === "number" && Number.isInteger(value.counter) && value.counter >= 0 &&
    "view" in value && (value.view === "compact" || value.view === "expanded");
}

export const story: PatternStory = {
  id: "session-memento", title: "Session Memento", kind: "screen",
  apis: ["SessionManager.inMemory", "SessionManager.appendCustomEntry", "SessionManager.getBranch", "setWidget"],
  states: [
    { id: "initial", label: "New view" },
    { id: "saved", label: "Saved snapshot", steps: [{ type: "action", name: "save" }] },
    { id: "restored", label: "Resumed view", steps: [{ type: "action", name: "resume" }] },
  ],
  setup({ ui, theme, action }) {
    // The harness has no ExtensionAPI. Use Pi's real in-memory session store;
    // appendCustomEntry is the storage primitive behind pi.appendEntry.
    const sessionManager = SessionManager.inMemory("/workspace/example");
    let snapshot: Snapshot = { version: 1, counter: 3, view: "compact" };
    const showView = () => ui.setWidget("counter", [
      theme.fg("accent", "Task counter · " + snapshot.counter),
      theme.fg("muted", "View preference: " + snapshot.view),
    ]);
    ui.setEditorText("Continue reviewing the tasks.");
    action("save", () => {
      sessionManager.appendCustomEntry("task-counter", { ...snapshot });
      ui.setWidget("counter", undefined);
      ui.setWidget("receipt", [theme.fg("success", "View closed · snapshot saved"),
        theme.fg("muted", "Counter 3 · compact · schema v1")]);
      // Closing the view discards its local state, not the saved entry.
      snapshot = { version: 1, counter: 0, view: "expanded" };
    });
    action("resume", () => {
      const entry = sessionManager.getBranch().findLast(entry =>
        entry.type === "custom" && entry.customType === "task-counter");
      if (!entry || entry.type !== "custom" || !compatible(entry.data)) throw new Error("No compatible snapshot");
      snapshot = { ...entry.data };
      ui.setWidget("receipt", [theme.fg("success", "Resumed from active branch")]);
      showView();
    });
    showView();
  },
};
```

## Known uses: seen in Nico's repos

- [**pi-extensions**](https://github.com/nicobailon/pi-extensions): Arcade: persist and resume through session entries
  - [arcade/tetris.ts:632-652](https://github.com/nicobailon/pi-extensions/blob/bca5070b541ffa7d273e74036dcd7d5f8b63eed3/arcade/tetris.ts#L632-L652) @bca5070b
  - [arcade/ping.ts:558-588](https://github.com/nicobailon/pi-extensions/blob/bca5070b541ffa7d273e74036dcd7d5f8b63eed3/arcade/ping.ts#L558-L588) @bca5070b
  - [arcade/picman.ts:313-328](https://github.com/nicobailon/pi-extensions/blob/bca5070b541ffa7d273e74036dcd7d5f8b63eed3/arcade/picman.ts#L313-L328) @bca5070b
  - [arcade/spice-invaders.ts:1060-1104](https://github.com/nicobailon/pi-extensions/blob/bca5070b541ffa7d273e74036dcd7d5f8b63eed3/arcade/spice-invaders.ts#L1060-L1104) @bca5070b
  - [arcade/badlogic-game/badlogic-game.ts:270-297](https://github.com/nicobailon/pi-extensions/blob/bca5070b541ffa7d273e74036dcd7d5f8b63eed3/arcade/badlogic-game/badlogic-game.ts#L270-L297) @bca5070b
- [**dot314**](https://github.com/nicobailon/dot314): Persist fold state while reusing a session-tree selector
  - [extensions/anycopy/index.ts:971-1035](https://github.com/nicobailon/dot314/blob/17cce138f3f687211d0485d204408a9650953c61/extensions/anycopy/index.ts#L971-L1035) @17cce138

## For agents: choose and check

Choose Session Memento when your job matches its intent and applicability above. Its neighbours in Lifecycle and mounting are listed below. Read the one whose intent fits your job more closely before you commit.

- [Elastic Overlay](https://pi-tui.ratstack.sh/patterns/elastic-overlay.md): Resolve overlay size and placement from current terminal dimensions.
- [Event Relay](https://pi-tui.ratstack.sh/patterns/event-relay.md): Update visible UI from named extension event channels.
- [Process Shell](https://pi-tui.ratstack.sh/patterns/process-shell.md): Bind a temporary terminal process view to one custom interaction.
- [Output Vault](https://pi-tui.ratstack.sh/patterns/output-vault.md): Keep completed job output available after its foreground view closes.
- [Deferred Crest](https://pi-tui.ratstack.sh/patterns/deferred-crest.md): Mount optional startup content only after deferred discovery remains eligible.
- [Mode Fence](https://pi-tui.ratstack.sh/patterns/mode-fence.md): Keep terminal components separate from dialog-capable and no-UI modes.
- [Snapshot Lens](https://pi-tui.ratstack.sh/patterns/snapshot-lens.md): Derive compact status and bounded detail from lifecycle snapshots.

Check your version:

- Render your version at 40, 60, 80 and 120 columns in the dark and light themes.
- Check width: no rendered line is wider than the terminal.
- Check style-leak: no line ends with colour, bold or a link still switched on.
- Check hard-coded-colour: every colour on screen comes from the active theme.
- Check height: the output fits in the rows the terminal has.
- Read the Pi 1.0.3 docs for [`pi.appendEntry`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#state-management), [`ctx.sessionManager.getBranch`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#state-management), [`ctx.sessionManager.getEntries`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#state-management), `SessionManager.inMemory`, `SessionManager.appendCustomEntry`, `SessionManager.getBranch`, `setWidget` before you use them.
- Every reference frame gets the verdict its state expects.

Next actions: `related({ id: "session-memento" })` lists what to read next, and `states({ id: "session-memento", width: 40 })` returns every frame and verdict at one width. The [build-pi-tui-component skill](https://pi-tui.ratstack.sh/skills/build-pi-tui-component.md) walks through the whole loop.

## Related

- [Branch Fold](https://pi-tui.ratstack.sh/patterns/branch-fold.md): uses saved fold identifiers

## Linked from

- [Branch Fold](https://pi-tui.ratstack.sh/patterns/branch-fold.md): persists the fold identifiers
