Skip to main content

Editorial workflows with reduce and generate in Nushell

Submitted by Lennart on

For a simple editorial workflow, I would almost certainly choose reduce, but the "cool" choice (and the one that feels more like an autonomous agent) is generate.

Here is how to decide based on what your "workflow" actually looks like:

Option 1: Use reduce (The "Linear Polisher")

Use reduce if you have a fixed set of operations you want to run on a piece of text (e.g., "Fix Grammar" "Shorten" "Change Tone").

In this scenario, you are "folding" a list of instructions over your text.

let tasks = ["fix grammar", "make it sound more professional", "convert to markdown"]
let original_text = "i is writing a nushell script for agents"

let final_draft = ($tasks | reduce -f $original_text {|task, current_text|
    # Each step takes the output of the previous LLM call
    call-brain $"Apply this edit: ($task)" $current_text
})

  • Best for: Consistency. You know exactly how many steps are involved.
  • Logic: .

Option 2: Use generate (The "Iterative Refiner")

Use generate if you want the agent to keep editing until it’s happy. generate creates a stream, which is perfect for an "Editorial Loop" where the agent looks at its own work and decides if it needs another pass.

# generate {|state| {out: <value_to_emit>, next: <next_state>} } <initial_state>
let initial_state = { text: "The draft...", count: 0, done: false }

let editorial_history = (generate {|state|
    if $state.done or $state.count > 3 { return null } # Exit condition

    let feedback = (call-brain "Review this text. Is it perfect? Respond with 'YES' or feedback." $state.text)
    
    if ($feedback | str contains "YES") {
        {out: $state.text, next: {done: true}}
    } else {
        let new_text = (call-brain $"Apply this feedback: ($feedback)" $state.text)
        {out: $new_text, next: {text: $new_text, count: ($state.count + 1), done: false}}
    }
} $initial_state)

let final_result = ($editorial_history | last)

  • Best for: Quality. It mimics a human editor who sends back a draft multiple times.
  • Logic: Start Is it good? No? Edit and Repeat.

Comparison Table

Feature reduce generate
Flow Deterministic (1 2 3) Dynamic (Loop until done)
Termination Ends when the list of tasks is empty Ends when a condition is met (or limit reached)
Visibility Harder to see intermediate steps mid-stream Emits every version as a stream member (great for logging)
Use Case Batch processing Self-correcting agents

My Recommendation

If you are building an Agent System with xs, use generate.

Why? Because generate allows the LLM to decide the "next state." It can output the current draft to an xs stream at every iteration, giving you a perfect audit trail of how the "editorial brain" evolved the text.

Would you like me to show you how to wrap that generate loop so it writes every "revision" to an xs stream in real-time?

Be sure to stay tuned if you want to learn more about xs (cross.stream), event sourcing and nushell with AI and LLMs.