Skip to content

Writing Directives

Directive Structure

Every agent-directive node needs:

{
"type": "agent-directive",
"id": "unique-id",
"directive": "What the agent should do",
"completionCondition": "When the task is complete",
"inputSchema": {/* expected response structure */},
"connections": { "success": "next-node" }
}

Writing Clear Directives

Be Specific

❌ Bad:

"Fix the bugs"

✅ Good:

"Fix TypeScript compilation errors in src/auth/.\n\nSteps:\n1. Run tsc --noEmit\n2. Fix each error\n3. Verify compilation succeeds"

Include Context

Use template variables:

"Implement step {{current_step_index}} of {{total_steps}}: {{current_step_name}}\n\nExpected outcome: {{expected_outcome}}"

Specify Output Requirements

"Run tests and report results.\n\nRequired output format:\n- Total tests\n- Passed count\n- Failed count\n- List of failed test names"

Completion Conditions

What Makes a Good Condition

  • Measurable: Can be verified objectively
  • Specific: Clear pass/fail criteria
  • Complete: All requirements covered

Examples

❌ Vague:

"Task is done"

✅ Specific:

"All tests pass (npm test shows 0 failures)"

✅ Multi-criteria:

"Implementation complete:\n- Feature works as specified\n- Tests added and passing\n- No TypeScript errors"

Agents will claim completion when they believe the condition is met. Make conditions unambiguous to prevent premature completion.

Using InputSchema Effectively

Require Evidence

{
"inputSchema": {
"properties": {
"task_completed": { "type": "string", "enum": ["yes", "no"] },
"evidence": {
"type": "string",
"description": "Concrete proof: command output, test results, etc."
}
},
"required": ["task_completed", "evidence"]
}
}

Force Explicit Choices

{
"inputSchema": {
"properties": {
"quality_check": {
"type": "string",
"enum": ["pass", "fail"],
"description": "Did implementation meet quality standards?"
}
},
"required": ["quality_check"]
}
}

Route on the Answer

An enum answer usually decides where the run goes next. Put the cases on the same node, so the step that has the evidence is the step that decides:

{
"id": "review",
"type": "agent-directive",
"directive": "Review the change against the acceptance criteria.",
"completionCondition": "Verdict recorded with the findings that support it",
"inputSchema": {
"type": "object",
"globalInputs": ["review_verdict"],
"properties": {
"findings": { "type": "string" }
},
"required": ["review_verdict"]
},
"expressions": ["review_round = review_round + 1"],
"cases": [
{
"when": { "operator": "gte", "left": { "contextPath": "review_round" }, "right": 3 },
"output": "exhausted"
},
{
"when": {
"operator": "eq",
"left": { "contextPath": "review_verdict" },
"right": "blocked"
},
"output": "blocked"
}
],
"connections": {
"success": "merge",
"blocked": "fix-issues",
"exhausted": "escalate",
"error": "handle-error"
}
}

review_verdict and review_round are declared in the workflow variableRegistry, so both are readable by bare name; a node-local output is read as review.findings. The expressions run after the answer is validated and before the cases, so a case can read what they computed. Cases are tried in order and success is the fallback — a passing verdict inside the round budget goes to merge. error and timeout are reserved for control flow: no case may name them, and they need none.

Collect Structured Data

{
"inputSchema": {
"properties": {
"test_results": {
"type": "object",
"properties": {
"total": { "type": "number" },
"passed": { "type": "number" },
"failed": { "type": "number" }
},
"required": ["total", "passed", "failed"]
}
},
"required": ["test_results"]
}
}

Rules Are Outcomes, and Each Carries Its Reason

Where a directive states a rule — what a plan must be, what a review may block on, what evidence counts — state the outcome the result must satisfy, and say why the opposite is bad.

An outcome can be judged against a case its author never imagined; a procedure can only be replayed, so it is right exactly where the author’s picture holds and quietly wrong everywhere else. The reason is what lets an executor apply a rule to an unforeseen case instead of guessing, and what lets a reviewer argue with the rule on the merits rather than on whether it was obeyed.

Where a body of criteria is long enough to be walked item by item, open it by saying that a rule which does not apply to the current change is met and needs nothing produced to prove it inapplicable — otherwise it turns back into a checklist.

❌ Procedure:

"STEP 1: read the plan. STEP 2: check every item has a verification. STEP 3: report the count."

✅ Outcome with its reason:

"Every unit names the evidence that would accept it, in observable terms.\n\nWhy: acceptance criteria written as adjectives — robust, clean, complete — cannot be met or refused, so they end up decided by whoever is more insistent."

Raising the voice is not a substitute: MANDATORY, NEVER and CRITICAL add no state of the world in which the rule holds or fails. See Directive as a Drill Order.

Directive Patterns

Sequenced Work

Give the order only where the order is a real dependency, and say what makes it one:

"Migrate the schema before switching the reader, because the reader rejects rows the old schema still produces.\n\nReport the state after each of the two, with the command you used."

Conditional Instructions

"{{#if has_tests}}Run test suite: {{test_command}}{{else}}No tests configured, skip testing{{/if}}"

Verification Requirements

"Verify implementation:\n\n- [ ] Feature works as specified\n- [ ] Edge cases handled\n- [ ] Error messages clear\n\nProvide evidence for each checkbox."

Common Mistakes

Too Much Freedom

❌ Problem:

"Make the code better"

✅ Solution:

"Refactor auth module:\n- Extract validation logic to separate function\n- Add error handling for null inputs\n- Add JSDoc comments"

Missing Context

❌ Problem:

"Fix the error"

✅ Solution:

"Fix error in {{file_path}}:\n\nError message: {{last_error}}\nIteration: {{current_iteration}}"

Avoidable Routing Scaffolding

A condition or expression node whose only job is to route or count what the preceding directive already knows is avoidable routing scaffolding: it adds a hop the reader must follow and a second place the decision can drift from its evidence. Fold it into the directive as cases or an expressions entry — and keep a standalone node where the decision is genuinely shared, reads state no single node produced, or earns a named place in the route.

❌ Problem:

[validate] → [route-validation] → [fix] / [next]

✅ Solution:

[validate, cases on issues_count] → [fix] / [next]

No Success Criteria

❌ Problem:

"completionCondition": "Done"

✅ Solution:

"completionCondition": "npm test passes with 0 failures, npm run build completes without errors"

Read your directives from the agent’s perspective. Would you know exactly what to do and when you’re done?

See Also