Skip to content

Frameworks

Your framework already works — and not because we wrote an integration for it.

An engine is a process, not a library binding. Nothing in your framework imports anything of ours, so if it can take a message and return a reply, it already works: any language, any framework, or an agent loop you wrote yourself. The table below is what has been run end to end with zero code changes. It is evidence, not a supported-list.

None of this ships inside the agent, deliberately. There is no bundled CrewAI, no vendored LangGraph, no plugin to keep in step with someone else’s releases. You install your framework the way you already do, in your own environment, at whatever version you want — and the runner below is a file you own and can change. The agent only ever starts a process.

That is also why this list can never be out of date in the way an integration list would be: a framework released tomorrow works the same day, without us shipping anything.

The shape is the same however you start: a runner agent, with the framework’s runner sealed as its engine. You then replace the sample with your own crew, graph or team.

a runner agent ──► the framework's runner as its engine ──► answers turns
│
you replace the sample with your own work

Three ways to get there:

By handprovision a runner, put the file below where its engine points, seal it. Every step is on Bring your own engine
From the workspaceask for the framework you want and have the runner created for you
At provisioning timename the framework when the runner is created

The second and third are the intended path and remove the copying; the first is what every one of them does underneath, and is worth reading once so you know what was built for you.

The code on each page below is that starting point — a working agent on the first turn, and a file you own. Gut it: swap the crew, change the tools, point it at your own data. The agent does not care what the process does, only that it starts and answers.

FrameworkTimeSession persistence
CrewAI24swrapper-owned transcript
AutoGen5ssave_state() / load_state() blob
Agno9snative SqliteDb
Google ADK4snative DatabaseSessionService
LangGraph6snative SqliteSaver checkpointer
LlamaIndex7sContext serialize / restore
OpenAI Agents SDK6snative SQLiteSession
Pydantic AI4smessage_history round-trip
Semantic Kernel6sChatHistory serialize / restore
smolagents5swrapper-owned transcript

Times are one cold call each, all the way through: encrypted message → /external/incoming → engine proxy → adapter → runner.

None of these frameworks has a HexaEight dependency. An engine is a process rather than a library binding, so the framework never imports anything of ours.

Every integration is the same three pieces. What actually differs between frameworks is how a conversation survives between turns — because every turn is a separate process, in-memory state is gone by design and the framework has to rehydrate from disk.

Agno, Google ADK, LangGraph, OpenAI Agents SDK

The framework owns persistence. You hand it the session id and it does the rest.

agent = Agent(..., db=SqliteDb(db_file=DB), session_id=sid, add_history_to_context=True)
return str(agent.run(message).content)

Least code, least to get wrong. Prefer this when the framework offers it.

AutoGen, LlamaIndex, Semantic Kernel, Pydantic AI

The framework hands you an opaque blob; you write it out and read it back, keyed on the session id.

if f.exists(): await agent.load_state(json.loads(f.read_text()))
result = await agent.run(task=message)
f.write_text(json.dumps(await agent.save_state()))

Full fidelity — tool calls and intermediate steps survive, not just the final text.

CrewAI, smolagents

No native session concept, so you keep the transcript yourself and replay it into the prompt.

history = "\n".join(f'{t["role"]}: {t["content"]}' for t in transcript)
desc = f"Conversation so far:\n{history}\n\nNew message: {message}"

Simplest to reason about, and the one that loses fidelity: the model sees text rather than structured turns, and long conversations eventually need trimming.

No framework has a HexaEight dependency. Each one reads the route that was injected for this spawn:

def load():
if os.environ.get("OPENAI_BASE_URL"): # injected for this spawn
return {"base_url": os.environ["OPENAI_BASE_URL"],
"api_key": os.environ.get("OPENAI_API_KEY", ""),
"model": os.environ.get("OPENAI_MODEL", "")}
return { ... } # your own fallback when running standalone

Env-first, and per-process. Injection happens per spawn, so nothing is shared and there is no rewrite race. The key is a per-turn nonce that dies with the turn — your provider key never appears in your code, your config, or your repository.

The fallback branch is what lets the same runner execute standalone during development, which is also the first thing to try when a turn misbehaves.

All ten bind tools through their own native API — @tool, tools=[...], @function_tool, @kernel_function, FunctionTool.from_defaults. The agent does not mediate them, so a tool call is ordinary framework behaviour, and every model hop in a reason → tool → answer loop routes through the proxy.

Tool implementations stay outside the framework files, so the same three functions serve all ten:

import hetool
def get_working_folder() -> str: return hetool.get_working_folder()
def list_files(path: str = ".") -> str: return hetool.list_files(path)
def run_shell(command: str) -> str: return hetool.run_shell(command)

Turn the adapter’s own trace on — it captures the OpenAI SDK request and response logging too:

Terminal window
HEIA_DIAG=1 # -> /tmp/adapter_diag.log

Then, in order:

  1. Run the runner standalone, pointed straight at a provider. If it fails there, it is your code.
  2. Check the router log, not the client’s error. On this path clients invent their reasons — Connection error, Error in generating model output — and debugging those inventions as if they were true is expensive.
  3. Only then suspect the framework. In the first run of these ten, two apparent framework failures turned out to be one poisoned session in the transport underneath. Both frameworks were fine.

Copy the runner and adapter patterns into your own project. They are editable Python; shipping them as product code would imply a guarantee that cannot be made.

The contract is what is stable — satisfy it with code you own.