17 Built-in Step Definitions
BDD only works if the assertions are easy to write.
Tactus ships with a library of built-in step definitions so your specs can focus on intent (policy and invariants) rather than “glue code”.
This chapter is a quick field guide to the steps you’ll use most often.
17.1 Tool Assertions
Tools are a natural audit surface for agent workflows. Common steps:
the {tool} tool should be calledthe {tool} tool should not be calledthe {tool} tool should be called exactly {n} time(s)the {tool} tool should be called at least {n} time(s)the {tool} tool should be called with {param}={value}
In practice, “should not be called” is one of your most valuable invariants.
Example:
Then the send_email tool should not be called
And the finalize_recap tool should be called
17.2 State and Stage Assertions
State is how you encode “what happened” in a durable workflow. Stages are how you make progress legible.
Useful steps include:
the state {key} should existthe state {key} should be {value}the stage should be {stage}the stage should transition from {a} to {b}
Example:
Then the state message_id should exist
And the stage should be complete
17.3 Output Assertions
Outputs are your contract with callers. Steps include:
the output {key} should existthe output {key} should be {value}the output {key} should match pattern "{regex}"
Pattern matching is especially useful for “human-ish” text where exact matches are brittle.
17.4 Running the Procedure (When Steps)
Most specs follow the same shape:
Given the procedure has started
When the procedure runs
Then the procedure should complete successfully
If your workflow is agent-driven (multi-turn), you’ll also see:
When the {agent} agent takes turns
In both cases, Tactus executes the procedure as written; your spec is asserting what happened.
17.5 Setting Inputs in Scenarios
You can set procedure inputs in Gherkin:
Given the input recipient_name is "Sam"
And the input target_count is 3
When the procedure runs
Then the state counter should be 3
This is how you test different branches without creating separate files.
17.6 When to Write Custom Steps
Built-in steps cover a lot. Add custom steps only when:
- you need a compound assertion (e.g., “action items should be non-empty when notes contain action language”)
- you need to inspect a complex nested structure
- you want a reusable domain-specific check across many specs
Custom steps can be defined in Lua using step("...", function(...) ... end) inside your .tac file. Keep them deterministic and small—custom steps are test code, not workflow code.
17.7 How It Connects to the Running Example
In Part II, we already used built-in steps to assert things like:
- “send_email is called exactly once”
- “state.message_id exists”
- “stage is complete”
In this part, we’ll use the same step library to make our safety and reliability policies executable.