ZeroRoot Docs
Missions

Missions

Authoring reference for the mission DSL, DAG model, every node type, every constraint field, validation flow, and patterns you can copy-paste.

A mission is a directed acyclic graph (DAG) of work units that Gibson dispatches against a target. You write a mission file in YAML, CUE, or JSON. You validate it locally and submit it to the daemon. The daemon walks the graph, captures observations, surfaces findings, and emits a structured event stream.

This page is the authoring reference. If you have not run your first mission yet, start with Getting Started. Come back here when you want the full field-by-field tour.

What a mission is

A mission has three structural pieces:

  • A set of nodes, one per work unit. Each node is one of seven NodeType values (agent, tool, plugin, condition, parallel, join, job).
  • A set of edges, directed dependencies between nodes. The orchestrator dispatches a node only when every predecessor has finished.
  • Entry and exit points, node IDs that have no incoming edges (entry) and no outgoing edges (exit). A mission needs at least one of each.

You choose stable string IDs as the keys of the DAG. The same IDs appear in edges.from / edges.to, in entryPoints, exitPoints, and in any node's dependencies list.

Node types

TypeWhat it does
NODE_TYPE_AGENTDispatches an autonomous reasoning step. The agent receives a task, calls tools and plugins, and returns a result.
NODE_TYPE_TOOLCalls a tool's typed RPC directly, no LLM in the loop for this step.
NODE_TYPE_PLUGINInvokes one named method on a multi-method plugin component (e.g. shodan.host_lookup).
NODE_TYPE_CONDITIONBranches based on a CEL expression evaluated over prior node outputs.
NODE_TYPE_PARALLELFans out to multiple sub-nodes concurrently, capped by max_concurrency. Sibling failures are isolated.
NODE_TYPE_JOINBarrier, waits for every node in wait_for to complete, then merges results via MergeStrategy.
NODE_TYPE_JOBOpens a job on a bank of always-on Claude Code and runs the verify loop until the acceptance passes. Ships with epic zeroroot-ai/gibson#1706. See Job node.

Lifecycle states

The daemon advances every mission through this state machine:

StateMeaning
PENDINGDefinition accepted, not yet dispatched.
RUNNINGOrchestrator is walking the DAG.
PAUSEDSuspended at a clean node boundary; resumable from the latest checkpoint.
COMPLETEDEvery exit point reached without a terminal failure.
FAILEDA non-recoverable failure terminated the run.
CANCELLEDA cancel request stopped the run; active grants for in-flight components expire.

The event stream renders these transitions in real time on the mission detail page. See Observability for the trace side.

Top-level fields reference

These fields belong to the root MissionDefinition. The schema is generated from the SDK protos. It lives at src/data/mission-definition.schema.json. Every field below has a description there.

FieldTypeNotes
namestringHuman-readable identity. Required.
descriptionstringOne-paragraph summary. Recommended.
versionstringSemantic version of the definition (e.g. 1.0.0).
target_refstringTarget name or ID. Required at dispatch unless overridden by CreateMissionRequest.target_id.
nodesmap<string, MissionNode>The work units, keyed by node ID.
edgesMissionEdge[]Directed dependencies. Each edge is {from, to, condition?, metadata?}.
entry_pointsstring[]Node IDs with no incoming edges. At least one required.
exit_pointsstring[]Node IDs with no outgoing edges. At least one required.
constraintsMissionConstraintsBaked-in operational limits, see Constraints.
workspaceWorkspaceConfigRepository cloning + workspace knobs for code-touching missions, see Workspace config.
dependenciesMissionDependenciesLists of required agents / tools / plugins; the mission refuses to dispatch if any are missing or denied.
metadatamap<string, string>Free-form labels for filtering and search.
idstringServer-assigned on CreateMissionDefinition. Leave empty when authoring.
sourcestringGit URL the definition was installed from, if any. Daemon-set.
created_at / installed_attimestampDaemon-set on persist.

Per-node fields live on MissionNode. This page documents them under each node type below. All node types share these node-level fields:

FieldTypeNotes
idstringStable identifier; must match the map key.
namestringHuman-readable label rendered in the dashboard.
descriptionstringFree-form context for reviewers.
typeenumOne of the seven NODE_TYPE_* values.
dependenciesstring[]Node IDs that must complete before this node executes.
timeoutduration stringPer-node wall-clock cap (e.g. "10m").
retry_policyRetryPolicyBackoff + retry count, see Retry policy.
reuse_policyReusePolicyScope and re-use across runs, see Reuse policy.
data_policyDataPolicyKnowledge-graph storage + retention.
metadatamap<string, string>Per-node labels.

