DEV Community

Cover image for From Chat to Pipeline: Running Octomind Non-Interactively in CI and Scripts
Don Karter
Don Karter

Posted on Originally published at octomind.run

From Chat to Pipeline: Running Octomind Non-Interactively in CI and Scripts

How to take an AI agent out of the terminal and into automation – non-interactive runs, JSON Lines output, structured output with JSON Schema, and multi-step workflows. A practical guide to Octomind in CI, cron jobs, and scripts.

An AI agent that only works when you're typing at it is a tool. An AI agent that works inside a pipeline is infrastructure. Octomind is built to run non-interactively from day one, but pinning the right capabilities is what keeps your CI job from flaking.

I've put Octomind into CI checks, cron jobs, and deploy scripts. This is what I wish I'd known the first time, from the basic "run it without a terminal" up to structured output you can pipe into other tools. Sharing this here because the dev.to community asked practical questions about agent automation, and these patterns might save you a few CI failures.

The Core Idea: Pipe In, Read Out

Interactively, you type and Octomind answers in a styled terminal. Non-interactively, you give it the prompt on stdin and it runs once and exits. The switch is the --format flag:

echo "Review the changes in this PR and flag anything risky" \
  | octomind run developer:general --format plain
Enter fullscreen mode Exit fullscreen mode

Pass a --format value, or pipe stdin instead of attaching a terminal, and the session runs once, reads its input from stdin, and exits when done. That's the whole non-interactive contract. plain keeps the human-readable, terminal-decorated output; for anything a machine consumes, you want jsonl.

JSON Lines: Output a Pipeline Can Parse

--format jsonl emits structured JSON Lines regardless of whether a terminal is attached. One object per event, easy to parse, stable to depend on:

echo "List the three highest-risk files in this diff" \
  | octomind run developer:general --format jsonl
Enter fullscreen mode Exit fullscreen mode

Use this format in CI. Pipe it into jq, grep it for the events you care about, store it as a build artifact. Every line carries a type – assistant, tool_use, tool_result, cost, status, and so on – so the model's answer is one filter away: jq -r 'select(.type == "assistant") | .content'. Because each line is a complete JSON object, you can stream and process it as the run goes rather than waiting for the end.

A reliability note that has bitten me: pin down the environment the agent runs in. In CI you want deterministic behavior, not whatever tools happen to match your prompt. Octomind normally loads capabilities on demand by matching your message – great interactively, slightly unpredictable in a pipeline. Force-load the tools you need at boot instead:

echo "Audit src/ for hardcoded secrets" \
  | OCTOMIND_CAPABILITIES=codesearch-semantic,filesystem-read \
    octomind run developer:general --format jsonl
Enter fullscreen mode Exit fullscreen mode

Everything in that list is loaded before the first turn, regardless of what the automatic matching thinks of your prompt. Matching still runs on top of it, though – if you want the surface to be exactly that list and nothing else, also set auto_capabilities = false in your config. Same tools on every run is exactly what you want when a green check depends on it.

Structured Output: Make the Agent Return Data, Not Prose

Structured output turns an agent into a pipeline stage. Pass --schema with a JSON Schema file and the model's output is constrained to match it. Instead of a paragraph you have to parse with fragile regex, you get clean, typed JSON:

echo "List the top 3 TODOs in this codebase" \
  | octomind run developer:general --format jsonl --schema todos.schema.json
Enter fullscreen mode Exit fullscreen mode

Every assistant reply for that run conforms to your schema, while tool calls still flow normally underneath – only the final text is constrained. This is how you wire an agent into a larger system: have it emit { "issues": [...] } and feed that straight into the next step, no scraping required. A ready-to-use example ships in the repo at config-templates/todos.schema.json.

Structured output needs a model that supports it. Most providers do – OpenAI, Google, xAI, DeepSeek, Groq, OpenRouter, and more. Anthropic models don't, and the run fails fast with a clear message if you pick one that can't. Like --model, the schema is a runtime override that isn't persisted, so pass it again on each run.

Multi-Step Workflows

For multi-step flows, use octomind workflow. It runs a TOML-defined pipeline, reading input from stdin:

echo "Build a JSON-to-CSV CLI in Rust" | octomind workflow build-flow.toml --format jsonl
Enter fullscreen mode Exit fullscreen mode

With --format jsonl it emits one assistant event per step as it completes, each tagged with a step field (the last one is the final result), followed by a single aggregated cost event. Per-step progress and cost stay on stderr for a human to watch, so stdout is clean for your parser. Use --dry-run first to print the execution plan and validate the flow without spending a token.

Guardrails Are Even More Important Here

When a human is watching, a wrong move gets caught. In an unattended pipeline, nobody's watching – which makes guardrails and the sandbox mandatory, not optional. For any automated run I do two things by reflex:

# restrict writes to the working directory — OS-enforced (Landlock on Linux, Seatbelt on macOS)
octomind run developer:general --sandbox --format jsonl < prompt.txt
Enter fullscreen mode Exit fullscreen mode

I also set spending limits in config so a runaway loop can't run up a bill while I'm asleep:

max_session_spending_threshold = 2.0
max_request_spending_threshold = 0.5
Enter fullscreen mode Exit fullscreen mode

Interactively the session cap asks before continuing; in a pipeline there's nobody to ask, so it stops the run. A pipeline that can't write outside its directory and can't spend more than two dollars is a pipeline you can actually trust to run on a schedule. When a guardrail fires it shows up in the JSONL stream as an injected event with source_kind set to guardrail_hook or guardrail_validator, so you can assert in CI that a rule fired (or didn't) as part of your test.

A Real CI Shape

Put together, a PR-review check looks like this:

#!/bin/sh
set -e
git diff origin/main... > /tmp/diff.txt

echo "Review the diff in /tmp/diff.txt. Return findings as JSON matching the schema." \
  | OCTOMIND_CAPABILITIES=filesystem-read \
    octomind run developer:general \
      --sandbox --format jsonl --schema review.schema.json \
  | tee review.jsonl

# downstream: parse review.jsonl, post comments, fail the build on severity >= high
Enter fullscreen mode Exit fullscreen mode

Deterministic tools, sandboxed, structured output, cost-capped. The agent does the judgment; your script does the plumbing. (If you specifically want code review on every GitHub PR, we package this exact pattern as a ready-made Action so you don't have to assemble it yourself.)

What Changes When Nobody's Watching

Once an agent runs cleanly without a terminal, you stop thinking of it as a thing you use and start thinking of it as a thing you deploy. Cron jobs that triage issues overnight. CI checks that flag risky diffs. Scripts that summarize a day's commits. Same binary, same models – just pointed at stdin instead of a keyboard, emitting JSON instead of prose.

Start with one echo ... | octomind run --format jsonl. Add a schema when you need structured data, a sandbox and spending cap when it runs unattended, and a workflow when one prompt isn't enough. The same binary that answers in your terminal can run your pipeline at 3 AM. That's the difference between a tool and infrastructure.

Get Octomind – and put your agent on the pipeline.

Top comments (0)