Analyst agent: what it is and how to create one in OpenCode
| What an analyst agent is | |
| Researcher vs analyst | |
| Create the agent file | |
| Tools and data access | |
| System prompt that works | |
| How to launch it | |
| Decision memo format | |
| Evals | |
| Guardrails |
What an analyst agent is
An analyst agent is a read-mostly subagent
that turns raw data into a decision memo. Its inputs are logs, metrics,
CSV dumps, database query results and git history; its method is to run
read-only commands that count, aggregate and compare; its output is a
short memo with numbers, risks and one recommendation. It never edits
files and never changes the systems it measures.
It has the full agent loop — restate the
question, run a command, read the output, refine — but every tool call is
a measurement, not a mutation. Where a chatbot would guess a percentage,
the analyst prints the command and the count it is built on.
Researcher vs analyst
The two read-only roles answer different questions and chain well:
- Researcher works with sources: code, docs, the open web. Output is findings with citations — file:line or URL.
- Analyst works with datasets: logs, metrics, query results, git history. Output is numbers with the commands that produced them.
- Chain: the researcher maps what exists, the analyst measures it. Example: researcher finds where requests are logged, analyst computes the error rate from those logs.
A quick sanity check when picking the role: if the answer ends with a reference, launch the researcher; if it ends with a number, launch the analyst.
Create the agent file
File .opencode/agents/analyst.md, id analyst. The key part is the shell allowlist: measurement commands are allowed, everything else is denied by a catch-all rule — permission rules are ordered and the last match wins:
--- description: Turns logs, metrics and git history into a decision memo mode: subagent model: opencode/gpt-6-luna steps: 10 permissions: - action: edit resource: "*" effect: deny - action: shell resource: "head *" effect: allow - action: shell resource: "tail *" effect: allow - action: shell resource: "wc *" effect: allow - action: shell resource: "grep *" effect: allow - action: shell resource: "sort *" effect: allow - action: shell resource: "uniq *" effect: allow - action: shell resource: "python3 *" effect: allow - action: shell resource: "git log *" effect: allow - action: shell resource: "git diff --stat*" effect: allow - action: shell resource: "*" effect: deny --- Restate the question as one measurable sentence before touching data. Show the command, then the number it produced. Verify every headline number with a second command or a second source. State confidence and what would change the conclusion. No writes, no deletes, no installs. Mask personal data in examples. Answer as a decision memo: Question, Data, Findings, Risks, Recommendation.
Field notes:
- mode: subagent — the parent decides when measurement is worth a child session.
- steps: 10 — analysis drifts into curiosity; the cap forces closure.
- deny edit + deny-all shell last — the allowlist above the catch-all is the whole safety model. Adding a command means adding one allow line, not relaxing a deny.
- model: counting and comparing is Luna work; escalate to Opus only when the memo drives a costly decision.
Tools and data access
The core tools do most of the work: read, glob, grep for files, the allowlisted shell for counting. Two additions pay off fast:
- Python through the allowlisted shell. One python3 -c line replaces ten piped greps for grouping and percentiles. For bigger jobs, the execute tool runs a script in a confined runtime — no filesystem writes.
- MCP for databases. A server exposing a SELECT-only query tool beats raw shell access: the schema is discovered, the queries are logged, writes stay impossible.
Keep the question tool reachable: a metric with an unclear definition (what counts as a visitor, a 5xx, an active user) produces a confident wrong memo. One clarifying question is cheaper than one wrong decision.
System prompt that works
Three habits separate a useful memo from confident noise. First, the question becomes one measurable sentence — error rate of GET /api/orders over the last 7 days — before any command runs. Second, every headline number is verified twice: a second command, or the same command on a narrower window. Third, the memo names its data window and the thing it did NOT check. The prompt in the file above encodes all three; the report format below enforces them.
How to launch it
Give the parent a question with a dataset in it:
Use the analyst subagent to measure the 5xx rate per endpoint from site_logs and say whether it grew this week
A typical first command in the trace:
wc -l site_logs/access.log
Pin it as .opencode/commands/analyze.md:
--- description: Measure one question with the read-only analyst agent: analyst model: opencode/gpt-6-luna subagent: true --- Analyze: $ARGUMENTS Return a decision memo: Question, Data, Findings, Risks, Recommendation.
To chain roles, the orchestrator gets both children in its subagent permissions: researcher maps where requests are logged, analyst measures the rate. Three child results land back in the parent session.
Decision memo format
Fixed skeleton, five sections, one screen:
Question Did the 5xx rate of GET /api/orders grow this week? Data site_logs/access.log, 214,003 lines, Oct 14-21; verified with wc -l and a second pass on a 2-day window. Findings - 5xx rate 0.42% this week vs 0.19% last week (+0.23 pp). - 87% of 5xx hit /api/orders; top status 502 (1,204 hits). - Growth starts Oct 19, 14:00 — matches the deploy in git log. Risks - Log source only; no client-side errors counted. - Two days of the window include a bot crawl (user agent filtered). Recommendation - Roll back the Oct 19 deploy first, then re-measure before any fix.
Every number in Findings must be reproducible from Data. If a section cannot be filled, the memo says so — an empty Risks is a warning sign, not a clean result.
Evals
Five fixed tasks with numbers you can check by hand:
- x2 counting: lines matching a pattern in a fixture log — pass when the reported count equals grep -c.
- x1 comparison: error rate week over week on a synthetic dataset — pass when the delta matches to one decimal.
- x1 discipline: a question with no data in scope — pass when the memo reports the gap and runs no writes.
- x1 chaining: researcher maps the log format, analyst measures the rate — pass when the memo cites the file the researcher found.
Guardrails
- No writes, no deletes, no installs. The deny catch-all is the contract; every new capability is an explicit allow line, reviewed like code.
- SELECT only. Database access through an MCP query tool that cannot mutate.
- Secrets and personal data. No reading .env files; tokens and emails are masked in memo examples.
- Honest rounding. 0.19% is not "about zero"; precision is stated once and kept everywhere.
- Missing data is a finding. If the window or source is missing, the memo reports the gap instead of bridging it with an estimate.
Article author: Arthur Isaev
| AI | |
| Creating and developing agents | |
| Researcher agent | |
| Analyst agent | |
| Agent setup audit: the aredel case | |
| Writing skills for AI assistants | |
| OpenCode coding agent | |
| One API key for 300+ models |