Per-NodeType config

Each example is a complete, copy-pasteable YAML mission with one node of the type in question. Submit any of them with:

gibson mission validate path/to/mission.yaml
gibson mission submit   path/to/mission.yaml --target example.com

Agent, agent_config

AgentNodeConfig selects an agent component by name and dispatches a Task. The agent calls tools and plugins on the author's behalf. The task's goal, context, and per-task constraints shape what the agent does.

name: webapp-scan-agent
description: Run the webapp-scan agent against a single target.
version: "1.0.0"
target_ref: example.com

nodes:
  scan:
    id: scan
    type: NODE_TYPE_AGENT
    agent_config:
      agent_name: webapp-scan
      task:
        goal: "Enumerate auth surfaces and flag any unauthenticated admin routes."
        context:
          depth:
            string_value: "shallow"
        constraints:
          max_turns: 20
          allowed_tools: ["nuclei", "ffuf"]

entry_points: ["scan"]
exit_points: ["scan"]

agent_config.max_tokens_per_call overrides the mission-level cap (see Per-call token cap cascade).

Tool, tool_config

ToolNodeConfig calls one named tool with a typed input map. Use this node type when the step does not need an LLM in the loop. An example is an nmap scan whose output you only feed forward.

name: port-scan
description: nmap against the target's well-known web ports.
version: "1.0.0"
target_ref: example.com

nodes:
  ports:
    id: ports
    type: NODE_TYPE_TOOL
    tool_config:
      tool_name: nmap
      input:
        target: "example.com"
        ports: "22,80,443,8080,8443"

entry_points: ["ports"]
exit_points: ["ports"]

The input values are strings. Each tool component parses them per its declared schema.

Plugin, plugin_config

PluginNodeConfig calls one method on a multi-method plugin component. A plugin differs from a tool in one way. One plugin advertises several callable methods behind a single component identity.

name: shodan-enrich
description: Look up host metadata via the shodan plugin.
version: "1.0.0"
target_ref: example.com

nodes:
  enrich:
    id: enrich
    type: NODE_TYPE_PLUGIN
    plugin_config:
      plugin_name: shodan
      method: host_lookup
      params:
        host: "example.com"

entry_points: ["enrich"]
exit_points: ["enrich"]

Condition, condition_config

ConditionNodeConfig branches on a CEL expression that is evaluated over prior node outputs. The true_branch and false_branch fields list the downstream node IDs to run for each outcome. The orchestrator skips the branch that is not chosen.

name: triage-on-findings
description: Only enrich when the scan produced findings.
version: "1.0.0"
target_ref: example.com

nodes:
  scan:
    id: scan
    type: NODE_TYPE_AGENT
    agent_config:
      agent_name: webapp-scan

  has_findings:
    id: has_findings
    type: NODE_TYPE_CONDITION
    condition_config:
      expression: "nodes.scan.findings_count > 0"
      language: LANGUAGE_CEL
      true_branch:  ["enrich"]
      false_branch: ["report_empty"]
    dependencies: ["scan"]

  enrich:
    id: enrich
    type: NODE_TYPE_AGENT
    agent_config:
      agent_name: shodan-enrich

  report_empty:
    id: report_empty
    type: NODE_TYPE_AGENT
    agent_config:
      agent_name: empty-report

edges:
  - from: scan
    to: has_findings

entry_points: ["scan"]
exit_points: ["enrich", "report_empty"]

The language field defaults to LANGUAGE_CEL. The unspecified value is treated as CEL for backward compatibility with older documents.

Parallel, parallel_config

ParallelNodeConfig runs sub_nodes concurrently. The max_concurrency field gates the fan-out (0 = unlimited). Sibling failures are isolated. One failed sub-node does not cancel its siblings. Sub-nodes are inline MissionNode values, not references.

name: parallel-recon
description: Fan out three reconnaissance agents at once.
version: "1.0.0"
target_ref: example.com

