CrewAI
Verified as a sealed mission 2026-09-27, in the workspace and as a runner called from outside (BYOA) — general questions, live weather and BBC News through HexaEight-verified services, search of this machine’s documents, “who am I”, conversation memory across turns, and an injection probe that never reached a shell. Verified as a process engine 2026-08-19 — PASS, 24 s.
As a sealed mission
Section titled “As a sealed mission”HexaEight decides who may ask and which command may run; CrewAI does the work. It is a
direct mission — the same one-card mission every framework
uses — and the answer is CrewAI’s, headed Answered by CrewAI.
The complete example — runner, pinned requirements, install steps and tests — is in
integration_examples/crewai;
the mission bundle is the integration-crewai-v2 release.
The mission (CrewAI_v1_Runner) has one card, ask-runner: every message, greetings included, is
written to question.txt and handed to the one approved command; the reply is what the crew printed,
unchanged. The one command, identical on every run and every machine:
python3 runner.py --question-file question.txt- The question travels in a file, never on the command line — a question containing
$(…)or quotes is only ever text. - The command is approved once and pinned to
runner.py’s sha256. Edit the file and it will not run until it is sealed again. runner.pylives inside the mission; every turn gets a fresh copy of the approved file.- Installed with 1.0.66? That was the two-card v1 mission with
crewai_heia_runner.py. Replace it withhexaeight-activate enable crewai --force— the old one is kept under~/.hexaeight-harness/backups/.
What the runner file must do — one self-contained file, because only the file the command names is pinned:
| model | build CrewAI’s LLM from the route the turn provides (ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_MODEL) — no provider key in the file |
| tools | call service_call over the turn’s ticket (HEIA_MCP_CONFIG) for served memories and APIs; search local memories with hexaeight-engine --memory-search |
| conversation | keep CrewAI’s transcript keyed on HEIA_WORK_SESSION, and replay it into the task |
| output | print only the answer. Send CrewAI’s own output to a log file at the file-descriptor level — the turn captures stdout and stderr together |
| privacy | set CREWAI_DISABLE_TELEMETRY=true, OTEL_SDK_DISABLED=true and CREWAI_TRACING_ENABLED=false before CrewAI loads. CrewAI sends telemetry by default, and its first-run tracing prompt otherwise lands in the answer |
| environment | if the python3 that launched it has no CrewAI, re-launch itself under the CrewAI virtual environment — so the command never names a machine path |
| role | a helpful general assistant that uses tools only for live data and documents. A role phrased as “answer using the tools” makes CrewAI refuse ordinary questions |
Set it up in one command, from your agent’s folder — agent r30, harness engine r41 and Activate 1.0.68 or later, and Python 3.10–3.13 (or uv, which supplies one):
hexaeight-activate enable crewaihexaeight-activate enable crewai --venv ~/my-env # or use an environment you already haveTo serve it to other people’s software, run the same command inside a runner’s folder — see Serving it to other people and systems.
CrewAI reaches the model through LiteLLM, the library its LLM class uses underneath; both are
pinned (crewai==1.15.2, litellm==1.93.0) because LiteLLM builds the actual request to the agent’s
model route.
The conversation transcript is stored unencrypted and is not trimmed; both are being addressed.
Other agents: the runner asks another HexaEight agent a question through its built-in
connect-to-agent memory, and reaches other agents’ API routes through the memories that point at
them — Two agents talking to each other. As a runner it does not relay
by default.
As a process engine
Section titled “As a process engine”CrewAI has no built-in cross-process session, so the wrapper keeps the transcript and replays it into the task description each turn.
The runner
Section titled “The runner”import sys, os, json, pathlibsys.path.insert(0, os.path.expanduser("~/agenttests"))import azcfg, hetool
from crewai import Agent, Task, Crew, Process, LLMfrom crewai.tools import tool
SESS_DIR = pathlib.Path(os.path.expanduser("~/agenttests/crewai/sessions"))SESS_DIR.mkdir(parents=True, exist_ok=True)
@tool("get_working_folder")def get_working_folder() -> str: """Return the agent's current working folder (absolute path).""" return hetool.get_working_folder()
@tool("run_shell")def run_shell(command: str) -> str: """Run a shell command in the working folder and return its output.""" return hetool.run_shell(command)
def load(sid): f = SESS_DIR / f"{sid}.json" return json.loads(f.read_text()) if f.exists() else []
def save(sid, transcript): (SESS_DIR / f"{sid}.json").write_text(json.dumps(transcript, indent=2))
def invoke(sid, message): transcript = load(sid)
c = azcfg.load() llm = LLM(model="openai/" + c["model"], base_url=c["base_url"], api_key=c["api_key"])
agent = Agent( role="Conversational assistant", goal="Answer the user, using the prior conversation for context and tools for operational questions.", backstory="You keep track of everything said earlier in this session.", llm=llm, tools=[get_working_folder, run_shell], verbose=False, )
history = "\n".join(f'{t["role"]}: {t["content"]}' for t in transcript) desc = ( "Here is the conversation so far in this session (may be empty):\n" f"{history or '(no prior messages)'}\n\n" f"New user message: {message}\n\n" "Reply concisely to the new user message." ) task = Task(description=desc, expected_output="A concise reply.", agent=agent) crew = Crew(agents=[agent], tasks=[task], process=Process.sequential, verbose=False)
reply = str(crew.kickoff()).strip()
transcript.append({"role": "user", "content": message}) transcript.append({"role": "assistant", "content": reply}) save(sid, transcript) return replyEngine definition
Section titled “Engine definition”{ "type": "process", "file": "/home/you/crewai/.venv/bin/python3", "nativeSession": true, "argsNew": ["/home/you/he_adapter.py", "/home/you/crewai_session.py", "", "{message}"], "argsResume": ["/home/you/he_adapter.py", "/home/you/crewai_session.py", "{sessionId}", "{message}"], "replyField": "result", "sessionIdField": "session_id", "approve": false, "workdir": "/home/you/work", "proxy": { "shape": "openai", "baseUrlEnv": "OPENAI_BASE_URL", "keyEnv": "OPENAI_API_KEY", "modelEnv": "OPENAI_MODEL", "model": "<route>|<provider model>" }}Adoption
Section titled “Adoption”- Keep your existing crew, agents and tasks as they are.
- Wrap the kickoff in
invoke(sid, message). - Add transcript load/save keyed on
sid, and replay it into the task description. - Read the route from
azcfg.load()and buildLLM(...)from it. - Seal the engine definition, point
external.engineat it, restart.
Gotchas
Section titled “Gotchas”model needs the openai/ prefix. CrewAI’s LLM routes on the provider prefix, so it is
model="openai/" + c["model"], not the bare model id.
CrewAI prints its own tracing banner to stdout. The adapter redirects runner stdout to stderr — if you write your own adapter, do the same or the JSON envelope is unparseable.
Transcript replay is text, not structured turns. Tool calls from earlier turns are not replayed, only the final text. For long conversations, trim or summarise the history you replay.
verbose=False on both Agent and Crew keeps the output quiet; useful even with the stdout
redirect in place.