# Decisions > Make structured decisions over an input state document with step.decision, with validated typed answers from the built-in jevos provider. Some steps cannot be expressed as a shell command or a transform. Deciding whether a finding is in scope, ranking leads, or choosing the next action needs judgement over messy input. `step.decision` makes that judgement an explicit, typed step: it declares an input state, one or more named questions, a provider, and where the validated result is written. ## `step.decision` ```bhw step.decision( state = path.file("findings.json"), questions = { "relevant": decision.noul("Is this finding relevant?"), "severity": decision.choice("Severity?", options = ["low", "medium", "high"]), "priority": decision.score("Priority?", min = 0, max = 3), }, provider = "jevos", endpoint = config.var("jevos_endpoint"), output = path.file("decision.json"), ) ```
Parameter Meaning
state Input document the decision is made over ( path.file(...) ), read at execution time and treated as untrusted data
questions Ordered {name: decision_question} mapping; names are unique
provider Optional decision provider: "jevos" (the default) for the built-in live server, or "mock" for the deterministic in-process provider
output Where the validated result document is written ( path.file(...) )
endpoint jevos endpoint, often config.var(...) (default local server: http://127.0.0.1:8017/v1/systemone )
api_key Optional jevos API key; must be a config.secret(...) reference
model Optional jevos model alias (default jev-latest )
## Question types Questions are created with the `decision` namespace and carry their own validation rules: ```bhw decision.noul("Is this finding relevant?") decision.choice("Severity?", options = ["low", "medium", "high"]) decision.score("Priority?", min = 0, max = 3) ``` - `noul` is binary: jevos returns P(yes), the answer is `P(yes) >= 0.5`, and the probability is carried through as the reported confidence. - `choice` requires a non-empty list of unique, non-empty options; the answer must be one of them. - `score` requires ordered bounds (`min` must not exceed `max`); the answer must be an integer inside them. Invalid definitions are rejected when the workflow is parsed. Invalid provider answers are rejected at execution time, before anything is written. ## Inputs and credentials `state` is a file and is always treated as data, never as instructions. Provider configuration is referenced, never embedded: ```bhw step.decision( state = path.file("findings.json"), questions = {"relevant": decision.noul("Is this finding relevant?")}, provider = "jevos", endpoint = config.var("jevos_endpoint"), api_key = config.secret("jevos_api_key"), output = path.file("decision.json"), ) ``` `api_key` accepts only a `config.secret(...)` reference; literal credentials are rejected so they can never enter the serialized workflow definition. Endpoints and keys are resolved at execution time and are not written to the result document. ## Providers `provider` is optional and selects the decision implementation; it defaults to `"jevos"`. Two providers ship, and the platform standardizes on them: - `"jevos"` is the built-in live provider: a self-hosted [jevos](https://github.com/feder-cr/jev) decision server, reached over HTTP at `endpoint` (default `http://127.0.0.1:8017/v1/systemone`). jevos answers yes/no (`noul`), multiple-choice, and score questions on a laptop CPU and returns a probability for each answer. Point `endpoint` at your jevos instance and, if you set `JEV_API_KEY` on it, supply the same key through `config.secret(...)`. - `"mock"` is deterministic and in-process. It needs no endpoint or credential and is what the test suite uses, so decision steps stay hermetic. Adding a provider is a code change, not a configuration value: the engine rejects any other `provider` name rather than implying a provider zoo. Two jevos limits surface as parse-time or step errors: a `choice` needs 2–26 options and a `score` needs 2–10 levels, so keep score bounds within a ten-point span. ## The result document The validated result is written to `output` as JSON: a mapping from question name to a typed answer. ```json { "answers": { "relevant": {"type": "noul", "value": true, "probability": 0.82}, "severity": {"type": "choice", "value": "high"}, "priority": {"type": "score", "value": 2} }, "order": ["relevant", "severity", "priority"] } ``` Downstream steps consume the file like any other workspace artifact: ```bhw step.exec(command = ["jq", "-r", ".answers.severity.value", "decision.json"]) ``` ## Trust boundary A decision result is model-produced data, not proof. Every answer is validated against the question that produced it before the document is written, but: - a self-reported `probability` is not a calibrated probability, and - a `choice` or `score` answer is a judgement about the input, not a verified fact. Treat the document as one more input to validate downstream, not as an authority. Because the questions and their bounds are declared in the workflow, the result stays typed and reviewable instead of collapsing into free text. ## A security-triage workflow ```bhw triage = activity( name = "triage", triggers = [trigger.dispatch()], steps = [ step.exec(command = ["nuclei", "-l", "live.txt", "-jsonl", "-o", "findings.json"]), step.decision( state = path.file("findings.json"), questions = { "relevant": decision.noul("Is this finding relevant to the target?"), "severity": decision.choice( "How severe is the finding?", options = ["low", "medium", "high", "critical"], ), "priority": decision.score("Triage priority?", min = 0, max = 3), }, provider = "jevos", endpoint = config.var("jevos_endpoint"), output = path.file("decision.json"), ), step.exec(command = ["sh", "-c", "jq -r '.answers.priority.value' decision.json > priority.txt"]), ], ) workflow(name = "triage", activities = [triage]) ```