nodes:
  fanout:
    id: fanout
    type: NODE_TYPE_PARALLEL
    parallel_config:
      max_concurrency: 3
      sub_nodes:
        - id: nmap
          type: NODE_TYPE_AGENT
          agent_config:
            agent_name: nmap-recon
        - id: webcrawl
          type: NODE_TYPE_AGENT
          agent_config:
            agent_name: webcrawl-recon
        - id: shodan
          type: NODE_TYPE_AGENT
          agent_config:
            agent_name: shodan-recon

entry_points: ["fanout"]
exit_points: ["fanout"]

Join, join_config

JoinNodeConfig waits until every node ID in wait_for has finished. Then it merges their results per strategy. JOIN is separate from PARALLEL. A join can merge non-parallel branches.

name: scan-and-merge
description: Two independent scans merged in document order.
version: "1.0.0"
target_ref: example.com

nodes:
  network_scan:
    id: network_scan
    type: NODE_TYPE_AGENT
    agent_config:
      agent_name: nmap-recon

  web_scan:
    id: web_scan
    type: NODE_TYPE_AGENT
    agent_config:
      agent_name: webapp-scan

  merge:
    id: merge
    type: NODE_TYPE_JOIN
    join_config:
      wait_for: ["network_scan", "web_scan"]
      strategy: MERGE_STRATEGY_CONCAT
    dependencies: ["network_scan", "web_scan"]

edges:
  - from: network_scan
    to: merge
  - from: web_scan
    to: merge

entry_points: ["network_scan", "web_scan"]
exit_points: ["merge"]

MergeStrategy values:

ValueMeaning
MERGE_STRATEGY_CONCATPreserve source order in the merged output.
MERGE_STRATEGY_REDUCEBuilt-in reducer (semantics defined per executor).
MERGE_STRATEGY_FIRSTReturn the first source to complete.
MERGE_STRATEGY_LASTReturn the last source to complete.
MERGE_STRATEGY_CUSTOMEvaluate the aggregator CEL expression against the source results.

When strategy is MERGE_STRATEGY_CUSTOM, set aggregator to a CEL expression. The expression sees sources, a map from node ID to that node's result. The expression returns the merged value.

Constraints reference

MissionConstraints declares mission-level operational limits. When you bake the limits into the definition, the mission describes itself. Callers do not have to supply limits out-of-band at dispatch time.

Every numeric field treats zero as unlimited. Set a positive value to enforce a limit.

constraints:
  max_duration: "30m"          # wall-clock cap (Duration string)
  max_tokens: 200000           # cumulative LLM tokens across all agent nodes
  max_cost: 5.00               # cumulative USD across all agent nodes
  max_findings: 50             # stop after this many findings
  severity_threshold: "medium" # drop findings below this severity
  require_evidence: true       # reject findings that omit evidence
  blocked_tools:
    - "rm-rf"
  blocked_domains:
    - "prod.example.com"
  max_turns_per_agent: 30      # per-agent-node turn cap
  allowed_techniques:          # taxonomy ID allowlist (empty = any)
    - "T1190"
  blocked_techniques:          # taxonomy ID blocklist
    - "T1486"
  max_tokens_per_call: 8000    # cap per individual LLM invocation

Field-by-field:

FieldTypeNotes
max_durationDuration stringWall-clock limit for the entire mission ("30m", "2h", "90s"). 0 = no time limit. Minimum 1 minute when set.
max_tokensint64Cumulative LLM token budget across all agent nodes. 0 = unlimited. Minimum 1000 when set.
max_costdoubleCumulative LLM cost ceiling in USD. 0 = unlimited. Minimum $0.01 when set.
max_findingsint32Stop the mission after this many findings. 0 = unlimited.
severity_thresholdstringMinimum severity to record ("low", "medium", "high", "critical"). Empty = accept all.
require_evidenceboolReject findings that omit evidence.
blocked_toolsstring[]Tool names that must not be invoked. Enforced at dispatch time.
blocked_domainsstring[]Network domains agents must not contact. Best-effort, enforced at the tool level.
max_turns_per_agentint32Cap on Observe→Think→Act iterations for any single agent node. 0 = unlimited.
allowed_techniquesstring[]Allowlist of taxonomy attack-technique IDs. Empty = any technique allowed (unless blocked).
blocked_techniquesstring[]Blocklist of taxonomy attack-technique IDs. Wins over allowed_techniques.
max_tokens_per_callint32Cap on tokens consumed per individual LLM invocation. 0 = unlimited.

