Fan-out code review
Goal
Review a change the way a good team does, with several specialists each looking for a different class of problem, and collect their findings into one report. A graph fans out the same code to independent reviewer agents (bugs, style, security, ...) and fans in their outputs to a single aggregated review.
This recipe shows the graph fan-out (tee) and fan-in node types. As with
the other recipes, each step is shown in the console first, then Via the
CLI. For a graph, the visual editor and the CLI manifest describe the exact same
spec; the manifest is often the clearest way to read a tee/fan-in graph, so this
recipe leads the graph step with the editor's Import spec paste (which takes
that same JSON) and the primectl create graph -f manifest.
Ingredients
- An LLM provider.
- One reviewer agent per angle you care about. This recipe uses two: a bug reviewer and a style reviewer.
If you have not connected primectl yet, see "Connecting the CLI" in the
RAG knowledge base recipe.
Walkthrough
1. Create the reviewer agents
One agent per review angle; each is scoped by its system prompt to one concern.
In the console:
- Go to Compute > Agents and click New agent.
- On Basic set ID to
reviewer-bugsand pick the LLM provider + Model; leave Tools empty; on Advanced paste the bug-review system prompt. Click Create. - Repeat for
reviewer-stylewith the style-review system prompt.
Via the CLI:
primectl create -f reviewer-bugs.yaml
primectl create -f reviewer-style.yaml
kind: agent
spec:
id: reviewer-bugs
description: Code reviewer.
model: { provider_id: <llm>, model_name: <model> }
tools: []
system_prompt:
- >-
You review code for BUGS and correctness issues ONLY. List concrete bugs
concisely; if none, say so.
kind: agent
spec:
id: reviewer-style
description: Code reviewer.
model: { provider_id: <llm>, model_name: <model> }
tools: []
system_prompt:
- >-
You review code for STYLE and readability issues ONLY. List concrete style
improvements concisely; if none, say so.
2. Create the fan-out / fan-in graph
A fan_out node with a tee spec dispatches the same input to each reviewer; a
fan_in node waits for all of them and renders an aggregate.
In the console:
- Go to Compute > Graphs and click New graph. Give it the ID
code-reviewand pick any Seed agent (the seed Begin/agent/End is just a starting skeleton you will replace). Click Create to open the visual editor. - The fastest way to build the exact graph below is the editor's raw paste: click
Import spec, paste the JSON from the manifest's
spec(thenodes,edges, andentry_node_id), and click Load into editor. Then click Save. - Or build it node by node with Add node: a Begin, a Fan-out (set its
mode to tee and list
review_bugsandreview_styleas targets), the two Agent nodes (pickreviewer-bugs/reviewer-styleand set eachinput_template), a Fan-in (set theaggregate_template), and an End (set theoutput_template). Use Add edge in Static mode to wirestart -> split,review_bugs -> combine,review_style -> combine, andcombine -> done. Click Save.
Via the CLI:
primectl create -f review-graph.yaml
kind: graph
spec:
id: code-review
description: "Fan-out code review: parallel reviewers, aggregated."
max_iterations: 10
nodes:
- { kind: begin, id: start, input_schema: { type: object, required: [code], properties: { code: { type: string } } } }
- { kind: fan_out, id: split, specs: [ { kind: tee, target_node_ids: [review_bugs, review_style] } ] }
- { kind: agent, id: review_bugs, agent_id: reviewer-bugs, input_template: "Review this code for BUGS only:\n{{ initial_input.code }}" }
- { kind: agent, id: review_style, agent_id: reviewer-style, input_template: "Review this code for STYLE only:\n{{ initial_input.code }}" }
- { kind: fan_in, id: combine, aggregate_template: "## Bugs\n{{ nodes.review_bugs[0].text }}\n\n## Style\n{{ nodes.review_style[0].text }}" }
- { kind: end, id: done, output_template: "{{ nodes.combine.text }}" }
edges:
- { kind: static, from_node: start, to_node: split }
- { kind: static, from_node: review_bugs, to_node: combine }
- { kind: static, from_node: review_style, to_node: combine }
- { kind: static, from_node: combine, to_node: done }
Three things that will save you a failed run:
- Fan-out targets are addressed as a list. A
teeputs each target's output atnodes.<target>as a one-element list, so the fan-in template reads{{ nodes.review_bugs[0].text }}, not{{ nodes.review_bugs.text }}(the latter ends the graph withended_detail: template_error, "'list object' has no attribute 'text'"). - The fan_out spec does the dispatch; you wire the join. You do not add
split -> revieweredges (thetarget_node_idsspawn them). You do addreviewer -> combineedges; the fan_in fires only when every incoming edge's source has produced output. - Adding an angle is one agent + two lines: a new reviewer agent, its node
listed in
target_node_ids, and areviewer -> combineedge. On a single-concurrency LLM the reviewers serialize, but each still gets an independent pass.
3. Run it
The graph's Begin node declares an input_schema with a code field, so both the
console and the CLI can pass the code in as structured graph input.
In the console:
- Click New session, set the Binding to
graph, and pick thecode-reviewgraph and a Workspace. - Because the graph declares an input schema, the modal renders a code field; paste the code to review there. Click Create and watch the run.
Via the CLI:
primectl session run <workspace-id> --graph code-review --graph-input '{"code": "def divide(a, b):\n return a / b"}'
To wire it into your workflow instead, drive the graph however the change arrives:
a scheduled trigger for a nightly sweep, or a webhook trigger from your forge
on each PR (pass the diff as graph_input.code). Deliver the aggregated report by
having a final agent post it (see the
stock-news monitor for the inform_user
pattern).
Testing
Run the graph with a small, deliberately flawed function as the code input:
{"code": "def divide(a, b):\n result = a / b\n return result"}
Expected outcome (verified):
- The
teedispatches both reviewers; in the graph state you seereview_bugsandreview_stylerunning at the same superstep (the samelast_run_iteration). - The bug reviewer flags the real defect, for example "does not handle the case
where
bis zero, which would cause aZeroDivisionError", while the style reviewer independently suggests "return the result directly without assigning it ... add a docstring". - The graph ends
completed, and the end output is the aggregated ## Bugs / ## Style report from the fan_in.