Private Beta
.md

BountyHub Docs

Agents

AI agent steps with explicitly granted capabilities: prompt, provider, model, typed context list, tools, and output contracts.

step.agent is a first-class execution primitive for AI agents inside workflows. Use it for work that needs judgment rather than a fixed command: triaging nuclei findings, deduplicating noisy scan results, summarizing what changed on a target since last week.

#The Principle: Explicit Capability Granting

An agent receives only what you explicitly give it:

  • prompt — its instructions.
  • context — named resources it may read.
  • tools — the capabilities it may act with (shell, MCP servers).
  • output — the file it must write its result to, and in what format.
  • mode — whether it runs unattended, asks before side effects, or plans first.
  • iterations — an optional cap on the agent's loop.

Nothing from the workflow leaks in implicitly. This makes agent steps safe to reason about: you always know exactly what an agent can see and do.

BHW

provider is required: every agent names the project-enabled provider key it runs with via provider.key("provider/key-name"). The key must be enabled for the project; otherwise the editor reports an unknown_provider_key diagnostic and the run fails. model is optional and only selects which configured model to use.

#Parameters

#prompt

The instructions for the agent. A plain string, or a function evaluated at runtime with the run context (same as step.script content):

BHW

#provider and model

provider is mandatory and selects a stored API key; key values never appear in the workflow:

BHW

The referenced key must be enabled for the project (the editor flags other references with an unknown_provider_key diagnostic). model is optional:

BHW

The name selects which configured model to use; the workflow stores only the reference.

#context

A list of typed items built with agent.context.* (a dict is rejected). Each item pairs an LLM-facing description with a value passed by reference — contents are never injected wholesale into the model context:

BHW
ConstructorValueMeaning
agent.context.text(description, value)string / input / config.*Inline text, resolved at execution time
agent.context.path(description, path)path.file / path.directory / path.globFile access by reference
agent.context.artifact(description, artifact)step.upload_artifact(...) result or <activity>.artifacts.<name>Artifact by reference
agent.context.dataset(description, dataset)dataset.table("name")Dataset table by reference

Before the agent starts, every path, artifact and dataset item is materialized into its workspace, and the preamble tells the agent where to find each one:

  • agent.context.path — used in place (it is already a workspace path).
  • agent.context.artifact — downloaded and extracted under context/<activity>/<artifact>/.
  • agent.context.dataset — exported to context/<table>.jsonl, one record per line. The export is bounded (1000 rows / 4 MiB); when it is cut short the preamble says so, so the agent treats it as a subset rather than the whole table.

Declaring any of them also grants the agent the read_file and list_dir tools, so it can actually fetch what you gave it instead of only being told it exists. agent.context.text is resolved and inlined into the preamble instead.

Declaration order is preserved. Duplicate handling aside, keep descriptions specific: they are the agent's only map to what each reference contains.

#tools

Grant capabilities explicitly. Without them, the agent can only read its context and produce text.

GrantMeaning
tool.shell()May run shell commands
mcp.server(name)May use an already-configured MCP server (e.g. "github")
tool.task()May delegate work to the specialized subagents declared in harness (see below)

MCP references point at connections configured outside the workflow; credentials never appear in your workflow file. Grant the minimum needed: a triage agent usually only needs read access to context plus one MCP server, while a report-drafting agent may need nothing but its context.

#Specialized agents (harness)

A single agent runs every delegated task on its own model. When some work is easy (reconnaissance, parsing, triage) and some is hard (exploitation, reporting), that wastes money. Declare a harness so the agent can delegate through tool.task() to specialized subagents, each with its own provider key and model:

BHW
  • agent.harness([...]) declares the delegation targets; the agent reaches them only when tool.task() is granted.
  • agent.specialist.recon(...), .exploit(...), .triage(...) and .report(...) — one method per builtin role. Each inherits that role's preamble and tools; declaring the same role twice is rejected.
  • Each specialist's provider and model default to the step's, so a specialist only names what it overrides. provider may point at a different key than the step's; every referenced key must be enabled for the project.
  • Subagents cannot delegate further; declaring tool.task() inside a specialist's tools is rejected.

Every specialist provider key must be enabled for the project, exactly like the step's own provider. Delegation is bounded — subagents run at depth one and return distilled results, not transcripts.

#output

Required. Declares where the agent writes its final response and in what format. The file becomes an ordinary workspace resource, so later steps read it directly, push it into a dataset, or publish it as an artifact:

