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
NodeTypevalues (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
| Type | What it does |
|---|---|
NODE_TYPE_AGENT | Dispatches an autonomous reasoning step. The agent receives a task, calls tools and plugins, and returns a result. |
NODE_TYPE_TOOL | Calls a tool's typed RPC directly, no LLM in the loop for this step. |
NODE_TYPE_PLUGIN | Invokes one named method on a multi-method plugin component (e.g. shodan.host_lookup). |
NODE_TYPE_CONDITION | Branches based on a CEL expression evaluated over prior node outputs. |
NODE_TYPE_PARALLEL | Fans out to multiple sub-nodes concurrently, capped by max_concurrency. Sibling failures are isolated. |
NODE_TYPE_JOIN | Barrier, waits for every node in wait_for to complete, then merges results via MergeStrategy. |
NODE_TYPE_JOB | Opens 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:
| State | Meaning |
|---|---|
PENDING | Definition accepted, not yet dispatched. |
RUNNING | Orchestrator is walking the DAG. |
PAUSED | Suspended at a clean node boundary; resumable from the latest checkpoint. |
COMPLETED | Every exit point reached without a terminal failure. |
FAILED | A non-recoverable failure terminated the run. |
CANCELLED | A 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.
| Field | Type | Notes |
|---|---|---|
name | string | Human-readable identity. Required. |
description | string | One-paragraph summary. Recommended. |
version | string | Semantic version of the definition (e.g. 1.0.0). |
target_ref | string | Target name or ID. Required at dispatch unless overridden by CreateMissionRequest.target_id. |
nodes | map<string, MissionNode> | The work units, keyed by node ID. |
edges | MissionEdge[] | Directed dependencies. Each edge is {from, to, condition?, metadata?}. |
entry_points | string[] | Node IDs with no incoming edges. At least one required. |
exit_points | string[] | Node IDs with no outgoing edges. At least one required. |
constraints | MissionConstraints | Baked-in operational limits, see Constraints. |
workspace | WorkspaceConfig | Repository cloning + workspace knobs for code-touching missions, see Workspace config. |
dependencies | MissionDependencies | Lists of required agents / tools / plugins; the mission refuses to dispatch if any are missing or denied. |
metadata | map<string, string> | Free-form labels for filtering and search. |
id | string | Server-assigned on CreateMissionDefinition. Leave empty when authoring. |
source | string | Git URL the definition was installed from, if any. Daemon-set. |
created_at / installed_at | timestamp | Daemon-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:
| Field | Type | Notes |
|---|---|---|
id | string | Stable identifier; must match the map key. |
name | string | Human-readable label rendered in the dashboard. |
description | string | Free-form context for reviewers. |
type | enum | One of the seven NODE_TYPE_* values. |
dependencies | string[] | Node IDs that must complete before this node executes. |
timeout | duration string | Per-node wall-clock cap (e.g. "10m"). |
retry_policy | RetryPolicy | Backoff + retry count, see Retry policy. |
reuse_policy | ReusePolicy | Scope and re-use across runs, see Reuse policy. |
data_policy | DataPolicy | Knowledge-graph storage + retention. |
metadata | map<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:
| Value | Meaning |
|---|---|
MERGE_STRATEGY_CONCAT | Preserve source order in the merged output. |
MERGE_STRATEGY_REDUCE | Built-in reducer (semantics defined per executor). |
MERGE_STRATEGY_FIRST | Return the first source to complete. |
MERGE_STRATEGY_LAST | Return the last source to complete. |
MERGE_STRATEGY_CUSTOM | Evaluate 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:
| Field | Type | Notes |
|---|---|---|
max_duration | Duration string | Wall-clock limit for the entire mission ("30m", "2h", "90s"). 0 = no time limit. Minimum 1 minute when set. |
max_tokens | int64 | Cumulative LLM token budget across all agent nodes. 0 = unlimited. Minimum 1000 when set. |
max_cost | double | Cumulative LLM cost ceiling in USD. 0 = unlimited. Minimum $0.01 when set. |
max_findings | int32 | Stop the mission after this many findings. 0 = unlimited. |
severity_threshold | string | Minimum severity to record ("low", "medium", "high", "critical"). Empty = accept all. |
require_evidence | bool | Reject findings that omit evidence. |
blocked_tools | string[] | Tool names that must not be invoked. Enforced at dispatch time. |
blocked_domains | string[] | Network domains agents must not contact. Best-effort, enforced at the tool level. |
max_turns_per_agent | int32 | Cap on Observe→Think→Act iterations for any single agent node. 0 = unlimited. |
allowed_techniques | string[] | Allowlist of taxonomy attack-technique IDs. Empty = any technique allowed (unless blocked). |
blocked_techniques | string[] | Blocklist of taxonomy attack-technique IDs. Wins over allowed_techniques. |
max_tokens_per_call | int32 | Cap 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_callapplies to every agent / tool / plugin LLM call. - Each node's
*_config.max_tokens_per_calloverrides 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:
| Field | Type | Notes |
|---|---|---|
name | string | Unique workspace name. Required. |
url | string | Git URL (HTTPS or SSH). Required. |
branch | string | Branch to check out. Defaults to the repository's default branch. |
shallow | bool | Enable git clone --depth 1. |
credential_name | string | Reference to a Gibson-managed credential. Optional for public repos. |
depends_on | string[] | Repository names that must clone first, enables topological ordering for multi-repo missions. |
WorkspaceSettings fields:
| Field | Type | Notes |
|---|---|---|
base_directory | string | Workspace clone root. Daemon uses a temp directory when empty. |
cleanup_on_complete | bool | Delete workspace directories after the mission ends. Defaults to true at the daemon. |
use_worktrees | bool | Per-agent isolation via Git worktrees, concurrent modifications without conflicts. |
lsp_enabled | bool | Start language servers for code validation. |
lsp_timeout | Duration string | Cap 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.
-
Schema / structural,
gibson mission validate. The CLI parses the file (CUE / YAML / JSON). It validates the structure against the embedded#MissionDefinitionschema. 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 -
Server-side protovalidate.
CreateMissionDefinitionruns 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 -
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_idanddefinition_idboth required onCreateMission- The DAG must be acyclic. Every
dependencies/wait_for/ edge endpoint must resolve to a known node ID.
-
Authorization. The Gibson permissions layer checks the caller against the action:
CreateMissionDefinitionrequires 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.
reuse | Behavior |
|---|---|
skip | If a previous output exists in scope, skip this node and use the cached value. |
rerun | (default) Always re-execute, even if an output exists. |
merge | Re-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
- Scaffold.
gibson mission new --template webapp-scan missions/scan.yaml. Templates ship with the ADK. Seeopensource/adk/templates/. - Edit. Pick a node type per the per-NodeType
examples. Wire dependencies via
edgesanddependencies. - Validate.
gibson mission validate missions/scan.yaml. Repeat until the CLI printsok. - Render. If you want to inspect the proto-shaped JSON that the
daemon will see, run
gibson mission render missions/scan.yaml. - Submit.
gibson mission submit missions/scan.yaml --target example.com. The CLI streams the event log the dashboard renders.
Related
- Getting Started, the fast path from install to first mission.
- Findings, what
finding.submittedevents 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
gibsoncommand, including the fullgibson missionsubcommand surface.
Jobs
A job is the unit of work a bank member holds. Send one from the console, give it repositories and acceptance, answer its questions, and close it with a verdict.
Job node
The job node drives a job on a bank from a mission. Its executor runs the verify loop until the acceptance passes or the passes run out.