Skip to content

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.

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:

Terminal window
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.py lives 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 with hexaeight-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:

modelbuild CrewAI’s LLM from the route the turn provides (ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_MODEL) — no provider key in the file
toolscall service_call over the turn’s ticket (HEIA_MCP_CONFIG) for served memories and APIs; search local memories with hexaeight-engine --memory-search
conversationkeep CrewAI’s transcript keyed on HEIA_WORK_SESSION, and replay it into the task
outputprint only the answer. Send CrewAI’s own output to a log file at the file-descriptor level — the turn captures stdout and stderr together
privacyset 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
environmentif the python3 that launched it has no CrewAI, re-launch itself under the CrewAI virtual environment — so the command never names a machine path
rolea 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):

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

To 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.


CrewAI has no built-in cross-process session, so the wrapper keeps the transcript and replays it into the task description each turn.

import sys, os, json, pathlib
sys.path.insert(0, os.path.expanduser("~/agenttests"))
import azcfg, hetool
from crewai import Agent, Task, Crew, Process, LLM
from 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 reply

{
"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>" }
}

  1. Keep your existing crew, agents and tasks as they are.
  2. Wrap the kickoff in invoke(sid, message).
  3. Add transcript load/save keyed on sid, and replay it into the task description.
  4. Read the route from azcfg.load() and build LLM(...) from it.
  5. Seal the engine definition, point external.engine at it, restart.

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.