Use Case: Automated Code Review in CI/CD
Run Octomind as part of your CI/CD pipeline to automatically review pull requests, check for security issues, or enforce coding standards.
The Problem
Manual code review is slow. You want AI to catch common issues -- security vulnerabilities, performance problems, style violations -- before human reviewers even look at the PR.
Solution
Use non-interactive mode and read the result back as JSON.
Two facts shape everything below, so get them straight up front:
-
--format plainis decorated terminal text, not data. In a pipeline the assistant reply is wrapped in─────horizontal rules, colored, and markdown-rendered by default. It is meant for humans (e.g. a job summary), not forjq. Use it only for human-readable output. -
--format jsonlis the machine-readable surface. It emits a stream of type-tagged JSON objects, one per line --assistant,cost, and (when they occur)thinking,tool_use,tool_result,status. It is NOT a single JSON object. To get the model's answer you filter theassistantline(s) out of the stream.
Note on "structured output": the CLI does not enforce a JSON Schema on the model's reply. There is no
--schemaflag. What you get is prompt-coaxed JSON -- you ask the model to reply as JSON and parse it best-effort. Provider- enforced schema output is only available through the WebSocket and ACP server interfaces. See Structured Output.
Note on roles: the commands below use the built-in
assistantrole. The built-in roles areassistant,task_refiner,task_researcher, andreduce(the default tag isassistant:concierge). A dedicateddeveloper:generalagent also exists; it ships via the built-in default tapmuvon/tap, which auto-clones on first use (a one-time network fetch). See Tap System.
Basic: Review from Stdin
In non-interactive mode Octomind reads the entire message from stdin -- a
single stream. Do not combine a pipe with a heredoc (<<<); only one of them
reaches stdin and the other is silently dropped. Build the whole prompt, diff
included, and pipe it once:
# Feed the prompt + diff to Octomind and print a human-readable review
diff=$(git diff main..HEAD)
printf 'Review this diff for security issues, performance problems, and bugs. Be specific about file and line numbers.\n\n%s' "$diff" \
| octomind run assistant --format plainThe --format plain output is colorized and framed for a terminal, which is fine
for a human reading the log. If you need to scrape it as text, prefer the jsonl
approach below, or disable rendering with enable_markdown_rendering = false in
config (and strip ANSI / the ───── rules) -- but JSON is the cleaner path.
Structured: JSON Output for Pipeline Decisions
Ask the model to reply as JSON, run with --format jsonl, then pull the
assistant payload out of the stream and parse the JSON the model put inside it:
#!/bin/bash
# ci-review.sh
set -euo pipefail
diff=$(git diff main..HEAD)
# Run non-interactively. jsonl is a stream of type-tagged objects, one per line.
stream=$(printf 'Review this diff for issues. Respond ONLY with JSON of the form: {"summary": "...", "issues": [{"file": "...", "line": 0, "severity": "...", "description": "..."}], "approval": "approve|request_changes"}.\n\n%s' "$diff" \
| octomind run assistant --model openai:gpt-4o --format jsonl)
# Slurp the stream, keep the last assistant line, take its .content (the model's text).
review=$(echo "$stream" | jq -rsc 'map(select(.type == "assistant")) | last | .content')
# $review is the model's JSON string — parse it as JSON now.
approval=$(echo "$review" | jq -r '.approval')
errors=$(echo "$review" | jq '[.issues[] | select(.severity == "error")] | length')
echo "Review: $approval ($errors errors)"
if [ "$approval" = "request_changes" ] || [ "$errors" -gt 0 ]; then
echo "$review" | jq '.issues[]'
exit 1
fi# .github/workflows/ai-review.yml
name: AI Code Review
on: [pull_request]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Octomind
run: curl -fsSL https://raw.githubusercontent.com/muvon/octomind/master/install.sh | bash
- name: Run AI Review
env:
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
run: |
diff=$(git diff origin/main..HEAD)
# --format plain is fine here: we want a human-readable summary in the job log.
summary=$(printf 'Review for security, performance, bugs.\n\n%s' "$diff" \
| octomind run assistant --format plain)
echo "$summary" >> "$GITHUB_STEP_SUMMARY"Multi-step pipelines
For anything beyond a single review call -- chaining steps and passing one step's
output into the next -- use the octomind workflow
subcommand instead of hand-rolling shell. It reads the initial input from stdin
and runs each step as an independent octomind run subprocess:
git diff main..HEAD | octomind workflow review.toml 2> progress.logNote an important quirk: a real workflow run writes everything -- per-step
responses, progress, cost, and totals -- to stderr. stdout is empty (it is
used only by --dry-run, which prints the execution plan). So you cannot capture
a workflow's final answer from stdout; if a downstream step needs a result, have
that step write it to a file itself. See Workflows
for the full model.
Cost Control
Octomind has two spending thresholds, in USD, and they behave differently -- this matters in CI:
# Hard-stops a single request once it exceeds the limit. Safe for CI.
max_request_spending_threshold = 0.50 # max $0.50 per review
# PROMPTS the user before continuing once the cumulative session cost exceeds
# the limit. The prompt is interactive — in a non-interactive CI run there is
# nobody to answer it, so the run can block. Leave at 0.0 in CI, or rely on the
# request threshold instead.
max_session_spending_threshold = 0.0 # 0.0 = no limit>0 enables a threshold; 0.0 disables it. For CI, prefer
max_request_spending_threshold (it stops execution) and keep
max_session_spending_threshold at 0.0 to avoid a blocking prompt.
Or use a cheaper model for initial triage. run takes its message from stdin --
there is no free-text message argument (the only positional is the role/tag):
echo 'Quick check for obvious bugs' \
| octomind run assistant -m openrouter:openai/gpt-4o-mini --format plainClean CI logs
To keep CI output tidy:
- Non-interactive mode (
--format plain/jsonlwith piped stdin) shows no spinner or animations -- those only appear in an interactive terminal. - Set
log_level = "none"in config (oroctomind config --log-level none) to suppress informational logging. - Restrict filesystem writes with the
--sandboxflag (orsandbox = truein config) so a review run can read your tree but cannot write outside the current working directory. Octomind has full tool access by default -- it can read and write files, not just the diff you pipe in -- so sandboxing is worth enabling in CI.
Key Points
-
--format plain= human-readable, decorated terminal text (rules + color + markdown).--format jsonl= machine-readable stream of type-tagged JSON objects, one per line. - Both run non-interactively and read the whole message from stdin. Use one stdin source -- never a pipe AND a heredoc together.
- Extract the answer from jsonl with
jq -rsc 'map(select(.type=="assistant")) | last | .content'. -
--modelto balance cost vs quality per pipeline step. - The CLI gives prompt-coaxed JSON, not schema-enforced output. For enforced schemas use the WebSocket/ACP servers -- see Structured Output.
- Octomind has full tool access -- it can read and write files, not just the
diff you pipe in. Use
--sandboxto confine writes in CI.