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.
Who does what
Section titled “Who does what”| HexaEight does | Your framework does |
|---|---|
| who is calling, and whether they may (identity, policy, vouch) | the crew, graph or team |
| encryption in both directions | its tools and its reasoning loop |
| the model route for each turn — no provider key in your code | its own conversation memory, keyed on the session id it is handed |
| which command may run — approved once, pinned to the file’s sha256 | everything else |
| every call it makes to a memory or another agent’s API — through the turn’s ticket, policy-checked |
Two ways to run one
Section titled “Two ways to run one”| As a sealed mission (recommended) | As a process engine | |
|---|---|---|
| what is sealed | a mission whose card runs ONE approved command | the framework’s process as the engine itself |
| what a caller can change | the question — never the method | the question |
| tamper-evidence | the runner file is sha-pinned: edit one byte and it will not run until re-approved | the engine entry is sealed |
| reaches memories and APIs | yes — the turn’s ticket is handed to the process | through its own tools |
| verified with | CrewAI, LangGraph, Pydantic AI, Agno — one command each, below | all ten below |
As a sealed mission
Section titled “As a sealed mission”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, unchangedThe 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:
hexaeight-activate enable crewai # or langgraph, pydanticai, agnohexaeight-activate enable langgraph --venv ~/my-env # use an environment you already haveIt 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).
| framework | mission | the framework half of runner.py |
|---|---|---|
| CrewAI | CrewAI_v1_Runner | one Agent, one Task carrying the conversation, a sequential Crew; model via CrewAI’s LLM (LiteLLM) |
| LangGraph | LangGraph_v1_Runner | a prebuilt ReAct graph over the tools; model via ChatAnthropic |
| Pydantic AI | PydanticAI_v1_Runner | an Agent with native message history; model via AnthropicModel |
| Agno | Agno_v1_Runner | an Agent; the reply is its final assistant message only |
| AutoGen, Google ADK, LlamaIndex, OpenAI Agents SDK, Semantic Kernel, smolagents, Hermes Agent | coming soon | each 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.
Serving it to other people and systems
Section titled “Serving it to other people and systems”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:
hexaeight-activate add-runner --from ~/<your-agent> --dir ~/runner-crewai \cd ~/runner-crewaihexaeight-activate enable crewai # into THIS runner's own storehexaeight-activate runner-memory --share weather --share bbc-newshexaeight-activate restart agent # stops and starts only this folder's agentA 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.
As a process engine
Section titled “As a process engine”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 workYou 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.
| Framework | Time | Session persistence |
|---|---|---|
| CrewAI | 24s | wrapper-owned transcript |
| AutoGen | 5s | save_state() / load_state() blob |
| Agno | 9s | native SqliteDb |
| Google ADK | 4s | native DatabaseSessionService |
| LangGraph | 6s | native SqliteSaver checkpointer |
| LlamaIndex | 7s | Context serialize / restore |
| OpenAI Agents SDK | 6s | native SQLiteSession |
| Pydantic AI | 4s | message_history round-trip |
| Semantic Kernel | 6s | ChatHistory serialize / restore |
| smolagents | 5s | wrapper-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.
Pick your resume strategy first
Section titled “Pick your resume strategy first”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_MODELand keep their transcript keyed on the session id. The process-engine adapter below injectsOPENAI_*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.
1 · Native session store
Section titled “1 · Native session store”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.
2 · Serialize and restore a state blob
Section titled “2 · Serialize and restore a state blob”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.
3 · Wrapper-owned transcript
Section titled “3 · Wrapper-owned transcript”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.
One helper, every framework
Section titled “One helper, every framework”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 standaloneEnv-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 hetooldef 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)What a framework can reach
Section titled “What a framework can reach”- 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 toservice_callover 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-agentmemory 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.
Debugging
Section titled “Debugging”Turn the adapter’s own trace on — it captures the OpenAI SDK request and response logging too:
HEIA_DIAG=1 # -> /tmp/adapter_diag.logThen, in order:
- Run the runner standalone, pointed straight at a provider. If it fails there, it is your code.
- 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. - 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.
Adopt, do not depend
Section titled “Adopt, do not depend”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.
- Write an engine — the contract these all satisfy
- Deploy an agent — get one running first
- LLM Router — where the injected route points