Skip to content

th-82ad57: coding-tool parity across all polyglot LocalServers (go/ts/python/dotnet)#329

Open
brentrager wants to merge 5 commits into
mainfrom
th-82ad57-polyglot-tools
Open

th-82ad57: coding-tool parity across all polyglot LocalServers (go/ts/python/dotnet)#329
brentrager wants to merge 5 commits into
mainfrom
th-82ad57-polyglot-tools

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Problem

Only the Rust engine wired a coding toolset. The go/ts/python/dotnet serve binaries booted a chat-only agent — no file tools — so on any coding task the agent replied "I don't have file editing tools available" and scored a structural 0% on the parity bench. Shipping Go alone would leave the engine lopsided; parity means all four land together.

What this does

Ports the same six workspace-confined tools Rust has — read_file, write_file, edit_file, list_files, grep, bash — into every remaining LocalServer, each wired via that server's WithTools analogue and gated on the same env contract (SMOOTH_NO_TOOLS, SMOOTH_WORKSPACE default cwd). Go (already merged-pattern here) is the reference; the other three mirror it faithfully, including identical constants (read limit 2000, list cap 200 / walk 50000, grep cap 200, bash output cap 50000, bash timeout 120s).

Every filesystem path routes through a resolveWorkspacePath equivalent confining it to the workspace root — a trust boundary, tested against ..//absolute/symlink escapes in each language.

Engine Tools Wiring Tests
Go coding_tools.go cmd/serve/main.go
TypeScript codingTools.ts main.ts 15/15 ✓
Python coding_tools.py server.py/__main__.py 22/22 ✓
.NET CodingTools.cs host/Program.cs 20/20 ✓

All four verified green locally (the polyglot dirs lack PR CI, so local verification is the gate).

Known pre-existing caveat (NOT from this PR)

The TS server's full pnpm build currently fails on ToolHook/toolHooks missing from the published @smooai/smooth-operator-core npm — the th-56aa52 IToolHook bump landed in the code but the core package exporting it isn't published yet (core→server publish-ordering gap). This is already the state on main; the TS coding-tools themselves pass in isolation (15/15). The TS server won't fully run until core is published.

🤖 Generated with Claude Code

brentrager and others added 4 commits July 24, 2026 22:20
…edit files

The go/ts/python/dotnet `cmd/serve` binaries called ServeLocal WITHOUT
WithTools, so the local agent was chat-only and replied "I don't have file
editing tools" — structural 0% on the parity bench.

Fix GO first (prove the pattern, unlock a second engine in the matrix):

- New go/server/coding_tools.go: CodingTools(workspace) builds the
  workspace-confined set read_file / write_file / edit_file / list_files /
  grep / bash as core.FuncTool, mirroring the Rust daemon's smooth-tools.
  Every path routes through resolveWorkspacePath (lexical, no symlink follow)
  so a prompt-injected agent can't escape the workspace; bash runs sh -c
  rooted at the workspace with a catastrophic-command deny + timeout.
- Wire it in cmd/serve/main.go via WithTools, confined to SMOOTH_WORKSPACE
  (default: cwd, which is what the bench launches the server in). Opt out with
  SMOOTH_NO_TOOLS=1. CodingTools is the reusable pattern ts/python/dotnet copy.
- coding_tools_test.go covers write→read round-trip, edit uniqueness/replace_all,
  list/grep skipping node_modules, bash cwd + catastrophic block, and path
  confinement (absolute + relative escapes rejected).
- Fix a pre-existing compile break: tool_hooks_test.go called CreateSession
  with the old 4-arg signature (merge collision on main; Go dir lacks PR CI),
  which stopped the whole server test package from compiling.

Verified live: built cmd/serve against llm.smoo.ai (deepseek-v4-flash), drove
one canonical WS turn (create_conversation_session → send_message "Create a
file hello.txt containing exactly: WORLD" → eventual_response). The agent
emitted a write_file tool call, the tool wrote the file, and hello.txt landed
on disk as exactly 5 bytes "WORLD".

Follow-ups (same fix, sibling hosts): ts / python / dotnet cmd/serve.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Python serve binary booted a chat-only agent — no coding tools — so on any
coding task it replied "I don't have file editing tools available" and scored a
structural 0% on the parity bench.

Ports go/server/coding_tools.go faithfully: the same six tools (read_file,
write_file, edit_file, list_files, grep, bash), the same JSON-schema parameters,
the same constants (read limit 2000, list cap 200 / walk budget 50000, grep cap
200, bash 50000 bytes / 120s), and the same catastrophic-command circuit breaker.

Every model-supplied path routes through resolve_workspace_path, which confines
it to the workspace root lexically — a prompt-injected agent can't read or write
outside the workspace. Tested with absolute, relative, and dip-out-and-back
escapes against every path-taking tool.

Wired into serve_local on the sibling hosts' env contract: SMOOTH_WORKSPACE picks
the root (default cwd), SMOOTH_NO_TOOLS=1 opts back out to chat-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFjpPZwCqFyBxhZba1fQzk
The .NET `serve` host booted a chat-only agent — no tools were registered on
the IReadOnlyList<AITool> DI seam the FrameDispatcher reads from — so on any
coding task the agent replied "I don't have file editing tools available" and
scored a structural 0% on the parity bench.

Faithful port of go/server/coding_tools.go: the same six tools (read_file,
write_file, edit_file, list_files, grep, bash), the same argument names, the
same limits (read 2000 lines, list 200 entries / 50000 walk budget, grep 200
matches, bash 50000 bytes / 120s), and the same catastrophic-command deny.

Every path the model supplies routes through CodingTools.ResolveWorkspacePath,
which confines it to the workspace root lexically — an escape (`../`, an
absolute path outside the root, a sibling sharing the root's name prefix) is
refused before any filesystem access. That guard is the trust boundary and is
covered by tests that fail without it.

Wired in the host on the Go env contract: SMOOTH_WORKSPACE (default: process
cwd) sets the root, SMOOTH_NO_TOOLS=1 opts back out to chat-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFjpPZwCqFyBxhZba1fQzk
@changeset-bot

changeset-bot Bot commented Jul 25, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: c1e65f9

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

The TS server imports ToolHook/toolHooks from @smooai/smooth-operator-core
(th-56aa52 IToolHook work), but the dep pinned ^0.20.4 which predates those
exports — a pre-existing typecheck-test red on main. Published core 1.7.1
exports ToolHook/toolHooks/PermissionHook; bumping consumes it. No engine
publish needed. All 233 server tests pass, typecheck clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017QkUKdJ4Xbx7WXV2FAg555
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant