Skip to content

The conductor

The conductor is the part of the workbench that runs an agent. It takes a request, applies every check, calls the agent, and returns an envelope. It lives in harness/src/dd_harness/conductor.py as Conductor.invoke.

Every front end calls the conductor. The CLI, the A2A endpoint and the web viewer are thin wrappers around it (Transport and tracing). This means an agent is held to the same rules however it is called.

The conductor does not know about any particular payload class. It treats every agent the same way, using only what the agent declares in its AgentSpec. Adding an agent never requires a change to the conductor.

What the conductor fills in

Some parts of an envelope must not be decided by the agent that produced it. The conductor sets these itself:

  • the invocation identifier and timestamps;
  • the telemetry, including the trace identifier;
  • the grounding mode the agent declared, which the linter then enforces;
  • the hash of the input;
  • the human the invocation acted for, acting_for;
  • the project the run belongs to, project_id, taken from the conversation or, for a delegated child, from its parent's grant (Projects). A request that names one is refused, and the agent is never sent it.

In a project, the conductor also puts the project's facts into a DraftRequest before the input is hashed (dd_harness.project_facts): the latest inventory and the requirements of the repository chosen replace whatever the caller sent, and a slot the project has no value for is removed.

acting_for comes from the caller's transport, not the request. The transport asks its authenticator who the caller acts for and passes the answer to invoke. Until there is sign-in, the authenticator returns the one principal the operator configured, marked assurance: asserted (Policy and identity). The agent is not told who it acts for.

An agent returns only an AgentResult: an outcome, an optional payload, and the evidence it used, plus the model id and token counts if it called a model.

The steps

The conductor first validates the request against the JSON Schema, checks its invocation_id is new, and looks up the agent. A malformed request, an invocation_id that is already stored or still running, or an unknown agent id is a mistake by the caller, so the conductor raises an exception rather than returning an envelope. A stored run is never overwritten.

After that, every problem becomes an outcome in the envelope. The caller, the run store and a reviewer all see the same record.

Step What it checks If the check fails
1. Policy gate Is the agent enabled in the institution's profile? Does it declare the action class the profile assigns it? Does that class need approval? Not enabled: failed, problem agent-not-permitted. Another class: failed, problem action-class-mismatch. Needs approval: referred to a data steward. The agent does not run.
2. Input check Does the agent read this input class? failed, problem input-not-accepted. The agent does not run.
3. Run the agent The agent runs. The conductor checks it returned the payload class it declared. An exception, or a payload of the wrong class: failed, problem agent-error.
4. Grounding linter Is the output based only on what the agent's grounding mode allows? See grounding.md. A succeeded envelope becomes failed, problem grounding-violation. The payload and evidence are removed.
5. Validate and store The envelope is validated against the JSON Schema. In a project, a succeeded run with a Document payload is recorded as a proposed version of a project document, caused by the run. Then the run is stored. An invalid envelope raises an exception; it is a bug in the workbench. A draft whose revises names no version of the project, or one of another kind: failed, problem revision-refused, and nothing is added to the project.

The policy profile is a YAML file in profiles/. The deployment chooses it when the conductor is built (DD_PROFILE, or --profile), and the conductor records it on every envelope as policy_bundle_ref and policy_digest. A request cannot name one. The gate answers only the questions in step 1 (Policy and identity, Policy and identity).

Delegation

An agent in grounding mode delegation, such as an orchestrator, may ask the workbench to run other agents for it (Orchestration). It never calls them directly.

  1. When the conductor runs such an agent, it issues a grant: a random token, valid only while that run is in progress. The token and the harness's own A2A address travel to the agent with the request. workbench serve, dd-harness serve and the app (data-director) use their own host and port as the address; DD_HARNESS_URL overrides it. workbench invoke issues no grant. The app serves the A2A routes without an authenticator, so they accept a callback under a live grant and refuse every other request.
  2. The agent calls ctx.delegate(agent_id, input). The SDK sends a new request back to the harness's A2A endpoint with the token.
  3. Conductor.invoke_delegated checks the token and refuses an unknown or expired one, or an agent delegating to itself. It then runs the request through every step above, as a run of its own, in a trace of its own linked to the parent's. It takes the conversation and acting_for from the grant, whoever sent the callback, is gated by the conductor's own profile, and sets parent_invocation_id.
  4. The conductor records the child's id, agent, version, status and envelope hash against the grant. When the parent finishes, the grant is revoked and those records become the parent envelope's delegations, even if the parent then fails.

A delegated run is never given a grant, so delegation is one level deep. A delegated run refused by the policy gate is a failed child envelope, which the orchestrator relays. A caller may not set parent_invocation_id; invoke refuses a request that does.

What a run leaves behind

Each run appends the envelope to runs/invocations.jsonl and writes a folder, runs/<invocation_id>/, containing the files below. A delegated run gets its own folder and line, written before its parent's.

File Contents
request.json The request as the agent was given it: as received, with the conductor's project_id and, in a project, the project's facts.
envelope.json The envelope returned.
spans.jsonl The OpenTelemetry trace of the run.
grounding.txt The grounding linter's verdict and any violations.
sources.txt The source check's verdict: how many cited records were verified, which were unresolved, and any violations.
crate/ A Process Run Crate: ro-crate-metadata.json, a standard provenance record naming the agent, the human it acted for, the request and the outputs, beside copies of the files it names. It is written after the checks and never read back (Contract).

DD_RUNS_DIR changes where runs are written. DD_WRITE_CRATE=0 stops the Process Run Crate being written.