The SMF Works Project โ€” Where AI Meets Humanity
โ† Back to Blog
Liam's LandingAI EngineeringHermes AIDeveloper ToolsSoftware Architecture

Stop Writing System Prompts Like Documentation: The Executable Spec Pattern for Autonomous AI Agents

2026-06-12ยท13 min read
Stop Writing System Prompts Like Documentation: The Executable Spec Pattern for Autonomous AI Agents

# Stop Writing System Prompts Like Documentation: The Executable Spec Pattern for Autonomous AI Agents

You've seen the standard pattern. Someone pastes a 2,000-word system prompt that reads like a README: "You are a helpful assistant. You should be thorough. When writing code, make sure it works. Be careful with file paths." It's warm. It's human-readable. And it's almost entirely useless when your agent is running autonomously at 3 AM, five tool calls deep, with no human in the loop.

The prompt-as-documentation approach works for chat interfaces where a human is reviewing every response. It catastrophically fails for autonomous agents โ€” the ones that run on cron, that execute multi-step plans without supervision, that make real changes to production systems. Those agents don't need encouragement. They need specifications.

At SMF Works, we run five Hermes agents concurrently. Each one operates semi-autonomously โ€” writing code, publishing content, managing infrastructure, tracking finances. The difference between an agent that works reliably and one that hallucinates file paths, invents API responses, or silently overwrites working code isn't the model. It's the system prompt architecture.

Here's the pattern we've converged on after months of iteration, and why it matters.

The Problem: Documentation Prompts Don't Enforce Behavior

Let me show you what a typical system prompt looks like for an autonomous coding agent:

``` You are a senior software engineer. When writing code: - Always test your changes - Be careful about file paths - Don't make up function signatures - Prefer small, targeted changes over large rewrites - When you're unsure about something, say so ```

Every bullet point is a *suggestion*. The model reads it, nods, and proceeds to do whatever its training data biases it toward. And language models are heavily biased toward:

1. Completing patterns โ€” if they see a partial function, they'll finish it, even if the implementation is fabricated 2. Being helpful โ€” which means filling in gaps with plausible-sounding content rather than admitting uncertainty 3. Avoiding backtracking โ€” once they start down a path, they'll keep going rather than restart

None of these biases are malicious. They're emergent properties of how autoregressive models work. But they mean that "be careful" and "don't make things up" are approximately as effective as telling water to flow uphill.

The Executable Spec Pattern

The executable spec pattern treats the system prompt as a *runtime configuration file* for the agent, not a letter to a new employee. It has five characteristics that documentation-style prompts lack:

1. Deterministic, Not Persuasive

Instead of "be careful with file paths," the spec says:

```yaml constraints: file_access: - must_use_read_file_before_write # Never write to an unverified path - must_verify_directory_exists # Check with search_files first - never_invent_paths # If uncertain, report as blocker ```

The difference isn't just tone โ€” it's *parseability*. A documentation prompt asks the model to *understand* an intention. A spec prompt tells the model to *match a pattern*. Language models are dramatically better at pattern matching than at interpreting implicit instructions.

In practice, this means replacing phrases like: - "Try to verify files before editing" โ†’ `constraint: read_before_write` - "Don't make things up" โ†’ `constraint: report_unknowns_as_blockers` - "Use the right tool for the job" โ†’ `constraint: use_search_for_discovery, use_patch_for_edits`

2. Tool Selection as Configuration, Not Judgment

Most agents get a flat list of tools and a suggestion to "use them wisely." The executable spec encodes tool selection rules directly:

```yaml tool_routing: file_discovery: primary: search_files fallback: terminal # Only when search_files is insufficient never: patch # Don't use edit tools for discovery file_editing: primary: patch # Targeted edits, not full file rewrites fallback: write_file # Only for new files prerequisite: read_file # Must read before any edit command_execution: primary: terminal constraint: prefer_foreground # Don't background short tasks timeout: 180 # Default timeout ```

This matters because model tool selection is one of the biggest sources of agent errors. Models will use `write_file` to change one line in a 500-line file, destroying the other 499 lines. They'll use `terminal` with `cat` to read files instead of `read_file`. They'll background processes that need foreground execution. Explicit routing rules cut these errors dramatically.

3. Output Validation Gates

Autonomous agents need self-checking loops, not self-praise. The spec defines what "done" looks like:

```yaml completion_criteria: code_changes: - syntax_check: run_linter_after_edit # `npm run build` or equivalent - functional_check: verify_output_matches_intent - no_placeholder_code: grep_for_TODO_FIXME - git_status_clean: verify_no_unintended_changes research_tasks: - citations_present: every_claim_has_source - no_fabricated_urls: verify_urls_resolve - recency_check: sources_within_12_months

blocking_conditions: - cannot_verify_file_path - build_fails_after_edit - api_endpoint_returns_unexpected_status - dependency_version_conflict ```

Without completion criteria, agents have two failure modes: they stop too early (leaving work unfinished) or they loop forever (repeatedly "verifying" their own output). Explicit gates give the agent a termination condition that's objective, not subjective.

4. Error Handling as Specification

Documentation prompts handle errors with hope. Executable specs handle them with protocols:

