An open ledger book labelled “ORCHESTRATOR” lying at the centre of a wooden table, surrounded by small blank index cards, two of which are flipped face-up and labelled “SPECIALIST”
One orchestrator, many specialists dispatched and returned

TL;DR

  • In many Research Harness, an orchestrator agent hands each analysis step to a specialist that runs as a separate command-line process with an empty context. The specialist does its work and returns one compact JSON result.
  • Getting that to work reliably took several details: passing credentials without putting them in a logged command, attaching dataset columns to the task automatically, detecting timeouts from the process state instead of from output text, and resetting the HTTP connection after long waits.
  • Results are size-capped, with the full version saved to disk, and the cost of every child run is added to the task’s total.

Key Takeaways

  • Two different tools handle two different handoffs. activate_specialist switches the system prompt within the same conversation and process; dispatch_specialist starts a real child process, and only the orchestrator can call it.
  • A dispatch is a genuine CLI child process with its own environment. Credentials are mirrored to a mode-600 secrets.json rather than placed in the command string, which is logged and visible in the chat transcript.
  • Timeouts are detected from process state, not from matching text. An earlier version watched for the string “Command timed out” in the output, which also appears - and produces a false timeout - when a script inside the child times out while the child itself finishes normally.
  • Results are capped at 8,000 characters with the full version saved to disk, and every dispatch’s cost rolls into the task’s total. The orchestrator keeps a running history of the last 20 dispatches, so context stays small without losing the record of what already ran.

A geological analysis runs in steps: load, check quality, clean, transform, cluster, plot, verify. Run all of it in one agent conversation and the context fills with column statistics, previews and script logs long before the analysis is done.

The Geocluster Research Harness runs on multi-agent orchestration: the agent the user talks to delegates to an orchestrator, and the orchestrator hands each step to a specialist for data operations, transforms, analytics or visualisation. Meet the Geocluster Research Harness describes those layers. This post is about the mechanism underneath: running each specialist as a child process.

Two ways to hand over work

The harness uses two tools for delegation, and they work differently.

activate_specialist switches the system prompt within the current conversation. The user-facing agent calls it to hand over to the orchestrator, and the orchestrator calls it again to hand back with a summary. No new process starts and no extra model request is made; the next turn simply uses a different prompt. The orchestrator stays in the same conversation on purpose, because tracking hypotheses across steps means seeing what the user asked and everything each specialist returned.

dispatch_specialist starts a new process. It is the only way to run a specialist, and only the orchestrator has it.

What a dispatch runs

The agent is a fork of the Cline coding agent, which ships a command-line version. A dispatch runs that CLI as a child process:

bash
CLINE_SPECIALIST=dataops \
CLINE_DATA_DIR=<extension storage folder> \
CLINE_PARENT_MODEL_ID=<model the user selected> \
CLINE_PARENT_PROVIDER=<provider the user selected> \
cline '<task for the specialist>' --json -y
  • CLINE_SPECIALIST tells the child which specialist prompt and tool set to load.
  • The two CLINE_PARENT_* values make the child use the same model and provider the user picked.
  • --json prints every message as one JSON line on standard output, which the parent reads.
  • -y auto-approves actions, since nobody is watching the child to click Approve.

The child starts with nothing but its task. It connects to the same local tool server as the parent, does its work, and must finish by returning a structured result with a status, a small results object, evidence for or against the current hypothesis, recommendations and errors.

Credentials without a logged command

The user types their API key into the editor, which stores it in VS Code’s secret storage. A child process cannot read that storage.

Before every dispatch, the parent copies the key for the active provider into a secrets.json file inside the extension’s storage folder, with file permissions set to owner-only (mode 600), and only writes when the value has changed. The child reads its secrets from that folder.

The key never goes into the command string. That string is written to the extension log and appears in the chat transcript, so anything placed in it should be assumed public.

Giving the specialist the columns up front

Many dispatches concern one table, and a specialist that must first discover the column names spends a turn doing it. When the orchestrator passes a dataset_path, the parent reads the first 4 KB of the file, skips it if it contains null bytes (a sign of binary data), detects whether it is comma, tab or semicolon delimited, and appends the column list and an estimated row count to the task:

text
Dataset columns (auto-detected from /workspace/default/data/meridian_ridge_geochem.csv):
Columns (12): SAMPLE_ID, EASTING, NORTHING, DEPTH_M, LITHOLOGY, Au_ppm, Cu_ppm, Pb_ppm, Zn_ppm, As_ppm, Fe_pct, SiO2_pct
Estimated rows: ~240

The path must resolve inside the working folder; anything outside is refused and logged.

Timeouts from process state, not from text

Each dispatch has 600 seconds. An earlier version decided a dispatch had timed out by looking for the text “Command timed out” in the child’s output. That text also appears when a script inside the child times out while the child itself finishes normally, which produced false timeouts.

The dispatch now relies on a flag from the process runner that says whether the child completed or was killed. A killed child becomes an error result with a message suggesting the objective be split into smaller tasks. The orchestrator’s instructions reinforce the limit: at most one or two plots per visualisation dispatch.

Reset the connection after a long wait

A dispatch can keep the parent waiting for up to ten minutes. In that time the keep-alive socket the parent holds to the model provider may be closed, and the next request fails with a socket error. The parent resets its HTTP client after every dispatch, including when the dispatch throws.

Recovering a structured result

Models do not always return exactly what they were asked for. The parent reads the child’s output stream from the end, looking for the final completion message, and tries to recover a structured result from it: parsed directly as JSON, or extracted from JSON embedded in prose. When a child never produced a completion message at all, the orchestrator receives an error carrying the readable tail of the output, so it can decide whether to retry or report the failure.

Error results are returned to the orchestrator as tool errors instead of ordinary results. That gives the model a structural signal that the step failed, which is harder to talk past than a sentence in the middle of a JSON object.

Cap the result, keep the rest on disk

Specialists are told to keep results under about 2,000 characters and to write data to files, reporting only paths. As a backstop, the parent caps results at 8,000 characters. Anything larger is saved in full to a temporary file, the oversized entries are replaced with a note of their length, and a notice with the file path is added so the orchestrator knows where the rest is.

The orchestrator also gets a running history of dispatches in its prompt: the last 20 with their objective, status and duration, and a one-line count of anything older. When the conversation is compacted, the record of what has already run survives.

Cost and status in the chat

While a child runs, the chat shows a card with the specialist’s name and a running status. The parent reads token counts and cost from the child’s own request messages in the JSON stream, collects which tools the child called, and updates the card when it finishes with status, duration, tokens and cost. That cost is added to the task’s total alongside the parent’s own requests, so the figure at the top of the task reflects the whole analysis.

When a specialist needs a package

Sometimes a specialist needs a Python package that is not installed. Specialists are instructed not to install anything themselves. They call request_capability with the package name, reason and a fallback, and the handler requires their completion to relay that request as a structured “capability gap” result; any other completion is rejected until they do.

The orchestrator then dispatches the extended agent, the one layer meant to install packages, whose instructions are to install with version constraints and confirm the package imports before reporting success. The original specialist is dispatched again with "resume": true in its context, in a fresh process, and continues from where it stopped.

What this buys

Each step gets a clean context sized to its task. Failures stay contained in one process. The orchestrator reasons over short structured results instead of raw output. And every step leaves a visible trace of what ran, what it cost and what it returned.

The dispatch handler is agent/src/core/task/tools/handlers/DispatchSpecialistHandler.ts in the Geocluster Research Harness repository. How each specialist is limited to its own tools is covered in a companion piece on tool names as permissions, and the principles behind splitting agents this way in How Not to Build a Slop Cannon.