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.

HexaEight doesYour framework does
who is calling, and whether they may (identity, policy, vouch)the crew, graph or team
encryption in both directionsits tools and its reasoning loop
the model route for each turn — no provider key in your codeits own conversation memory, keyed on the session id it is handed
which command may run — approved once, pinned to the file’s sha256everything else
every call it makes to a memory or another agent’s API — through the turn’s ticket, policy-checked
As a sealed mission (recommended)As a process engine
what is sealeda mission whose card runs ONE approved commandthe framework’s process as the engine itself
what a caller can changethe question — never the methodthe question
tamper-evidencethe runner file is sha-pinned: edit one byte and it will not run until re-approvedthe engine entry is sealed
reaches memories and APIsyes — the turn’s ticket is handed to the processthrough its own tools
verified withCrewAI, LangGraph, Pydantic AI, Agno — one command each, belowall ten below

Every framework runs as the same mission — a direct mission with one card — and only the runner.py inside it differs:

runmission engine ── ONE mission: <Framework>_v1_Runner
card ask-runner mode: direct — every message, greetings included; no model step in the harness
1. write the message to question.txt (the message never reaches a shell)
2. run python3 runner.py --question-file question.txt ← approved, pinned to runner.py's sha256
└─ your framework, as its own OS process
3. reply with what it printed, unchanged

The harness makes no model call of its own here: it hands the message over and returns what comes back. The runner’s Answered by <Framework> line is the runner’s own statement — HexaEight renders the text as it is and vouches for nothing inside it; its guarantee is that the approved, sha-pinned runner.py is what ran. The runner travels inside the mission, and each turn gets a fresh copy.

One command sets it up, from your agent’s folder:

Terminal window
hexaeight-activate enable crewai # or langgraph, pydanticai, agno
hexaeight-activate enable langgraph --venv ~/my-env # use an environment you already have

It creates a pinned environment under ~/.heia/frameworks/<framework>/ (or, with --venv, checks that yours imports the framework and uses it, installing nothing into it); downloads the mission bundle from its release and verifies its sha256; installs the mission and builds its index on your machine; and has your agent seal the one command. Then choose runmission and the mission in the workspace. Requires agent r30, harness engine r41 and Activate 1.0.68 or later, and Python 3.10–3.13 (or uv, which supplies one).

frameworkmissionthe framework half of runner.py
CrewAICrewAI_v1_Runnerone Agent, one Task carrying the conversation, a sequential Crew; model via CrewAI’s LLM (LiteLLM)
LangGraphLangGraph_v1_Runnera prebuilt ReAct graph over the tools; model via ChatAnthropic
Pydantic AIPydanticAI_v1_Runneran Agent with native message history; model via AnthropicModel
AgnoAgno_v1_Runneran Agent; the reply is its final assistant message only
AutoGen, Google ADK, LlamaIndex, OpenAI Agents SDK, Semantic Kernel, smolagents, Hermes Agentcoming sooneach runs today as a process engine, below

Each runner.py is one self-contained file — only the file the command names is pinned — with two halves: the HexaEight half, identical in every runner (the turn’s ticket, service_call, memory search, who_am_i, the session transcript, and printing only the answer), and the framework half, run(question, session_id), which is yours to change. To run your own version, duplicate the mission, put your runner.py in it, and seal it; enable always installs the published one and never overwrites a mission that differs from it unless you pass --force. The complete examples are in integration_examples.

The steps above put the framework behind your workspace. To let a backend or another agent call it, give it a runner — a second agent sealed to this one mission:

Terminal window
hexaeight-activate add-runner --from ~/<your-agent> --dir ~/runner-crewai \
--mission CrewAI_v1_Runner --owner [email protected] --license personal --model "<route|model>"
cd ~/runner-crewai
hexaeight-activate enable crewai # into THIS runner's own store
hexaeight-activate runner-memory --share weather --share bbc-news
hexaeight-activate restart agent # stops and starts only this folder's agent

A runner sees only what you export into it — its mission, and nothing else, until runner-memory --share <name> exports a served memory’s pointer or links (or, with --copy, copies) a local memory. Choose its model deliberately: the framework needs one that really calls tools — measured on one route, a model answered weather and “who am I” by inventing a tool result instead of calling the tool. Check the runner’s <framework>-run.log for one [tool] line per call.

The whole path, step by step — including giving it your documents and APIs as memories, letting a caller in, and checking each answer really used its tools — is the Quick start: a framework for outside callers.

The shape here is 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

You do this by hand: provision a runner, put the file below where its engine points, seal it. Every step is on Bring your own engine. (For the sealed-mission shape, CrewAI, LangGraph, Pydantic AI and Agno each have the one-step enable above.)

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.

From here down is the process-engine shape — writing your own runner as the engine itself. The sealed-mission runners above already do all of it for you: they receive the route as ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN / ANTHROPIC_MODEL and keep their transcript keyed on the session id. The process-engine adapter below injects OPENAI_* instead.

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)
  • The model — through the route injected for the turn. No provider key in your code.
  • Memories and other agents’ APIs — as a sealed mission, the turn’s ticket is handed to your process (HEIA_MCP_CONFIG); a call to service_call over it reaches a served memory or a sealed API route, and the agent checks its policy on every call. A local memory is searched with the harness binary’s --memory-search.
  • Other agents, conversationally. Every runner has the connect-to-agent memory built in: memory_search("connect-to-agent", "<agent name> <question>") asks that agent and returns its answer. A runner is always answering someone else, so by default it does not relay — it says so instead. See Two agents talking to each other.

The approval covers the launch, not what your process starts afterwards. A command your Python code runs itself is not approved separately. Keep runners free of shell tools, and review the file before you approve it — the approval pins exactly what you reviewed.

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.