Skip to content

Bring your own engine

Everything so far used the engine that ships with the agent. This page is the other path: your own code behind the same identity and policy boundary.

There is no SDK and no library to link against. An engine is a process the agent runs, which is why it can be written in anything.

Your code stays yours, in your environment. Nothing is packaged into the agent, and nothing of ours is imported into your project — you keep your own dependencies, your own versions and your own release cycle. The agent starts a process and reads its output; that is the entire coupling.

The agent starts your process with an argument list it builds, and reads the reply out of the JSON your process writes to stdout. You declare both halves when you seal it:

fieldwhat it is
fileyour executable
argsNewthe argv for a new conversation
argsResumethe argv for continuing one
replyFieldwhich field of your JSON output holds the reply
sessionIdFieldwhich field carries the session id
nativeSessiontrue if your engine tracks its own sessions; the agent then passes the id back to you
workdirwhere the process runs

Two placeholders are substituted into the argv before your process is started:

{message} what the caller said
{sessionId} the conversation this belongs to

A real entry, from a working agent:

{
"type": "process",
"file": "/home/you/my-engine.sh",
"argsNew": ["-p", "{message}", "--output-format", "stream-json"],
"argsResume": ["-p", "--resume", "{sessionId}", "{message}", "--output-format", "stream-json"],
"replyField": "result",
"sessionIdField": "session_id",
"nativeSession": true,
"workdir": "/home/you/work"
}

So the whole of your obligation is: be startable with those arguments, and print JSON containing the fields you named. Everything else — proving who is calling, deciding whether they are allowed, encrypting both directions, keeping the session — is the agent’s, and already done by the time your code runs.

the agent suppliesyou supply
identity, and proof of the caller’sthe work
encryption, both directions
sessions, and the id that threads them
policy: who may call, and what they may reach
You already have the logica service, a framework, an internal tool — put it behind an identity without rewriting it
You need a language we do not shipit is a process, so the language is yours
You want your own loopyour own tools, your own model calls, your own control flow

If you only want to expose an existing HTTP service to other agents, you do not need this page — An API you already run is shorter and seals a named route instead.

This is not theoretical, and you may not need to start from nothing. Ten agent frameworks run end to end this way with no code changes — CrewAI, AutoGen, LangGraph, LlamaIndex and the rest, each keeping its own native session mechanism. Each comes with a working runner you can put behind an agent and then replace with your own. See Your framework already works.

The entry is stored under the agent’s own key, so it goes in through the CLI:

Terminal window
cd my-agent
hexaeight-activate engine --add <type> \
--name my-engine \
--model "<route>|<model id>" \
--router "<router-identity>|http://127.0.0.1:5100" \
--file /home/you/my-engine.sh

An engine that does its own work and never calls a model still takes --model and --router, because the sealed entry has one shape. Point it at any route; it will not be used.

For anything beyond the defaults — your own argsNew, a different replyField — write the object and seal it directly:

Terminal window
./hexaeight-agent-linux-x64 add-engine --name my-engine \
--router "<router-identity>|http://127.0.0.1:5100" \
--json @./entry.json

add-engine seals the object verbatim, so anything you did not change survives byte for byte.

file is often a small wrapper script rather than the engine itself. A wrapper sits in front of your process and shapes the turn before it runs — runner provisioning uses one to impose a single procedure and strip anything a caller sends that tries to choose another.

Two rules if you write one:

  • It receives the argv the agent built, and must exec the real engine with everything it did not deliberately change.
  • It must live outside the agent’s folder. Every turn runs with that folder masked, so a script kept inside it does not exist as far as the spawned process is concerned.
  • the identity folder is masked — your code cannot read env-file, even though it runs as the agent
  • shell access follows the exec policy; a sealed procedure with no cmdset cannot run commands at all
  • stdout is the reply channel. Keep diagnostics on stderr, or they become part of the answer
[hexaeight-agent] engines: SEALED store (N engine(s))
[hexaeight-agent] engine 'my-engine' -> process

-> process is your engine. Sealed to one procedure, you get the stronger line instead:

[hexaeight-agent] engine 'my-engine' runs ONE mission (sealed): My_Workflow

Export the sealed entry as plaintext, edit one field, re-seal:

Terminal window
./hexaeight-agent-linux-x64 export --plaintext --out ./dump.json
# edit, then:
./hexaeight-agent-linux-x64 add-engine --name my-engine --router "<router>" --json @./entry.json

Pass --file again on a re-seal if the entry had a wrapper. Omitting it unbinds the wrapper silently: no error, and the turn returns in a second or two with no work done.

The export is plaintext and contains everything sealed in the entry, including any headers. Write it somewhere you control and remove it afterwards.