Per-call token cap cascade

The max_tokens_per_call field cascades from mission to node:

  • Unless the node overrides it, the mission's constraints.max_tokens_per_call applies to every agent / tool / plugin LLM call.
  • Each node's *_config.max_tokens_per_call overrides the mission-level value for that node only.
  • Absence on a node means "cascade from the mission level". A zero on a node means "unlimited from this mechanism".

Dispatch-time override

The constraints baked into a MissionDefinition are the default. At dispatch, a caller may pass CreateMissionRequest.constraints to override them. The daemon merges the two, and dispatch wins on conflict. This lets you ship a strict definition and loosen it for a specific run. You do not edit the definition.

Workspace config

WorkspaceConfig clones repositories into per-mission workspaces that agents access through Harness.Workspace(name). Use it whenever your agents need to read code, run an LSP, or modify files.

workspace:
  repositories:
    - name: app
      url: "https://github.com/example/webapp.git"
      branch: main
      shallow: true
    - name: infra
      url: "git@github.com:example/infra.git"
      branch: main
      credential_name: github-readonly
      depends_on: ["app"]      # clones after `app` finishes
  settings:
    cleanup_on_complete: true  # delete workspace dirs when the mission ends
    use_worktrees: true        # per-agent isolation via Git worktrees
    lsp_enabled: true
    lsp_timeout: "30s"
    base_directory: ""         # daemon uses a temp dir when empty

RepositoryConfig fields:

FieldTypeNotes
namestringUnique workspace name. Required.
urlstringGit URL (HTTPS or SSH). Required.
branchstringBranch to check out. Defaults to the repository's default branch.
shallowboolEnable git clone --depth 1.
credential_namestringReference to a Gibson-managed credential. Optional for public repos.
depends_onstring[]Repository names that must clone first, enables topological ordering for multi-repo missions.

WorkspaceSettings fields:

FieldTypeNotes
base_directorystringWorkspace clone root. Daemon uses a temp directory when empty.
cleanup_on_completeboolDelete workspace directories after the mission ends. Defaults to true at the daemon.
use_worktreesboolPer-agent isolation via Git worktrees, concurrent modifications without conflicts.
lsp_enabledboolStart language servers for code validation.
lsp_timeoutDuration stringCap LSP validation duration.

Validation flow

A mission passes through four validation layers before it starts. Each layer fails closed. An error at any layer aborts the submit.

  1. Schema / structural, gibson mission validate. The CLI parses the file (CUE / YAML / JSON). It validates the structure against the embedded #MissionDefinition schema. Then it runs the same protovalidate rules that the daemon enforces server-side. This layer catches typos, missing required fields, and wrong enum values.

    validate: validation error: - definition.target_ref: value is required
  2. Server-side protovalidate. CreateMissionDefinition runs protovalidate again on the wire. It checks every (buf.validate.field).* annotation declared in the SDK protos. This layer is identical to layer 1. The server does not trust the client.

    invalid argument: nodes[scan].agent_config.agent_name: value is required
  3. Semantic. The daemon's mission service applies platform minimums and cross-field invariants. Confirmed checks include:

    • max_duration < 1 minute → reject (max_duration too short: minimum 1 minute required)
    • max_cost < $0.01 → reject (max_cost too low: minimum $0.01 required)
    • max_tokens < 1000 → reject (max_tokens too low: minimum 1000 tokens required)
    • target_id and definition_id both required on CreateMission
    • The DAG must be acyclic. Every dependencies / wait_for / edge endpoint must resolve to a known node ID.
  4. Authorization. The Gibson permissions layer checks the caller against the action:

    • CreateMissionDefinition requires the writer relation on the tenant.
    • CreateMission (dispatch) requires the member relation on the tenant plus a grant for every component the mission references.
    permission_denied: caller lacks writer on tenant

Run all four locally before submit:

gibson mission validate missions/triage.yaml   # layers 1 + 2 logic
gibson mission render   missions/triage.yaml   # see the proto-shaped JSON
gibson mission submit   missions/triage.yaml --target example.com

Common patterns

Parallel + join fan-out

Run independent recon agents in parallel. Then merge their findings into a single downstream triage step. Pair NODE_TYPE_PARALLEL (to schedule the fan-out) with NODE_TYPE_JOIN (to wait and merge).

