# Action Sieve

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

Behavioral / Overlays and dialogs · `action-sieve` · [HTML](https://pi-tui.ratstack.sh/patterns/action-sieve/) · [JSON](https://pi-tui.ratstack.sh/patterns/action-sieve.json) · [all patterns](https://pi-tui.ratstack.sh/patterns.md)

Also known as Conditional actions.

## Intent

Derive dialog choices from current control and completion state.

## Motivation

Interactive Shell's dialog omits return-to-agent unless the control state permits it.

## Applicability

- Use this when only some actions are valid for the selected job.

## Structure

```text
execution + control state
        -> valid choices
choice -> action
```

## 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.
- [`SelectList`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#compose-built-in-components): Owns a bounded selection list and selection callbacks.
- `Control snapshot`: Determines which actions are currently applicable.

Pi component 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), [`SelectList`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#compose-built-in-components), `Text`

## Consequences

- The chooser exposes only applicable actions.
- Choices must be rebuilt when control or execution state changes.

## Implementation

- Omit unavailable actions instead of allowing an invalid transition.
- Keep dialog choice separate from job execution state.

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

### Running job

`running`: Show takeover and background choices for a live synthetic job.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Action Sieve, Running job, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/action-sieve/running-40-dark.fbfd4d40730b.webp) ![Action Sieve, Running job, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/action-sieve/running-40-light.4c0a7ba49889.webp)
- 60 columns: ![Action Sieve, Running job, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/action-sieve/running-60-dark.60d71f1902fa.webp) ![Action Sieve, Running job, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/action-sieve/running-60-light.ec1d84d535c5.webp)
- 80 columns: ![Action Sieve, Running job, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/action-sieve/running-80-dark.73ac084228f2.webp) ![Action Sieve, Running job, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/action-sieve/running-80-light.7b3c46c7b075.webp)
- 120 columns: ![Action Sieve, Running job, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/action-sieve/running-120-dark.d3d48db4b45c.webp) ![Action Sieve, Running job, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/action-sieve/running-120-light.37b4cd8a72ba.webp)

### Human control

`human`: Show return-to-agent only while the human owns control.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Action Sieve, Human control, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/action-sieve/human-40-dark.15eccdfc8a69.webp) ![Action Sieve, Human control, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/action-sieve/human-40-light.bf68be861c36.webp)
- 60 columns: ![Action Sieve, Human control, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/action-sieve/human-60-dark.2119e3ce14bf.webp) ![Action Sieve, Human control, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/action-sieve/human-60-light.47808eb82082.webp)
- 80 columns: ![Action Sieve, Human control, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/action-sieve/human-80-dark.f11718cd10ae.webp) ![Action Sieve, Human control, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/action-sieve/human-80-light.46b8b726cd85.webp)
- 120 columns: ![Action Sieve, Human control, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/action-sieve/human-120-dark.b724b23e422d.webp) ![Action Sieve, Human control, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/action-sieve/human-120-light.43445555e46c.webp)

### Exited job

`exited`: Replace live controls with applicable completion choices.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Action Sieve, Exited job, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/action-sieve/exited-40-dark.cffba593787a.webp) ![Action Sieve, Exited job, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/action-sieve/exited-40-light.e0c2ec6f16e6.webp)
- 60 columns: ![Action Sieve, Exited job, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/action-sieve/exited-60-dark.fd08d28ba532.webp) ![Action Sieve, Exited job, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/action-sieve/exited-60-light.294906a0f287.webp)
- 80 columns: ![Action Sieve, Exited job, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/action-sieve/exited-80-dark.265ae469d384.webp) ![Action Sieve, Exited job, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/action-sieve/exited-80-light.26e01273b317.webp)
- 120 columns: ![Action Sieve, Exited job, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/action-sieve/exited-120-dark.efedf9806b32.webp) ![Action Sieve, Exited job, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/action-sieve/exited-120-light.bbc8f8845afe.webp)

## Sample code

`stories/patterns/behavioral/action-sieve.ts`, the story the frames above were rendered from.

