Repository navigation
v0.2.3 (beta)
Pre-releasethunc 0.2.3 is the last 0.2 release. It makes native tool calls the way agents work wherever they run: on Codex now, as on Claude Code, and in durable runs on Claude Code, which pick up after a crash mid-call. Durable agents can take tools=, the Claude API agent path no longer loses runs to max_tokens or a stalled reply, and edit can change many places at once.
| 8 tool-heavy tasks, Claude Sonnet 5.5, 3 runs each | Passed | Seconds per task | $ per task |
|---|---|---|---|
| thunc 0.2.2 agents, text protocol (Codex, durable runs, fallbacks) | 20/24 | 47 | 0.084 |
| thunc 0.2.3 agents, text protocol | 24/24 | 27 | 0.082 |
| thunc 0.2.3 agents on Claude Code, native calls | 24/24 | 8 | 0.021 |
| Claude Code itself, for reference | 24/24 | 10 | 0.078 |
Through the Claude API, thunc's agents passed 8 of 8 on Sonnet 5.5 ($0.023 a task) and on Opus 5.5 ($0.049 a task). On Codex, native calls took 30 seconds a task, against 45 for the text protocol 0.2.2 used there and 27 for Codex itself (16 of 16 each).
pip install --upgrade thuncimport thunc
thunc.configure(backend="codex") # agents now make native tool calls here too
def find_issue(title: str) -> int: # a tool is your own code, with a docstring for the model
"""Find an open issue by title. Returns its number, or 0."""
return tracker.find(title)
triage = thunc.Agent("triage", workdir=".", tools=[find_issue], effort="high")
@triage.task
def flaky_tests() -> list[str]:
"""Run the tests three times and list the ones that fail only sometimes."""
...# On a Temporal worker: durable runs take the agent's own tools now, and name those that may run
# again after a crash.
registry.agent_task("triage", flaky_tests, version="1", workspace_id="repo", retry_safe_tools=["find_issue"])Native tool calls on Codex (#55)
- The agent's tools are an MCP server that Codex calls, through the same relay as Claude Code: thunc carries out each call with its own tools, permissions and run record. Codex's own tools and your
~/.codexconfig stay out, its sandbox stays read-only, and only thunc's server is approved to run without asking. - A turn that ends without
finish, or fails, is continued withcodex exec resume. The run's Codex session is deleted when the run ends. - Fallback: when Codex can't start them, a run uses the text protocol with a warning.
protocol="native"raises instead, andprotocol="text"keeps the old way.
- On Claude Code, native calls in segments. One activity keeps one
claude -pprocess for many model replies, and checkpoints the run and Claude Code's session before each reply's calls. If the worker stops or the CLI dies, the retried activity restores the session and continues it with--resume. The call that was in flight, asked for again, gets its old journal entry: it's replayed, waits forresolve(), or runs. Never twice. tools=. Each call of the agent's own functions is journaled like a command: recorded before it runs and after, replayed when completed, and waiting forresolve()if a worker stopped mid-call.retry_safe_tools=names the ones that may simply run again.
The Claude API agent path (#52)
Agent(effort=...)on every backend. Claude 4.6 and later on the API get"high"by default; Claude Opus 5.5's own default is"medium", which is low for agentic coding.- Replies are streamed with room for 64,000 tokens. A reply cut off at
max_tokensgets an error result for its calls instead of ending the run,pause_turncarries on, and a reply that sends nothing fortimeoutseconds is stopped and asked again (one Opus reply in the benchmark sent nothing for an hour). - Old tool results are cleared on long runs (context editing), tools are strict where the model and schema allow, and connection errors, rate limits and server errors are retried as a step.
Steadier agents everywhere (#51, #52, #53)
- The text protocol reads replies that aren't only the action: the first complete action amid prose, a fence,
<invoke>markup or made-up results, with a note to the model to keep to JSON. On Claude Code, a text step stops as soon as its action has arrived. finishin the same reply as other calls is refused (except besideremember): in the benchmark, a run returned a guess written before its own read came back.- The model is told when few steps are left, in its last three replies before
max_steps.
edittakesreplace_all, andeditsfor several changes to one file in one call, all made or none.editno longer needs a priorread: it only changes text the agent quotes exactly.writestill replaces only a file read withread.- Large system prompts work on Claude Code: the prompt goes in a file, not on the command line, where large
follow=files could pass the operating system's limit.
Tested
- Offline tests run on Python 3.10–3.14 on Ubuntu, and on Windows; Temporal integration tests on Python 3.10 and 3.14, including a Claude Code run whose CLI dies mid-run. Tests can't reach a real
claude,codexorjev. - Live: the tool-use benchmark on Claude Code (Sonnet 5.5), the Claude API (Sonnet 5.5 and Opus 5.5) and Codex, with the results in
live_tests/bench_tooluse_report.md; a durable run on real Claude Code with the CLI killed after its first tool result, which finished with each effect done once.
Behavior changes
- Agents on
codexmake native tool calls by default, and fall back to the text protocol if they can't. - Durable runs on
claude-codemake native tool calls. Runs already in progress keep the text protocol. - Agents on the Claude API think at effort
highon Claude 4.6 and later. editno longer needs a priorread;writestill does.finishin the same reply as other calls is refused, except besideremember.- Nothing is removed.
Known issues: on Codex, native calls count each tool call as a step, so a long task reaches max_steps sooner, and their tokens aren't reported. Durable runs on Codex use the text protocol. On a fix in a long file, native calls take about twice Claude Code's steps. The full list is in CONTRIBUTING.md, and every change is in CHANGELOG.md.
Full changelog: v0.2.2...v0.2.3