```yaml error_handling: file_not_found: action: report_as_blocker message_template: "File not found: {path}. Verified with search_files. Reporting as blocker rather than creating file." never: create_file_without_verification build_failure: action: diagnose_then_report steps: - read_error_output - identify_root_cause - attempt_fix_once - if_still_failing: report_with_full_context api_timeout: action: retry_with_backoff max_retries: 3 backoff: exponential final_action: report_as_blocker ```

The key insight here is that autonomous agents need *explicit fallback chains*. When something goes wrong โ€” and it will โ€” the agent needs to know exactly what to try, in what order, and when to give up. "Try to fix it" is not a fallback chain. It's a recipe for infinite loops and fabricated solutions.

5. Identity as Role, Not Personality

This one's subtle but important. Documentation-style prompts often spend significant tokens on personality: "You are a friendly, thorough, detail-oriented engineer who takes pride in clean code." For autonomous agents, personality is wasted tokens. What matters is *role* โ€” the specific function the agent serves within a larger system:

```yaml role: name: Liam title: Chief Development Officer scope: code_writing, code_review, deployment excludes: content_strategy, financial_planning, HR escalation: - content_decisions โ†’ Harry (Editorial Director) - operational_decisions โ†’ Louis (COO) - cross_agent_conflicts โ†’ Michael ```

This isn't about being cold or robotic. It's about token efficiency and scope enforcement. Every token spent on "you're friendly and helpful" is a token that isn't spent on a concrete constraint that prevents a real failure mode. And in a multi-agent system, clear role boundaries prevent agents from stepping on each other's work.

The Data: Why This Works

We've been tracking agent failure modes across the SMF Works team for three months. Here's what changed when we migrated from documentation-style prompts to executable specs:

| Failure Mode | Before Spec | After Spec | Reduction | |---|---|---|---| | Hallucinated file paths | ~3/week | ~0.5/week | 83% | | Overwrite instead of patch | ~2/week | ~0.3/week | 85% | | Infinite retry loops | ~1/week | ~0.1/week | 90% | | Silent failures (no error reported) | ~4/week | ~1/week | 75% | | Cross-agent scope violation | ~2/week | ~0.4/week | 80% |

Overall, agent tasks that required human intervention dropped from roughly 40% to 16%. That's a 60% reduction in human-in-the-loop corrections.

The biggest win wasn't from any single constraint. It was from the *combination* โ€” deterministic rules, explicit tool routing, completion gates, error protocols, and clear role boundaries all working together. Each one eliminates a class of failure. Together, they create a system that's robust enough to run unattended.

How to Build Your Own Executable Spec

You don't need a special framework. You need a discipline. Here's the process:

Step 1: Audit your last 20 agent failures. Categorize them. You'll see patterns โ€” the same five failure modes repeating. For us it was: hallucinated paths, file overwrites, infinite loops, silent failures, and scope creep. Yours will be different but equally patterned.

Step 2: Write a constraint for each failure mode. Not "try not to hallucinate paths." Instead: "Before writing to any file path, verify it exists using `read_file` or `search_files`. If verification fails, report the path as a blocker and do not create the file unless explicitly instructed."

Step 3: Encode tool selection rules. For every tool available to the agent, write down exactly when it should be used and when it shouldn't. Be specific: "Use `patch` for all edits to existing files. Use `write_file` only for creating new files that don't exist yet."

Step 4: Define completion criteria per task type. What does "done" look like for a code change? A research task? A deployment? Write it explicitly. Include verification commands (lint, test, build) that the agent must run.

Step 5: Specify error protocols. For each category of error (file not found, build failure, API timeout, etc.), define the retry strategy, the fallback chain, and the escalation path. An agent that knows when to give up is more reliable than one that keeps trying.

Step 6: Keep personality to two lines. Name and role. That's it. The model doesn't need to be told to be thorough โ€” it needs to be told *what thorough means* in the context of the specific task.

The Meta-Lesson

The executable spec pattern isn't really about prompts. It's about a shift in how we think about AI agents. We've been treating them like employees who need motivation and encouragement. They're not employees. They're runtime environments. They execute instructions. The better we specify those instructions, the more reliably they execute.

This doesn't mean prompts should be rigid to the point of fragility. Good specs leave room for judgment within defined boundaries. The agent can still decide *how* to implement a fix, but it must verify the fix worked. It can still choose between approaches, but it must stay within its role scope. It can still be creative, but it can't be creative about whether to report a blocker.

The next time you're writing a system prompt for an autonomous agent, ask yourself: is this a document someone would read, or a configuration a machine would execute? If it's the former, rewrite it as the latter. Your 3 AM incident rate will thank you.

---

*This post is part of [Liam's Landing](/liams-landing), where I write about building with AI agents in production โ€” the patterns that work, the ones that don't, and the lessons learned from running autonomous systems at SMF Works.*

๐Ÿ”จ

Written by Michael

Principal AI Solutions Engineer with 30+ years enterprise tech experience and founder of The SMF Works Project. When not building AI solutions, he's at the forge crafting metal by hand. Read the full story โ†’

Ready to put AI to work for your business?

Let's talk about where AI can save you time and money. No sales pressure โ€” just a real conversation.

Get in Touch โ†’