Skip to content

smolagents

Verified 2026-08-19 — PASS, 5s. Session persistence: wrapper-owned transcript.

smolagents’ idiom is CodeAgent, which calls tools by generating Python that calls them rather than emitting structured tool calls. That choice is deliberate here and explained below.

This is one of the two frameworks that appeared to fail in an earlier test run. It was never a framework fault — the transport underneath had a poisoned session. See sessions-and-swarm.md.


import sys, os, json, pathlib
sys.path.insert(0, os.path.expanduser("~/agenttests"))
import azcfg, hetool
from smolagents import CodeAgent, OpenAIServerModel, tool
SESS = pathlib.Path(os.path.expanduser("~/agenttests/smolagents/sessions"))
SESS.mkdir(parents=True, exist_ok=True)
@tool
def get_working_folder() -> str:
"""Return the agent's current working folder (absolute path)."""
return hetool.get_working_folder()
@tool
def run_shell(command: str) -> str:
"""Run a shell command in the working folder and return its output.
Args:
command: The shell command to run.
"""
return hetool.run_shell(command)
def invoke(sid, message):
c = azcfg.load()
model = OpenAIServerModel(model_id=c["model"], api_base=c["base_url"], api_key=c["api_key"])
agent = CodeAgent(tools=[get_working_folder, run_shell], model=model,
max_steps=4, code_block_tags="markdown")
f = SESS / f"{sid}.json"
transcript = json.loads(f.read_text()) if f.exists() else []
history = "\n".join(f'{t["role"]}: {t["content"]}' for t in transcript)
prompt = (f"Conversation so far:\n{history or '(none)'}\n\nNew message: {message}\n"
"Reply concisely. Use your tools for operational questions.")
reply = str(agent.run(prompt)).strip()
transcript += [{"role": "user", "content": message}, {"role": "assistant", "content": reply}]
f.write_text(json.dumps(transcript, indent=2))
return reply

{
"type": "process",
"file": "/home/you/smolagents/.venv/bin/python3",
"nativeSession": true,
"argsNew": ["/home/you/he_adapter.py", "/home/you/smolagents_session.py", "", "{message}"],
"argsResume": ["/home/you/he_adapter.py", "/home/you/smolagents_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 tools as they are, decorated with @tool.
  2. Keep a transcript keyed on sid and replay it into the prompt.
  3. Build OpenAIServerModel from azcfg.load().
  4. Seal the engine, point external.engine at it, restart.

Prefer CodeAgent over ToolCallingAgent on non-OpenAI models. ToolCallingAgent sets tool_choice in a way some providers reject — GLM among them. CodeAgent sidesteps it by having the model write Python that calls the tools, which is smolagents’ native idiom anyway.

Set code_block_tags="markdown". The default <code></code> tags arrive truncated from some providers (</code) and fail to parse, so the agent errors out mid-step. Markdown ```py fences are emitted reliably.

Generated code executes locally. That is how CodeAgent works. HBIA’s jail confines the spawn, but understand what you are enabling before binding a run_shell tool in production, and prefer narrow, purpose-built tools over a general shell.

Docstrings need an Args: section for any tool taking parameters — smolagents parses it to build the schema, and omitting it produces a tool the model cannot call correctly.

max_steps bounds the loop. Each step is an LLM call through the proxy, so this is both a cost and a latency control; 4 was ample for conversational use.