BHW
formatThe runner requiresUse for
"text" (default)nothingReports, summaries, prose
"json"the whole response is one valid JSON valueA single structured result
"jsonl"each non-empty line is a JSON objectRecord streams that feed transform.jsonl() and step.update_dataset

The format is enforced at run time: a response that does not satisfy it fails the step (unless allow_failure = True). The runner also tells the agent the destination file and the required format before it starts.

Chaining off an agent's output is just ordinary file handling:

BHW

#mode

How the agent handles approvals and side effects. Defaults to auto.

BHW
modeBehaviour
agent.mode.autoRun to completion without asking. Safe for unattended/scheduled runs.
agent.mode.interactivePause and ask before side-effectful actions (shell commands, etc.). Needs a human to answer.
agent.mode.planInvestigate read-only, present a plan, and wait for approval before running anything that changes state.

auto is the default because a cron-triggered workflow has nobody to answer an approval prompt. Choose interactive for attended runs and plan when you want to review the agent's intended steps first.

In plan mode the agent's mutating tools are blocked until it calls present_plan and the user approves the plan; read-only tools stay available throughout. On approval the block lifts and the same session continues with the step's full capabilities — the agent keeps its context across the decision. On rejection the step fails.

#iterations

The maximum number of agent loop iterations before the step fails. Defaults to unlimited.

BHW

Use a cap to bound cost and catch a looping agent; each iteration is one model turn (which may include several tool calls).

#Realistic Patterns

#Triage scanner noise

Nuclei on a wide surface produces noise. Let an agent filter before you ever see it:

BHW

#Investigate a suspicious change

Combine scheduled diffing with an agent that investigates:

BHW

#Agents the Platform Does Not Own

Not every agent is a step.agent. You can run any agent CLI in a shell step (step.script/step.exec). When such an agent needs a human decision, the runner has already provisioned the way to ask: it installs the bh CLI on the step's PATH and exports the per-step endpoint as BOUNTYHUB_ASK_URL.

sh

bh ask posts the question to the platform, blocks until the operator answers (the run parks as WAITING_FOR_INPUT meanwhile), and prints the answer. It needs no token, so it works in a step that has none. A declined request exits with status 1; use bh --json ask ... when the step needs the full answer (text, confirmed, declined) — for example to branch on the confirmed flag of a --kind confirm question.

BHW

For the agent to discover this, the runner writes an AGENTS.md in the step workspace (only when one is not already there) describing bh ask, and hosts a loopback MCP server that exposes the same decision as the request_user_input tool. An agent that speaks MCP can call it directly.

#Platform modules for agent CLIs

You do not have to hand-roll the step.exec wrapper. The platform ships ready-made modules for the common agent CLIs, so you invoke them with step.module using the same prompt → output shape as step.agent:

ModuleWrapsExtra inputs
bountyhub/claudeClaude Code (claude -p)model, permission_mode, tools, allowed_tools, disallowed_tools, append_system_prompt, max_turns, output_format
bountyhub/codexOpenAI Codex (codex exec)model, sandbox, output_schema
bountyhub/hermesHermes Agent (hermes chat -q)model, provider, skills
bountyhub/opencodeopencode (opencode run)model (provider/model), agent, config, permission

Each one takes a required prompt, runs in its own isolated workspace (only its declared file/directory inputs are copied in; files from earlier steps are not visible unless passed explicitly), and declares two file outputs: output (the agent's final response) and log (the CLI's stderr transcript). Credentials are optional secret inputs bound from config.secret(...) — e.g. anthropic_api_key, openai_api_key, openrouter_api_key — mapped to the CLI's conventional environment variables. When you do not bind them, the CLI falls back to its own auth store, so the runner host must have the CLI on PATH.

BHW

Because the modules are ordinary step.module invocations, their outputs chain like any other step. A read-only Codex triage pass, for example:

BHW

The safety defaults keep unattended runs non-destructive: Codex starts in a read-only sandbox (pass sandbox = "workspace-write" to allow edits), Claude Code gets no bypassPermissions, Hermes one-shot sessions deny dangerous commands, and the opencode module never passes --auto. Configure permissions per module through the CLI-specific inputs (for opencode, config / permission; for Codex, sandbox).

#Notes

  • Agent steps participate in the normal lifecycle, including waiting for input (WAITING_FOR_INPUT) when the platform asks them something mid-run.
  • step.agent accepts name, allow_failure, and timeout like other steps, but it accepts neither id nor cond.
  • Secrets are safest kept out of prompts entirely; reference configuration via config.* when a tool or MCP server needs credentials — resolution happens at execution time and values are never serialized into your workflow.