Skip to content

[draft] cmd/edit: the edit tool as a standalone program for program tool calls - #1266

Open
bobzhang wants to merge 2 commits into
mainfrom
cmd-edit-tool
Open

[draft] cmd/edit: the edit tool as a standalone program for program tool calls#1266
bobzhang wants to merge 2 commits into
mainfrom
cmd-edit-tool

Conversation

@bobzhang

@bobzhang bobzhang commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

First step of the program-tool-call plan: cmd/edit runs the edit agent tool as a standalone program, so an mbtx snippet can compose edits itself instead of the model emitting one tool call per change. Edits can then be derived from something the model just computed (a moon check run, a grep) and applied within a single turn.

It is deliberately thin: it builds agent_tool/edit's definition and runs that package's own executor, so the decode, exact-match discipline, parse gate, and response text are the in-process tool's. There is no second implementation of edit to drift.

edit '{"path":"src/a.mbt","old_string":"one","new_string":"two","start_line":12}'
echo "$payload" | edit          # or stdin, for a large payload

Exit is 0 on a normal response and 1 on a reported error, so a snippet can branch without parsing the message.

Confinement

The caller writes the JSON, so the JSON does not decide where writes may land. OPENSEEK_EDIT_ROOT bounds writes to one root; OPENSEEK_EDIT_ALLOWED_PATHS narrows it to a worker's allowed_paths. A malformed allowlist fails the run rather than widening to the whole root.

Known limitation, and why mbtx wiring is NOT in this PR

Environment-supplied confinement binds a trusted spawner (the engine) but not a model-authored snippet: @process.run takes extra_env and inherit_env, so a snippet can start this program with any environment it chooses.

Today a snippet cannot write the workspace at all — moonrun's fs rules grant only its temp directory and a read-only role's scratch lab, specifically so a snippet cannot route around remove's provenance check. Adding this binary to the mbtx spawn allowlist as it stands would hand that reach back, so it is not on the allowlist and this PR does not touch spawnable_commands.

The fix is discovery instead of instruction: the engine writes the policy inside the workspace and the program finds it by walking up from the file it is about to modify. A snippet cannot plant a competing policy there because it cannot write there. That is the next step and wants its own review.

Provenance is also not carried: FileStateMap is an in-memory handle in the engine, so remove refuses to delete a file only this program touched. Fail-closed and documented.

Verification

  • moon fmt, moon check --deny-warn, moon info (no interface drift)
  • moon test agent_tool/edit agent_tool/multi_edit agent_tool/write_scope cmd/edit --target native — 103 passed
  • moon cram test tests/cram — 30 passed, including the new tests/cram/edit.md covering a successful edit, stdin input, a failed match, the parse gate, out-of-allowlist denial, and a .. escape attempt

🤖 Generated with Claude Code

https://claude.ai/code/session_01HHGA6k85VnewvkWC5miLzR

bobzhang and others added 2 commits September 4, 2026 09:37
…l calls

An `mbtx` snippet can now compose edits itself instead of the model emitting
one tool call per change, so edits can be derived from something the model
just computed (a `moon check` run, a grep) and applied in one turn.

`cmd/edit` is a thin main over `agent_tool/edit`: it builds that package's
definition and runs its own executor, so the decode, exact-match discipline,
parse gate, and response text are the in-process tool's, with no second
implementation to drift. Arguments are the tool's own JSON, from argv or
stdin; exit is 0 on a normal response and 1 on a reported error so a snippet
can branch without parsing the message.

Write confinement deliberately does NOT come from the payload — the caller
authors that, so it must not be able to widen its own reach. `OPENSEEK_EDIT_ROOT`
bounds writes to one root and `OPENSEEK_EDIT_ALLOWED_PATHS` narrows it to a
worker's `allowed_paths`; a malformed allowlist fails the run rather than
widening to the whole root. With neither set the program is unconfined against
the working directory, which is the standalone mode.

Provenance is not carried: the engine's `FileStateMap` is an in-memory handle,
so edits made here are invisible to it and `remove` refuses to delete a file
only this program touched. That is fail-closed and documented; sharing the
record across both paths is separate work.

tests/cram/edit.md covers a successful edit, stdin input, a failed match, the
parse gate, and the confinement (including a `..` escape attempt).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHGA6k85VnewvkWC5miLzR
A snippet controls a spawned child's environment (@process.run takes
extra_env/inherit_env), so environment-supplied confinement binds the engine
but not a model-authored snippet. Snippets cannot write the workspace today,
so admitting an editor with a caller-chosen scope would be a regression, not
an addition. Names the discovery-based shape that would fix it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHGA6k85VnewvkWC5miLzR
@bobzhang bobzhang changed the title cmd/edit: the edit tool as a standalone program for program tool calls [draft] cmd/edit: the edit tool as a standalone program for program tool calls Sep 4, 2026
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