Backends — a tool-using agent over a document¶
Module: agentdescent.backends
· API: AgentBackend, document_agent, openhands, tool_loop_backend, …
Completion is prompt -> text, and that is enough for a task
whose input fits in a prompt. It is not enough for
EvoSkill's OfficeQA: the answer is a figure buried in a
200 KB – 1.2 MB financial table that has to be found by grep and then
computed. A single completion cannot do that; an agent with tools can.
Every argument there is a domain concept, which is why the shape is kept
deliberately separate from the general contract: it is a domain adapter built
on Completion, not a competitor to it.
document_agent — adapt whatever agent you have¶
from agentdescent.agents import claude, claude_code
from agentdescent.backends import document_agent, openhands
document_agent(openhands(model="openai/deepseek-v4-flash")) # real tool agent
document_agent(claude_code()) # same task, other agent
document_agent(claude(model="claude-haiku-4-5")) # no tools -> inline
It does the right thing for what it is given:
- a
WorkspaceAgentgets a scratch directory with the document written into it, so it can genuinelygrepa 1 MB table; - a plain completion gets the document inline in the prompt, truncated at
inline_chars.
The inline path is a fallback, not an equivalent
Measured on three real OfficeQA items (documents of 266–390 KB) with
document_agent(openai_compatible(model="deepseek-v4-flash")): 1 of 3
correct, because at inline_chars=200_000 roughly half of each document
never reached the model. Truncation is never silent — it warns, naming the
fraction dropped — because an empty or wrong answer otherwise looks like a
model failure and is not one.
Skills as files, not as prompt text¶
For a workspace agent the library is written to .claude/skills/ in that same
scratch directory and the prompt carries a pointer, so the agent opens the one
skill it needs. That is the entire reason a skill directory is worth more than
a concatenated string: inlining the whole library in every question is what it
avoids.
A backend with no workspace has nowhere to put them, so it folds them back into
the inline skills block rather than dropping them in silence.
This is what EvoSkill uses now that its artifact is a
FileTree — with --backend claude-code the evolving
skill library reaches the agent as files.
The two implementations¶
openhands — a real OpenHands agent¶
from agentdescent.backends import openhands, openhands_backend
agent = openhands(model="openai/deepseek-v4-pro", base_url="https://api.deepseek.com")
backend = openhands_backend(model="openai/deepseek-v4-pro") # == document_agent(openhands(...))
OpenHands SDK v1.x with terminal + file_editor tools, driven by any LiteLLM
model — openai/<name> plus base_url targets any OpenAI-compatible endpoint.
Needs pip install openhands-ai (Python ≥ 3.12); the import is lazy, so the rest
of the framework runs without it. No Docker (local runtime).
openhands() is itself a WorkspaceAgent, so it also works anywhere a plain
completion does — including tree_runner.
tool_loop_backend — the dependency-free stand-in¶
from agentdescent.backends import tool_loop_backend
backend = tool_loop_backend(openai_compatible(model="glm-4.6"), max_steps=5)
A GREP / ANSWER ReAct loop over the document using any completion: search,
read the matching table region with its nearest header row, then compute and
answer. It mirrors what the real tool agent does, runs on any Python, and needs
no extra install — the right choice for reproducing the example locally.
Which one to use¶
| situation | backend |
|---|---|
| document fits comfortably in the prompt | document_agent(<any completion>) |
| large document, no extra dependencies | tool_loop_backend(<completion>) |
| large document, real tool use, Python ≥ 3.12 | openhands_backend(...) |
| you already run Claude Code or Codex | document_agent(claude_code()) |
All four satisfy the same AgentBackend protocol, so
examples/evoskill/evoskill_skill_discovery.py --backend … switches
between them with a flag and no other change.