# Workflows Overview > Learn how BountyHub workflows model bug bounty automation with activities, steps, and triggers. ## BountyHub Workflows Workflows are the way you describe automation in BountyHub. They are written in a Starlark-based language — a small, safe, Python-like dialect — and they describe the way you hunt in a concise, declarative way. Workflows act as if there were a replica of you doing the automated tasks continuously, feeding you information in real time, allowing you to do the best work you possibly can. In bug bounty hunting, being the first one to find a bug is crucial. The faster you find a bug, the higher your chances of being the first to report it and get the bounty. Use workflows to your advantage, and let them work 24/7 for you. ### Workflow Concepts Let's discuss the concepts that are important to understand when working with workflows. #### Workflow A workflow is a single unit of automation. It describes the flow you take manually when hunting for bugs: enumerate subdomains, probe for live hosts, scan them, triage results, and notify yourself. A workflow file is a Starlark program whose final value is a `workflow(...)` definition: ```bhw workflow( name = "recon", activities = [...], ) ``` Most automation tools work in terms of pipelines, where you define a series of steps that execute in order. Execution is broken into stages, and each stage depends on the output of the previous stage. Workflows are different. Workflows are continuous processes that keep running, always adapting to the latest data available. You define how you want to work, and the workflow continuously does it for you. #### Activities A workflow is composed of **activities**. An activity is a named unit of work with its own triggers, conditions, environment, and steps. Think of an activity as one "phase" of your hunting process: ```bhw workflow( name = "subdomain-recon", activities = [ activity( name = "discover", triggers = [trigger.dispatch()], steps = [ step.exec( command = ["subfinder", "-d", config.var("target_domain"), "-o", "subs.txt"], ), ], ), activity( name = "probe", triggers = [trigger.on(discover.succeeded())], steps = [ step.exec( command = ["httpx", "-l", "subs.txt", "-o", "live.txt"], ), ], ), ], ) ``` In this example: - The `discover` activity runs when you **dispatch** it manually (for example, from the UI or CLI). It enumerates subdomains for the target domain stored in configuration. - The `probe` activity triggers automatically whenever `discover` **succeeds**, and probes all discovered subdomains for live HTTP services. Activities within a workflow run independently, driven purely by their triggers. This is what makes workflows continuous rather than linear: an activity can wait for an event, run on a cron schedule, and react to other activities finishing — all in the same definition. #### Steps Steps are the individual units of execution inside an activity. BountyHub provides composable primitives instead of a single "run shell" escape hatch:
| Primitive | Purpose |
|---|---|
step.script
|
Run a shell script (the explicit shell API) |
step.exec
|
Run a process directly from an argv vector |
step.parallel
/
step.spawn
/
step.join
|
Structured concurrency |
step.for_each
|
Bounded data-parallel fan-out |
step.transform
|
Typed data pipelines over scan output |
step.agent
|
AI agent with explicitly granted capabilities |
step.update_dataset
|
Push observations into project datasets |
step.download_dataset
|
Read a project dataset into the workspace |
step.upload_artifact
/
step.download_artifact
|
Share files between activities |
step.module
|
Invoke a reusable module |