Skip to main content
AI tutorials

Compile Reliable Agent Skills into State Machines with HEXIS

Learn how to install HEXIS, compile an agent skill into an extended finite state machine, update it from execution traces, and run it through the CLI or Python API. This tutorial also explains machine concepts, validation, tool backends, generated guides, fallback behavior, and practical security considerations.

Compile Reliable Agent Skills into State Machines with HEXIS

What is HEXIS?

HEXIS is a Python package and command-line tool that compiles an agent skill, represented by a SKILL.md document, into an extended finite state machine. Instead of asking a language model to interpret the entire skill and choose every next step, HEXIS uses programmatic states, variables, guards, and transitions to enforce the order of operations.

Language models still perform reasoning and generation inside relevant states. However, the machine determines which operation runs next, which variables are available, when loops must stop, and which terminal outcome is reached. This design reduces the risk that an agent will skip, reorder, or incorrectly apply requirements written in the skill.

HEXIS agent skill compilation workflow

HEXIS is released under the MIT license and is currently marked as alpha software. It accompanies the paper Compiling Agent Skills into Extended Finite State Machines.

Core workflow

A typical HEXIS workflow has three stages:

  1. Compile: A model reads the skill document, task inputs, tool definitions, and skill rules, then drafts an efsm-v1 machine. HEXIS checks its schema, guards, reachability, termination, clause coverage, and tool references. Invalid drafts are returned to the model for revision.
  2. Update: New execution traces are normalized into events. For each trace step, a model, decisions file, or deterministic aligner decides whether to match an existing state, create a state, ignore noise, or exclude the trace.
  3. Execute: The runtime performs the action associated with the current state, records declared outputs, and takes the first transition whose guard evaluates to true.

An updated machine is accepted only when it passes the required checks and can replay the new trace as well as every previously accepted trace. If validation fails, the existing machine remains unchanged.

Key features

  • OpenAI-compatible compilation: Select a model, endpoint, and API-key environment variable from the command line.
  • Trace-based updates: Incorporate successful execution behavior one decision at a time without invalidating previously accepted traces.
  • Readable JSON machines: Machines use the plain JSON efsm-v1 format with typed variables and explicit transitions.
  • Multiple action types: States can perform tool, model, judge, user, or end actions.
  • Bounded control flow: Ordered guards and loop counters support branching and retry limits.
  • Generated documentation: Every normal build includes GUIDE.md and PROMPT.md.
  • Fallback behavior: The fallback state retries from the most recent tool step and can hand the task to interpreted execution of the original skill.
  • Tool backends: Machines can use OpenCode tools, a local Bash executor, or registry-defined tools realized by a model.
  • Hermetic testing: The repository contains more than 350 tests that do not require an API key, model endpoint, or network connection.

Installation and prerequisites

Install from PyPI

HEXIS requires Python 3.11 or 3.12. Install the package with pip:

pip install hexis-agent

The package is imported in Python as hexis, while its command-line executable is named hexis-agent.

Install from a checkout

pip install .

For development dependencies such as pytest, Ruff, build, and Twine, use an editable installation:

pip install -e ".[dev]"

Runtime requirements

Compiling and running model-backed machines requires an OpenAI-compatible chat endpoint. The default execution backend also expects OpenCode on PATH. If a machine only needs Bash, select --executor local to run commands in a local subprocess without OpenCode.

Try the offline quick start

The package includes a small hermetic example named table_clean. It uses a scripted model, scripted tools, and an in-memory file system, so it can run without network access.

from hexis.execution import runtime
from hexis.examples import table_clean as tc

machine = tc.reference_machine()
task = tc.gen_tasks(1, seed=0)[0]

result = runtime.run_task(
    machine,
    task,
    model=tc.build_model(),
    tools=tc.build_registry(tc.MemFS(task["files"])),
    doc=tc.skill_doc(),
)

print(
    result.stopped,
    " -> ".join(result.path()),
    tc.verify(task, result.trace),
)

The example should finish with a terminal result, print the state path, and report whether the produced trace passes the example verifier. It is a useful way to confirm that installation succeeded before configuring an external endpoint.

Configure a model endpoint

HEXIS reads the model identifier, base URL, and API key from command-line options or environment variables. A basic shell configuration looks like this:

export API_KEY=your-key
export MODEL=qwen3.6-flash
export BASE_URL=https://your-endpoint/v1

You can also place MODEL, BASE_URL, and API_KEY in a .env file. HEXIS searches upward from the working directory, and variables already present in the environment take precedence.

To keep reusable endpoint arguments in a shell variable:

M="--model qwen3.6-flash --base-url https://your-endpoint/v1"

The --api-key-env NAME option selects a different environment variable for the key. API keys are not passed on the command line or stored in the build manifest.

Compile a skill into a machine

Point the compiler at a skill path and choose an output directory:

hexis-agent compile \
  --skill path/to/skill \
  --out build/ \
  $M

The skill directory should contain SKILL.md. It may also contain compile.json, which can declare terminals, derived event labels, requirements, and terminal conditions. Each rule in that file quotes the sentence from which it was derived.

If compile.json is absent, the compiler asks the model to extract rules from the skill and checks the quoted text against the document. The effective configuration is then saved as rules.json.

What the build directory contains

  • machine.json stores the current machine.
  • machine_init.json preserves the initial compiled machine.
  • build.json records source information, command settings, model details, outcomes, and token usage, but never the API key.
  • skill/SKILL.md, tools.json, and rules.json preserve compilation inputs.
  • traces/ contains hashed copies of traces used by the build.
  • progress.json records trace outcomes and the accepted traces protecting the machine.
  • decisions.jsonl caches update questions and answers.
  • report.md, context.json, and log files provide diagnostic information.
  • GUIDE.md explains inputs, tools, states, transitions, loops, and fallback behavior.
  • PROMPT.md provides a system prompt for a tool-using agent to execute the machine state by state.

Understand the machine model

A HEXIS machine is a JSON document using the efsm-v1 format. Its main elements are variables, states, actions, transitions, terminals, and a fallback state.

Variables

Variables can be initialized from task input fields with init_from or from constant values with init. Actions write their results only to declared variables.

Actions

  • Model: Generates declared variables from a state-specific prompt and selected input variables.
  • Tool: Invokes a named tool. References such as ${command} are substituted at runtime.
  • Judge: Chooses a label from a fixed set or returns the abstain label.
  • User: Requests user input.
  • End: Stops execution with a terminal result.

Transitions and guards

Transitions are evaluated in order after a state's action finishes. The runtime takes the first transition whose guard holds. An empty guard is unconditional and should appear last. A transition can also increment a counter through inc, enabling bounded retry loops.

Guards use a restricted expression language implemented by hexis.machine.cond. It supports static analysis of properties such as mutual exclusion and loop bounds, and it does not use Python's eval.

Update a machine from traces

Execution traces are JSON Lines files. They begin with task metadata and a verdict, followed by one event per line. A trace can contain tool calls, model output, and an ending event.

First preview pending traces without making model calls or writing changes:

hexis-agent update \
  --build build/ \
  --traces traces/ \
  --show 3

Then perform the update:

hexis-agent update \
  --build build/ \
  --traces traces/ \
  $M

For every trace step, the decider selects one of four outcomes:

  • match reuses an existing state and adds a transition if necessary.
  • new creates a state based on the stated purpose.
  • ignore removes harness noise from consideration.
  • exclude omits the entire trace.

The model's answer also identifies a candidate state, describes the step's purpose, and may associate it with a skill clause. HEXIS validates the resulting candidate machine and replays all protected traces before accepting it.

Resume and control update costs

Model answers are cached in decisions.jsonl. Repeating the same command reuses those answers, while an interrupted run with exit status 3 can resume from saved progress. Use --no-cache only when you intentionally want the model to answer again.

An update generally costs about one model call per trace step. Use --max-traces N to limit a run. For model-free alternatives, supply human or external decisions through --decisions FILE, or choose deterministic alignment with --decider align.

Before processing new traces, HEXIS checks the current machine and replays every accepted trace. If a manual edit or changed tool definition has broken the build, the command exits with status 2 without modifying it.

Run the compiled machine

Inspect build/GUIDE.md before execution. It documents required task inputs, available tools, state actions, transition order, retry limits, and fallback behavior.

Run the machine with key-value task inputs:

