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 called
  • the {tool} tool should not be called
  • the {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 exist
  • the 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 exist
  • the 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.