Skip to content

Commit f185287

Browse files
tonyfettesclaude
andcommitted
feat(agent): replace the shell tool with the myshell process EDSL
The agent no longer has a shell. Commands are spawned from `run_moonbit` programs through `bobzhang/myshell`, a shell-free process EDSL where the executable and the argument vector stay separate: `Cmd("moon", ["check"])` passes every argument literally, so `|`, `>`, `&&`, `$()` and `*` have no meaning and there is no quoting to get wrong. Pipes and control flow are ordinary MoonBit — capture `out.stdout` and filter it in code — which is also what replaces grep/sed/awk. What this removes is a parsing layer and the string heuristics defending it. What it keeps is everything the shell tool actually delivered: - Background jobs move to `run_moonbit`'s `run_in_background`, watched by `job_output` and `job_stop` (renamed from `shell_output`/`shell_stop`, since a registry without a shell must not advertise shell names). The job runtime, its spill dir, and the completion-notice steer are wired as before. - A detached snippet owns its directory until the session scope ends, rather than being reclaimed by the call that started it. - The foreground bound rises from 60s to 300s, because a snippet now runs whole `moon test` cycles rather than only compute. The source-write sandbox still wraps every run. Note the consequence: the shell tool could statically recognize trusted source-writing moon commands (`moon fmt`, `moon info`, `moon add`, `moon test --update`) and run them exempt, but an arbitrary snippet cannot be classified that way, so those commands are denied and source changes go through the file tools. The system prompt says so directly and tells the agent to report the commands it could not run. `run_moonbit` keeps refusing native FFI; it no longer refuses spawning processes, which is now the point of the tool. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 37a3955 commit f185287

25 files changed

Lines changed: 656 additions & 451 deletions

