# Output Vault

> 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 · `output-vault` · [HTML](https://pi-tui.ratstack.sh/patterns/output-vault/) · [JSON](https://pi-tui.ratstack.sh/patterns/output-vault.json) · [all patterns](https://pi-tui.ratstack.sh/patterns.md)

Also known as Retained job view.

## Intent

Keep completed job output available after its foreground view closes.

## Motivation

Interactive Shell's non-streaming dispatch retains completed sessions so output remains queryable after foreground completion.

## Applicability

- Use this when background output must remain inspectable for reattachment.

## Structure

```text
foreground view -> background owner
completed job -> retained output
reattach -> fresh view
```

## Participants

- [`ctx.ui.custom`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#use-custom-screens-and-overlays): Mounts one interaction and resolves through done.
- [`ctx.ui.setWidget`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#interact-with-the-user): Mounts, replaces or clears one near-editor widget.
- `Background owner`: Retains completed output independently of the foreground interaction.

Pi screen APIs: [`ctx.ui.custom`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#use-custom-screens-and-overlays), [`ctx.ui.setWidget`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#interact-with-the-user), `custom`, `setWidget`, `Box`, `DynamicBorder`

## Consequences

- Background output can be inspected after its first view closes.
- Retention differs by streaming mode and needs separate session ownership.

## Implementation

- The source's retention rules differ by streaming mode.
- Closing a view is not the same as releasing the background job.
- Pi does not provide a retained PTY-session store.

## 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.

### Background ownership

`background`: Show a synthetic job handed off from its foreground view.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Output Vault, Background ownership, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/output-vault/background-40-dark.a37871035181.webp) ![Output Vault, Background ownership, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/output-vault/background-40-light.a1ff9116df97.webp)
- 60 columns: ![Output Vault, Background ownership, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/output-vault/background-60-dark.d2d91aa91fe9.webp) ![Output Vault, Background ownership, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/output-vault/background-60-light.f882b8de4b79.webp)
- 80 columns: ![Output Vault, Background ownership, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/output-vault/background-80-dark.dd022aa24952.webp) ![Output Vault, Background ownership, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/output-vault/background-80-light.036efc15ccc5.webp)
- 120 columns: ![Output Vault, Background ownership, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/output-vault/background-120-dark.eafd83a55389.webp) ![Output Vault, Background ownership, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/output-vault/background-120-light.8752f11ed719.webp)

### Completed output retained

`completed`: Show a completed job still available in its background widget.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Output Vault, Completed output retained, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/output-vault/completed-40-dark.227d44e36da1.webp) ![Output Vault, Completed output retained, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/output-vault/completed-40-light.1d90a47411a6.webp)
- 60 columns: ![Output Vault, Completed output retained, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/output-vault/completed-60-dark.b523eade83ea.webp) ![Output Vault, Completed output retained, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/output-vault/completed-60-light.dfcd28ab5f6f.webp)
- 80 columns: ![Output Vault, Completed output retained, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/output-vault/completed-80-dark.f0e6e860d13b.webp) ![Output Vault, Completed output retained, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/output-vault/completed-80-light.11c8a90c7a36.webp)
- 120 columns: ![Output Vault, Completed output retained, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/output-vault/completed-120-dark.cff8169e2298.webp) ![Output Vault, Completed output retained, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/output-vault/completed-120-light.c0d01a5b5849.webp)

### Inspect retained output

`reattached`: Open a fresh view of its stored synthetic output.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Output Vault, Inspect retained output, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/output-vault/reattached-40-dark.75f15ea6f667.webp) ![Output Vault, Inspect retained output, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/output-vault/reattached-40-light.2f33af759460.webp)
- 60 columns: ![Output Vault, Inspect retained output, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/output-vault/reattached-60-dark.8b844da9f22b.webp) ![Output Vault, Inspect retained output, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/output-vault/reattached-60-light.68d78459705b.webp)
- 80 columns: ![Output Vault, Inspect retained output, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/output-vault/reattached-80-dark.e1afeb88ce47.webp) ![Output Vault, Inspect retained output, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/output-vault/reattached-80-light.a9959ecc7aa1.webp)
- 120 columns: ![Output Vault, Inspect retained output, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/output-vault/reattached-120-dark.266769d5bd8e.webp) ![Output Vault, Inspect retained output, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/output-vault/reattached-120-light.56d55ef865c0.webp)

## Sample code

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

```ts
// Output Vault: retain synthetic completed output independently of its view.
import { Box, Text, matchesKey, Key } from "@earendil-works/pi-tui";
import { DynamicBorder } from "@earendil-works/pi-coding-agent";
import type { PatternStory } from "../../../src/pattern.ts";

export const story: PatternStory = {
  id: "output-vault", title: "Output Vault", kind: "screen",
  apis: ["custom", "setWidget", "Box", "DynamicBorder"],
  states: [
    { id: "background", label: "Background ownership" },
    { id: "completed", label: "Completed output retained", steps: [{ type: "action", name: "complete" }] },
    { id: "reattached", label: "Inspect retained output", steps: [{ type: "action", name: "inspect" }] },
  ],
  setup({ ui, theme, action }) {
    // Non-streaming synthetic job. Closing a view does not clear its vault.
    const backgroundOwner = { state: "running", output: ["PASS parser tokens"] };
    ui.setEditorText("Keep reviewing while checks run.");
    const showJob = () => ui.setWidget("vault", [
      theme.fg("accent", "Parser checks · " + backgroundOwner.state),
      theme.fg("muted", backgroundOwner.state === "running"
        ? "Detached · output owned in background" : "Output retained · ready to inspect"),
    ], { placement: "belowEditor" });
    action("complete", () => {
      backgroundOwner.state = "completed";
      backgroundOwner.output.push("PASS empty input", "2 checks passed · exit 0");
      showJob();
    });
    action("inspect", () => {
      void ui.custom<void>((_tui, theme, _keys, done) => {
        // A fresh view reads retained data; dispose never mutates the owner.
        const panel = new Box(1, 0, line => theme.bg("customMessageBg", line));
        panel.addChild(new DynamicBorder(line => theme.fg("borderAccent", line)));
        panel.addChild(new Text(theme.fg("accent", "Retained parser output") + "\n" +
          theme.fg("toolOutput", backgroundOwner.output.join("\n")) + "\n" +
          theme.fg("muted", "Esc closes only this view"), 0, 0));
        panel.addChild(new DynamicBorder(line => theme.fg("borderAccent", line)));
        return {
          render: width => panel.render(width), invalidate: () => panel.invalidate(),
          handleInput: data => { if (matchesKey(data, Key.escape)) done(); },
        };
      }, { overlay: true, overlayOptions: { width: "90%", anchor: "top-center", row: 1, margin: 1 } });
    });
    showJob();
  },
};
```

## Known uses: seen in Nico's repos

- [**pi-interactive-shell**](https://github.com/nicobailon/pi-interactive-shell): Retain dispatch terminals for later completion queries
  - [overlay-component.ts:455-480](https://github.com/nicobailon/pi-interactive-shell/blob/77df9a8142a2f731635a4c5a01d68feecb5cced4/overlay-component.ts#L455-L480) @77df9a81
  - [overlay-component.ts:642-690](https://github.com/nicobailon/pi-interactive-shell/blob/77df9a8142a2f731635a4c5a01d68feecb5cced4/overlay-component.ts#L642-L690) @77df9a81
- [**pi-interactive-shell**](https://github.com/nicobailon/pi-interactive-shell): Reattach overlay supports bounded selection and deferred refresh
  - [reattach-overlay.ts:18-115](https://github.com/nicobailon/pi-interactive-shell/blob/77df9a8142a2f731635a4c5a01d68feecb5cced4/reattach-overlay.ts#L18-L115) @77df9a81
  - [reattach-overlay.ts:311-448](https://github.com/nicobailon/pi-interactive-shell/blob/77df9a8142a2f731635a4c5a01d68feecb5cced4/reattach-overlay.ts#L311-L448) @77df9a81

## For agents: choose and check

Choose Output Vault 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.
- [Deferred Crest](https://pi-tui.ratstack.sh/patterns/deferred-crest.md): Mount optional startup content only after deferred discovery remains eligible.
- [Session Memento](https://pi-tui.ratstack.sh/patterns/session-memento.md): Reconstruct deliberate view state from typed custom session entries.
- [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 [`ctx.ui.custom`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#use-custom-screens-and-overlays), [`ctx.ui.setWidget`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#interact-with-the-user), `custom`, `setWidget`, `Box`, `DynamicBorder` before you use them.
- Every reference frame gets the verdict its state expects.

Next actions: `related({ id: "output-vault" })` lists what to read next, and `states({ id: "output-vault", 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

- [Process Shell](https://pi-tui.ratstack.sh/patterns/process-shell.md): owns the temporary foreground interaction

## Linked from

- [Process Shell](https://pi-tui.ratstack.sh/patterns/process-shell.md): keeps output beyond the view