hexis-agent run \
  --machine build/ \
  --input request="Process this task" \
  --workdir work/ \
  --executor local \
  $M

Pass additional --input KEY=VALUE options for every input expected by the machine. To save the execution as a reusable JSON Lines trace, add:

--json run-trace.jsonl

The default executor uses OpenCode tools. The local executor runs Bash commands in a subprocess. At runtime, a machine may use only tools supplied by its backend or defined in a tool registry; HEXIS does not silently replace missing tools.

Use a machine from Python

The core runtime entry point is hexis.execution.runtime.run_task. Supply a loaded machine, a task with an input object, a model adapter, a tool registry, and the skill document when fallback execution may need it.

from hexis.execution import runtime

result = runtime.run_task(
    machine,
    {"input": {
        "request": "Answer the question",
        "output_path": "answer.txt",
    }},
    model=model,
    tools=tools,
    doc=skill_document,
)

print(result.stopped)
print(result.path())
print(result.trace)

Related APIs include hexis.machine.schema.load_machine for loading machines, hexis.llm.llm_client.client_from_env for endpoint configuration, and hexis.guide functions for rendering guides, prompts, and Mermaid diagrams.

Work with the supplied example machines

The repository includes four compiled machines under examples/machines/:

  • dabench: Answers a data-analysis question about a data file using a computation.
  • livemath: Answers a theorem-grounded multiple-choice mathematics question.
  • sealqa: Answers a question from a local corpus and includes evidence.
  • spreadsheet: Edits a workbook without changing its structure.

These machines commonly use a model state to write a shell command, a tool state to execute it, guarded retries based on returncode, a tool step that reads the answer back, and a judge state that selects either a verified or unverified terminal.

For example, run the LiveMath machine with:

hexis-agent run \
  --machine examples/machines/livemath \
  --input request="..." \
  --input output_path=answer.txt \
  --workdir work/ \
  --executor local \
  $M

The example machines can be loaded and structurally checked without a model, but running them requires a configured model endpoint.

Advanced tips

Regenerate documentation

Compilation and updates generate documentation automatically unless --no-guide is used. Regenerate it manually with:

hexis-agent guide --build build/

You can also point the command directly at machine.json. Add --embed-skill when you want the generated agent prompt to include the original skill, allowing interpreted execution after fallback.

Provide explicit tool definitions

Pass --tools registry.json when the machine uses custom tools. Tool names are treated as opaque identifiers during compilation. At runtime, registry-defined tools can be realized as shell commands by the model, while native tools must be supplied by the selected backend.

Tune model requests

Compilation and updates support options including --temperature, --max-tokens, --llm-timeout, --llm-retries, --stream, and --extra-body JSON. For Qwen-compatible endpoints, runtime reasoning can be controlled with --no-think, --think-budget, and --judge-think-budget.

Use provider-specific environment variables

The --provider NAME option reads NAME_MODEL, NAME_BASE_URL, and NAME_API_KEY. Built-in defaults are available for the MiniMax and DeepSeek provider names.

Know the exit statuses

  • 0 indicates success.
  • 2 indicates a usage, configuration, or build problem.
  • 3 indicates a model endpoint failure or interruption; progress is saved for resumption.

Run development checks

pytest
ruff check src tests
python -m build && twine check --strict dist/*

The test suite is hermetic. OpenCode-specific tests are skipped when the opencode executable is unavailable.

Security and privacy

HEXIS can execute tool arguments generated by language models, including shell commands. Run untrusted workloads in an isolated environment, and use only machines, skills, registries, and task files that you trust.

The update process sends trace steps, shortened tool results, and skill clauses to the configured model endpoint. Build directories retain trace copies and model questions. In addition, PROMPT.md contains the complete machine and prompts derived from the skill. Review these artifacts before sharing them.

The runtime limits each model call to the prompt of the state currently being executed, rather than exposing the entire machine to every call.

Conclusion

HEXIS turns natural-language agent instructions into explicit, validated workflows. By combining model reasoning inside states with programmatic transitions, trace replay, bounded retries, tool restrictions, generated guides, and fallback handling, it offers a structured way to make skill execution more predictable. Start with the offline example, compile a small trusted skill, inspect the generated guide, and then introduce traces incrementally while monitoring validation results.