README.mbt.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,12 @@ The `cmd/openseek` package is the single-binary entry point — a subcommand tre
131131
(default: the terminal UI; `run`/`serve`/`review`/`sessions` for the headless
132132
engine; `mcp` to validate MCP configuration). `openseek run` parses arguments
133133
and runs the agent package. The agent sends DeepSeek native function tools and
134-
supports twelve local tools: `shell` (with `shell_output` and `shell_stop` for
135-
background jobs — on Windows the plain foreground shell only), `read`, `edit`,
136-
`multi_edit`, `write`, `remove`, `plan`, `goal`, `run_moonbit`, and `finish`.
134+
supports eleven local tools: `run_moonbit` — which is both the scripting surface
135+
and the command runner, spawning processes through the shell-free
136+
[`bobzhang/myshell`](https://mooncakes.io/docs/bobzhang/myshell) EDSL, with
137+
`job_output` and `job_stop` watching anything it detaches as a background job —
138+
plus `read`, `edit`, `multi_edit`, `write`, `remove`, `plan`, `goal`, and
139+
`finish`. There is no shell tool, so no command ever goes through a shell.
137140

138141
```bash
139142
export DEEPSEEK=sk-...

agent/README.mbt.md

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,11 @@ calls `@agent.run`, but that decision lives outside the `agent` package.
103103

104104
`build_tools(runtime, scope)` returns the standard local tool registry:
105105

106-
- `shell`: run a command under the workspace root or an explicit cwd (including
107-
`moon check` for compiler feedback), with `run_in_background` support;
108-
- `shell_output` / `shell_stop`: read or stop a background shell job (omitted on
109-
Windows, where background jobs are not wired);
106+
- `run_moonbit`: compile and run a self-contained MoonBit program — both the
107+
scripting surface (transform files, parse JSON, compute, probe the language)
108+
and the command runner, since processes are spawned from the program through
109+
the shell-free `bobzhang/myshell` EDSL. Supports `run_in_background`;
110+
- `job_output` / `job_stop`: read or stop a background job;
110111
- `read`: read a text file;
111112
- `edit`: replace exact text in a file;
112113
- `multi_edit`: apply several explicit line-anchored replacements to one file;
@@ -115,16 +116,16 @@ calls `@agent.run`, but that decision lives outside the `agent` package.
115116
- `plan`: record or replace the step-by-step plan for a multi-step task;
116117
- `goal`: report standing-goal status (`met`, `continuing`, or `blocked`;
117118
`met` clears the goal — setting one is the serve `goal` command's job);
118-
- `run_moonbit`: compile and run a self-contained MoonBit program in an
119-
isolated package (automation and language probes; standard batteries only,
120-
no local packages);
121119
- `finish`: end the task with a final answer.
122120

121+
There is no shell tool: every command the agent runs is an argument vector
122+
handed to `@myshell.Cmd`, so no command text is ever parsed by a shell.
123+
123124
File-oriented tools capture `runtime.workspace_root()` when the registry is
124125
built. The registry also receives the runtime and task scope for stateful
125-
tools: the shell tools use both — background-job completion notices are pushed
126-
through `runtime.queue_steer`, and the background-job runtime plus its spill-dir
127-
cleanup are owned by the task scope's group.
126+
tools: the background-job path uses both — completion notices are pushed
127+
through `runtime.queue_steer`, and the job runtime plus its spill-dir cleanup
128+
are owned by the task scope's group.
128129

129130
```mbt check
130131
///|
@@ -139,9 +140,6 @@ async test "standard tools are registered in dispatch order" {
139140
],
140141
content=(
141142
#|[
142-
#| "shell",
143-
#| "shell_output",
144-
#| "shell_stop",
145143
#| "read",
146144
#| "edit",
147145
#| "multi_edit",
@@ -150,6 +148,8 @@ async test "standard tools are registered in dispatch order" {
150148
#| "plan",
151149
#| "goal",
152150
#| "run_moonbit",
151+
#| "job_output",
152+
#| "job_stop",
153153
#| "finish",
154154
#|]
155155
),
@@ -283,9 +283,9 @@ answer, `finish`, `abort`, cancellation, unexpected failure, or exhausted
283283

284284
## Operational Notes
285285

286-
This package is intended for trusted local automation. The standard `shell`
287-
tool can run arbitrary commands, while `edit` and `write` can modify files
288-
visible to the process. Use the CLI package for application-level policy,
286+
This package is intended for trusted local automation. `run_moonbit` can run
287+
arbitrary commands (its snippets spawn processes), while `edit` and `write` can
288+
modify files visible to the process. Use the CLI package for application-level policy,
289289
session storage, logging configuration, and serve-mode wire handling.
290290

291291
Run the package tests with:
@@ -312,11 +312,12 @@ improving:
312312
logs through async logging so piped runs such as `2>&1 | tee run.log` receive
313313
step output promptly.
314314
- Current MoonBit projects use `moon.mod`; `moon.mod.json` is legacy. Manifest
315-
or package-import edits should be followed quickly by a shell `moon check` or
316-
another explicit shell validation command.
317-
- Use `shell` for exact end-to-end MoonBit command validation beyond compiler
318-
feedback, especially `moon test`, `moon run`, `moon info`, `moon fmt`, and
319-
README command checks.
315+
or package-import edits should be followed quickly by a `moon check` or
316+
another explicit validation command.
317+
- Use `run_moonbit` for exact end-to-end MoonBit command validation beyond
318+
compiler feedback, especially `moon test`, `moon run`, and README command
319+
checks. Source-writing commands (`moon fmt`, `moon info`) are denied by the
320+
snippet sandbox and belong to the caller, not the agent.
320321
- For snapshot updates, run plain `moon test` first and only run
321322
`moon test --update` after deciding the failure is a stale snapshot or
322323
intentional output change, not a behavior bug.

agent/moon.pkg

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import {
55
"bobzhang/openseek/agent_runtime",
66
"bobzhang/openseek/agent_tool",
77
"bobzhang/openseek/agent_tool/bgjobs",
8-
"bobzhang/openseek/agent_tool/shell_output",
9-
"bobzhang/openseek/agent_tool/shell_stop",
8+
"bobzhang/openseek/agent_tool/job_output",
9+
"bobzhang/openseek/agent_tool/job_stop",
1010
"bobzhang/openseek/agent_tool/edit",
1111
"bobzhang/openseek/agent_tool/finish",
1212
"bobzhang/openseek/agent_tool/goal",
@@ -15,7 +15,6 @@ import {
1515
"bobzhang/openseek/agent_tool/run_moonbit",
1616
"bobzhang/openseek/agent_tool/read",
1717
"bobzhang/openseek/agent_tool/remove",
18-
"bobzhang/openseek/agent_tool/shell",
1918
"bobzhang/openseek/agent_tool/write",
2019
"bobzhang/openseek/deepseek",
2120
"bobzhang/openseek/deepseek/client",

agent/steer_test.mbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ async fn steer_test_server(group : @async.TaskGroup[Unit]) -> String? {
1616
assert_true(request_body.contains("also rename the file"))
1717
// The prebuilt registry reached the request: the standard tools ride
1818
// along even though the caller, not the turn, constructed them.
19-
assert_true(request_body.contains("\"name\":\"shell\""))
19+
assert_true(request_body.contains("\"name\":\"run_moonbit\""))
2020
conn.send_response(200, "OK", extra_headers={
2121
"Content-Type": "text/event-stream",
2222
})

0 commit comments

Comments
 (0)