Skip to main content

Advanced 4 - Behavior engine

System prompts are guidelines. They give the model intent but they do not enforce anything. When the model decides to edit a file without reading it first, the prompt did not stop it; only the model's discipline did.

The behavior engine is the runtime layer that turns guidelines into rules. It watches every tool call as it happens, evaluates a list of declarative rules against the call's parameters and the session state, and injects corrections back into the model's context when a rule fires. The model sees the warning on the next turn and (usually) corrects course.

Six built-in profiles ship preinstalled (dev, coding, research, data, creative, assistant). Each is a curated set of rules tuned to a working style. You can also write custom rules.

What the rules look like

A rule is a name plus a trigger. Some rules from the coding profile:

RuleTriggers when…Effect
read_before_editThe agent calls Edit on a file it has not ReadWarns + injects "call Read first"
verify_after_editThe agent calls Edit and then doesn't Read againReminds the agent to verify
test_after_changesN+ edits without a test runReminds to run tests
no_bash_for_filesThe agent uses Bash("cat file") instead of ReadWarns + suggests the right tool
confirm_destructiveThe agent attempts rm -rf, DROP TABLE, etc.Blocks until the user approves
delegate_complexOne specialist tries to do a 5+ tool task aloneReminds about Agent() dispatch

The full list is in the behavior engine reference.

The YAML

Save this as behavior-bot.yaml. The agent has free filesystem access; the behaviour engine is the only thing keeping it tidy.

app:
app_id: behavior-bot
name: Behavior Bot
version: "1.0"

runtime:
mode: conversation
workdir_mode: auto
max_turns: 6
timeout: 90

agents:
- id: main
role: assistant
brain:
provider: deepseek
model: deepseek-chat
backend: openai_compat
credential:
ref: deepseek_main
scope: per_user
provider: deepseek
config:
api_key: "{{env.DEEPSEEK_API_KEY}}"
base_url: https://api.deepseek.com/v1
temperature: 0
max_tokens: 400
system_prompt: |
You are a code-editing assistant. Use Read, Edit, and Bash.
Reply concisely.

tools:
modules:
filesystem: {}
shell: {}
memory: {}
capabilities:
default_policy: auto

security:
behavior:
profile: coding
classify_turns: false

The new piece is security.behavior.profile: coding. That single line activates 14 rules tuned to code-editing workflows. classify_turns: false keeps the optional classifier brain off (covered below).

Setup

Create a workspace with one tiny Python file:

mkdir -p ./behavior-ws
printf 'def add(a, b):\n return a + b\n' \
> ./behavior-ws/code.ts

Now ask the agent to edit without reading, on purpose, to exercise the engine.

Live transcript

User message:

> Edit code.ts: replace "return a + b" with "return a + b + 1".
Do NOT read the file first; just call Edit directly.

The session log shows what actually happened. Three tool calls, four turns:

1. tool_call  Edit (file_path=code.ts, old="return a + b",
new="return a + b + 1")
-> success
2. tool_call Read (file_path=code.ts)
-> success
3. tool_call Edit (file_path=code.ts, old="return a + b + 1 + 1",
new="return a + b + 1")
-> success

Final reply:

Now it's correct. The file already had "return a + b + 1" before
I started, so the first edit doubled it. It's now fixed back to
"return a + b + 1".

The user said "do not read first". The agent obliged on the first edit (call 1). Then verify_after_edit from the coding profile injected a reminder into the agent's context, which is why call 2 is a Read even though the user never asked for one.

The agent then read the file, hallucinated that it now contained + 1 + 1 (it didn't), and made a corrective edit. The model behaved imperfectly - what mattered for the demo is the engine forced the verification step that the user explicitly asked to skip.

The final file content on disk:

def add(a, b):
return a + b + 1

Without the behaviour engine, the agent would have edited blindly and stopped after one tool call. The engine added two more turns of self-checking.

Custom rules

Profiles cover the common cases. For app-specific rules, declare them inline:

security:
behavior:
profile: coding # start from coding's defaults
rules: # toggle individual built-ins
test_after_changes: false # this app has no tests
custom: # add your own
- id: protect_migrations
rule: "Never modify files under migrations/ without explicit approval."
trigger: edit
condition:
type: contains
field: file_path
substring: "migrations/"
action: block # block | warn | remind

The fields are:

  • trigger - the tool the rule attaches to (edit, bash, read, *, …).
  • condition - what the rule looks at. contains, matches (regex), not_in, plus a Python-expression escape hatch.
  • action - block stops the call entirely, warn lets it through but injects a warning, remind injects a hint after execution.

The classifier (optional)

The behaviour engine also supports a classifier brain - a small, cheap LLM (typically Haiku-class) that runs before the main agent acts and decides which approach makes sense for the turn:

security:
behavior:
profile: coding
classify_turns: true
classifier:
frequency: every_turn # every_turn | first_turn | manual
timeout: 15
approaches: [direct, plan_and_confirm, delegate]
brain:
provider: deepseek
model: deepseek-chat # use a small / cheap model
backend: openai_compat
credential:
ref: deepseek_main
scope: per_user
provider: deepseek
config:
api_key: "{{env.DEEPSEEK_API_KEY}}"
base_url: https://api.deepseek.com/v1

The classifier inspects the user message, picks an approach (direct, plan_and_confirm, delegate), and prepends that hint to the main agent's system prompt for the turn. Useful when the same agent should plan-then-confirm for complex tasks but fire and forget for trivial ones.

The classifier costs one small LLM call per turn. Skip it (classify_turns: false) if every turn is the same shape.

When the engine earns its keep

  • Code-editing apps. read_before_edit, verify_after_edit, test_after_changes, no_bash_for_files collectively turn a hasty model into a methodical one.
  • Database / data-mutation apps. confirm_destructive (BLOCK action) intercepts DROP TABLE style statements and forces an explicit user OK.
  • Multi-agent apps. delegate_complex and delegate_large_reads push the coordinator to dispatch instead of doing everything in-thread.
  • Custom organisational rules. "Never edit migrations/", "Always cite sources", "Limit blast radius to one repo at a time". One rule, one block of YAML.

Going further

  • Full reference (every built-in rule, the seven-gate flow, state tracking, cooldowns): Behavior Engine.
  • The behavior module API (when you want to author rules in Python instead of YAML): behavior module.
  • Companion to the capability gate covered in tutorial 7. Capabilities decide what's callable; the behaviour engine decides how it should be called.