# Script collect, AI summarise

Use this when you have a deterministic command that produces text (build logs,
test reports, a nightly export, system info) and you want a model to turn that
output into a useful summary or classification. It's the simplest bridge
between command-line tooling and a model that can explain the output in plain
language.

## The workflow

```yaml
name: demo-2-week-of-dinners
version: 1
initial: setup
states:
  setup:
    type: script
    label: Set up the week
    script: |
      const nights = ['Monday', 'Tuesday', 'Wednesday'];
      console.log(`Planning ${nights.length} dinners: ${nights.join(', ')}.`);
      export default nights;
    validator:
      kind: boolean
    on:
      'true': plan
      'false': couldnt-plan

  plan:
    type: map
    label: Plan each night
    model: claude-haiku-4-5
    over: '{{ setup.result }}'
    itemVar: night
    prompt: |
      Plan one dinner for {{ night }}. Give the dish, a one-line why it's
      a good weeknight pick, and a short ingredient list.
    reduce:
      prompt: |
        Here are the dinners you just planned:

        {{ outputs }}

        Turn them into one shopping list, grouped by aisle.
    on:
      success: done
      failure: couldnt-plan

  done:
    type: end
    label: Menu and list ready
    outcome: success

  couldnt-plan:
    type: end
    label: Couldn't finish the plan
    outcome: failure
```

This is the shape of `demo-2-week-of-dinners`, one of the starter templates
seeded by `roscoe init` (trimmed here to a few nights; the real template plans
a configurable week and layers in diet and pantry inputs).

## Running it

```bash
roscoe run demo-2-week-of-dinners
```

The script step runs immediately, building the list of nights to plan. The
map step then plans each night in parallel and folds the results into one
shopping list through its `reduce` step, and the run finishes at `done`. The
web UI shows every node's output side by side; the merged list is also
stored on the run record under `variables.plan.reduced`.

## Why these node types

`script` with `validator: kind: boolean` runs the setup logic and returns
`true`, which routes `'true'` into the plan step. If you'd rather skip the AI
step when the script fails, return `false` from the script; that routes
`'false'` straight to `couldnt-plan` instead of burning model calls on empty
input. `map` suits the planning step because it's one task (plan a dinner)
applied across a whole list, not a single forward edge; anything more
decision-shaped (classify into one of N categories, decide whether to
escalate) wants `ai_judge` instead, so a validator can parse the response.
The template token `{{ setup.result }}` works because every script node
writes its structured `result` (whatever it `export default`s), along with
`stdout`, `stderr`, and `exitCode`, to the variables bag, and any of them is
available to reference in a downstream prompt or script. The `reduce` step
sees the collected branch outputs as `{{ outputs }}`, a JSON array, so its
prompt is worded to expect a list rather than a single block of text.

## Real-world shapes

For a daily expense digest, the script exports the day's submitted expenses
to a CSV and the AI step turns the rows into a one-paragraph, plain-English
summary for a manager to skim. For CI summarisation, the script runs
`bun run test` and the AI step summarises failures into a Slack-ready
message; pair it with a downstream `script` node that posts to a webhook.
For a build narrator, the script runs `git log --since='1 week ago'
--oneline` and the AI step writes a release-notes draft; wrap it with a
`human` approval node to make it a publish workflow (see
`/docs/recipes/approval-flow`). The same shape covers incident triage too:
run `kubectl get events`, let the AI step extract the likely root cause and
rank severity, then route to page-on-call vs. log-only with a `condition`
or `ai_judge` node.

## Passing output to a later step

A script node can hand its result to a later script two ways. The tidiest
is a template reference to the structured `result`:
`{{ setup.result.0 }}`.

Upstream outputs are also flattened into the environment as
`ROSCOE_OUT_<node>_<field>`, which is handy when a later step wants an upstream
value without templating it into the source:

```yaml
post:
  type: script
  label: Post summary to Slack
  script: |
    import { $ } from 'bun';
    const summary = process.env.ROSCOE_OUT_plan_reduced ?? '';
    await $`curl -X POST ${process.env.SLACK_WEBHOOK} \
      -H "Content-Type: application/json" \
      -d ${JSON.stringify({ text: summary })}`;
    export default true;
  validator:
    kind: boolean
  on:
    'true': done
    'false': done
```

Reading from the environment is the safer choice for anything large or
untrusted: a `${...}` interpolation in `Bun.$` is escaped for you, so an
upstream value full of quotes or shell metacharacters can't break the command.

Scripts also get two per-run values: **`$ROSCOE_RUN_DIR`** (a temporary folder
unique to this run) and **`$ROSCOE_RUN_ID`** (the run's id). Write a file to
`$ROSCOE_RUN_DIR` in one step and read it in a later one; it's safe even when
several runs execute at once, unlike the workflow-wide `$ROSCOE_WORKFLOW_DIR`.
See [/docs/nodes/script](/docs/nodes/script) and
[/docs/authoring/output-chaining](/docs/authoring/output-chaining) for the full
`run` namespace (`{{ run.id }}` / `{{ run.dir }}` work in any node).

## Variations

To add a verifier, chain the AI step into an `ai_judge` that checks whether
the result is faithful to the source (see
`/docs/recipes/multi-step-analysis`). To branch on the script's output, return a
string instead of a boolean and switch the validator to `kind: enum` to
route on it, e.g. classify by severity. For structured output, use
`ai_judge` with `kind: enum` so the model returns a known category that
drives the next transition instead of free text.

## See also

- `/docs/nodes/script` — return values, validators, env passthrough
- `/docs/nodes/map` — fan-out modes, reduce, and per-branch outputs
- `/docs/recipes/multi-step-analysis` — verify the summary before publishing