```ts
// Action Sieve: derive valid choices from execution and control state.
import { getSelectListTheme } from "@earendil-works/pi-coding-agent";
import { SelectList, Text, type SelectItem } from "@earendil-works/pi-tui";
import type { PatternStory } from "../../../src/pattern.ts";

type ControlSnapshot = { execution: "running"; owner: "agent" | "human" } | { execution: "exited"; code: number };
function validChoices(snapshot: ControlSnapshot): SelectItem[] {
  if (snapshot.execution === "exited") return [
    { value: "resume", label: "Resume conversation" },
    { value: "output", label: "Inspect saved output" },
  ];
  return [
    snapshot.owner === "agent"
      ? { value: "takeover", label: "Take control" }
      : { value: "return", label: "Return to agent" },
    { value: "background", label: "Send to background" },
    { value: "stop", label: "Stop job" },
  ];
}

export const story: PatternStory = {
  id: "action-sieve", title: "Action Sieve", kind: "component",
  apis: ["SelectList", "Text"], rows: 10,
  states: [
    { id: "running", label: "Running job" },
    { id: "human", label: "Human control", steps: [{ type: "action", name: "takeover" }] },
    { id: "exited", label: "Exited job", steps: [{ type: "action", name: "exit" }] },
  ],
  setup({ tui, theme, action }) {
    let snapshot: ControlSnapshot = { execution: "running", owner: "agent" };
    let choices = new SelectList(validChoices(snapshot), 4, getSelectListTheme());
    let outcome = "Choose an applicable action";
    function rebuild(next: ControlSnapshot) {
      snapshot = next;
      choices = new SelectList(validChoices(snapshot), 4, getSelectListTheme());
      choices.onSelect = item => { outcome = "Chosen: " + item.label; tui.requestRender(); };
      outcome = "Unavailable actions are omitted";
      tui.requestRender();
    }
    rebuild(snapshot);
    action("takeover", () => rebuild({ execution: "running", owner: "human" }));
    action("exit", () => rebuild({ execution: "exited", code: 0 }));
    return {
      invalidate() { choices.invalidate(); },
      handleInput(data) { choices.handleInput(data); tui.requestRender(); },
      render(width) {
        const status = snapshot.execution === "exited"
          ? "Exited · code " + snapshot.code
          : "Running · " + snapshot.owner + " control";
        return [
          ...new Text(theme.fg("accent", "Build job · " + status), 0, 0).render(width),
          "", ...choices.render(width), "",
          ...new Text(theme.fg("muted", outcome), 0, 0).render(width),
          ...new Text(theme.fg("dim", "↑↓ choose · enter apply"), 0, 0).render(width),
        ];
      },
    };
  },
};
```

## Known uses: seen in Nico's repos

- [**pi-interactive-shell**](https://github.com/nicobailon/pi-interactive-shell): Represent completion choices as an explicit dialog state
  - [overlay-component.ts:600-730](https://github.com/nicobailon/pi-interactive-shell/blob/77df9a8142a2f731635a4c5a01d68feecb5cced4/overlay-component.ts#L600-L730) @77df9a81

## For agents: choose and check

Choose Action Sieve when your job matches its intent and applicability above. Its neighbours in Overlays and dialogs are listed below. Read the one whose intent fits your job more closely before you commit.

- [Shared Shell](https://pi-tui.ratstack.sh/patterns/shared-shell.md): Wrap specialized dialog content in a shared themed frame.
- [Warning Gate](https://pi-tui.ratstack.sh/patterns/warning-gate.md): Follow a consequential selection with a separate warning confirmation.
- [Abort Lantern](https://pi-tui.ratstack.sh/patterns/abort-lantern.md): Settle foreground loading before opening a separate result view.
- [Dialog Fuse](https://pi-tui.ratstack.sh/patterns/dialog-fuse.md): Show remaining time before a transient dialog automatically dismisses.
- [Abort Tether](https://pi-tui.ratstack.sh/patterns/abort-tether.md): Tie a built-in dialog's lifetime to an operation's abort signal.
- [Idle Fuse](https://pi-tui.ratstack.sh/patterns/idle-fuse.md): Reset a component-owned inactivity timer after every input.
- [Settings Bench](https://pi-tui.ratstack.sh/patterns/settings-bench.md): Keep configuration interaction separate from the live activity view.
- [Done Contract](https://pi-tui.ratstack.sh/patterns/done-contract.md): Resolve each custom interaction with a typed selection or cancellation.

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), [`SelectList`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#compose-built-in-components), `Text` before you use them.
- Every reference frame gets the verdict its state expects.

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

- [Control Baton](https://pi-tui.ratstack.sh/patterns/control-baton.md): changes control ownership

## Linked from

- [Warning Gate](https://pi-tui.ratstack.sh/patterns/warning-gate.md): filters choices before confirmation
- [Settings Bench](https://pi-tui.ratstack.sh/patterns/settings-bench.md): derives currently available choices
- [Control Baton](https://pi-tui.ratstack.sh/patterns/control-baton.md): offers only valid handoff actions