nodes:
  recon:
    id: recon
    type: NODE_TYPE_PARALLEL
    parallel_config:
      max_concurrency: 3
      sub_nodes:
        - id: r_nmap
          type: NODE_TYPE_AGENT
          agent_config: { agent_name: nmap-recon }
        - id: r_web
          type: NODE_TYPE_AGENT
          agent_config: { agent_name: webcrawl-recon }
        - id: r_shodan
          type: NODE_TYPE_AGENT
          agent_config: { agent_name: shodan-recon }

  collect:
    id: collect
    type: NODE_TYPE_JOIN
    join_config:
      wait_for: ["recon"]
      strategy: MERGE_STRATEGY_CONCAT
    dependencies: ["recon"]

  triage:
    id: triage
    type: NODE_TYPE_AGENT
    agent_config:
      agent_name: triage
    dependencies: ["collect"]

edges:
  - { from: recon,   to: collect }
  - { from: collect, to: triage  }

entry_points: ["recon"]
exit_points: ["triage"]

Condition branching

Run a cheap probe first. Run the expensive deep-dive only on a hit.

nodes:
  probe:
    id: probe
    type: NODE_TYPE_TOOL
    tool_config:
      tool_name: nmap
      input: { target: "example.com", ports: "443" }

  is_open:
    id: is_open
    type: NODE_TYPE_CONDITION
    condition_config:
      expression: "nodes.probe.findings_count > 0"
      true_branch:  ["deep_scan"]
      false_branch: ["report_clean"]
    dependencies: ["probe"]

  deep_scan:
    id: deep_scan
    type: NODE_TYPE_AGENT
    agent_config: { agent_name: webapp-scan }

  report_clean:
    id: report_clean
    type: NODE_TYPE_AGENT
    agent_config: { agent_name: empty-report }

edges:
  - { from: probe, to: is_open }

entry_points: ["probe"]
exit_points: ["deep_scan", "report_clean"]

Retry policy

Wrap a flaky tool in exponential backoff. RetryPolicy accepts constant / linear / exponential strategies.

nodes:
  flaky_lookup:
    id: flaky_lookup
    type: NODE_TYPE_PLUGIN
    plugin_config:
      plugin_name: shodan
      method: host_lookup
      params: { host: "example.com" }
    retry_policy:
      max_retries: 5
      backoff_strategy: BACKOFF_STRATEGY_EXPONENTIAL
      initial_delay: "1s"
      max_delay: "30s"
      multiplier: 2.0

Reuse policy, skip / rerun / merge

ReusePolicy controls what happens when the configured scope already holds an output. Use it so that expensive recon does not run again on every iteration.

reuseBehavior
skipIf a previous output exists in scope, skip this node and use the cached value.
rerun(default) Always re-execute, even if an output exists.
mergeRe-execute and merge new output into the existing one.
nodes:
  recon:
    id: recon
    type: NODE_TYPE_AGENT
    agent_config: { agent_name: nmap-recon }
    reuse_policy:
      input_scope:  "mission"     # mission_run | mission | global
      output_scope: "global"
      reuse:        "skip"

The input_scope and output_scope fields accept "mission_run", "mission", or "global". The default is "mission". With this default, outputs are reusable across runs of the same mission definition but not across definitions.

Authoring loop

  1. Scaffold. gibson mission new --template webapp-scan missions/scan.yaml. Templates ship with the ADK. See opensource/adk/templates/.
  2. Edit. Pick a node type per the per-NodeType examples. Wire dependencies via edges and dependencies.
  3. Validate. gibson mission validate missions/scan.yaml. Repeat until the CLI prints ok.
  4. Render. If you want to inspect the proto-shaped JSON that the daemon will see, run gibson mission render missions/scan.yaml.
  5. Submit. gibson mission submit missions/scan.yaml --target example.com. The CLI streams the event log the dashboard renders.
  • Getting Started, the fast path from install to first mission.
  • Findings, what finding.submitted events represent and how findings show up in the dashboard.
  • Knowledge graph, what tool output auto-populates as your mission runs.
  • Attack-path belief field, how the engine ranks what it has discovered and picks what to look at next.
  • Observability, traces, metrics, and replay in more detail.
  • Roles & permissions, what controls who can author, dispatch, and replay a mission.
  • CLI reference, every gibson command, including the full gibson mission subcommand surface.

On this page