Stoat Shell Mode
Stoat Shell Mode
Section titled “Stoat Shell Mode”Stoat’s headline ergonomic is the shell-mode toggle. The same REPL buffer can either feed the LLM agent or run shell commands directly, and the user toggles between the two without leaving the prompt.
The toggle
Section titled “The toggle”Three ways to flip:
- The slash command
/shell— works in any terminal. - The keyboard chord
Ctrl-X— for terminals that don’t intercept it for their own bindings (the slash command is always available as a fallback). - The CLI flag
--shell-mode— boots stoat directly into shell mode.
When the manager is in agent mode (the default) the prompt is
stoat> and each non-slash input is fed to the agent as a turn. When
in shell mode the prompt is stoat$ and each non-slash input is
executed as bash -c <input> against the current working directory.
stoat> /shell(shell mode: each input runs as 'bash -c <input>'. Type /shell to return to agent mode.)stoat$ pwd/Users/me/projstoat$ ls -la…stoat$ /shell(agent mode)stoat>Slash commands are dispatched regardless of the current mode — /shell
itself has to be reachable from inside shell mode to flip you back, and
/help / /exit etc. work in both.
Semantics
Section titled “Semantics”- Empty input in either mode just re-prompts.
/-prefixed input runs the slash dispatcher (full palette inslash-commands.md).- Non-slash input in agent mode runs
Agent.async_run(input)against the configured provider, with all default tools available. - Non-slash input in shell mode runs
bash -c <input>(or$BASH -cif the env var is set) inside the REPL’s working directory. stdout and stderr are captured and rendered together; the exit code is surfaced as a[exit N]marker when non-zero. - Empty stdout + zero exit prints
[exit 0]so the user knows the command ran (mirrors what most shells do for “no output, no error”).
The two paths share one history buffer, tagged by mode so /history
can render them side by side:
stoat$ /history 4> list the top-level files> read the README$ pwd$ ls -laLimits
Section titled “Limits”Shell mode is intentionally minimal — it’s not a full shell. Specifically:
- Built-ins like
cdare not honored. Each command runs in its ownbash -csubprocess, socd ../fooexits the subprocess immediately with no effect on the REPL’s working directory. Use--cwd(CLI) or embed stoat to drive it programmatically if you need long-lived directory state. - No prompt customisation per directory. The prompt prefix
(
stoat$) is fixed unless you embedShellModeManagerin your own code and pass a customshell_prompt. - No interactive child processes.
bash -ccaptures stdout/stderr by default, sovim,top,tmuxetc. won’t render. Drop to your shell for those.
The point isn’t to replace your shell — it’s to keep tactile shell moves on hand while you’re driving the agent.
Driving the toggle from code
Section titled “Driving the toggle from code”The state machine lives in chimera.stoat.shell_mode.ShellModeManager.
Embedders can use it directly:
from chimera.stoat.shell_mode import ShellModeManager, MODE_SHELL
mgr = ShellModeManager()mgr.set_mode(MODE_SHELL)result = mgr.run_shell("ls -la", cwd="/tmp")print(result.stdout)print(f"exit: {result.returncode}")The manager is purely state — it doesn’t own a REPL loop. The full
loop (prompt → dispatch → render) lives in
chimera.stoat.repl.StoatRepl, which is also exposed for embedders
who want the toggle wired into their own UI.
Driving from tests
Section titled “Driving from tests”StoatRepl accepts an input_fn parameter so test code can script the
loop without a TTY:
import iofrom chimera.stoat.repl import StoatRepl
inputs = iter(["/shell", "echo hi", "/shell", "/exit"])out = io.StringIO()
repl = StoatRepl( model="kimi-k2.6", workdir=".", out=out, input_fn=lambda _prompt: next(inputs),)repl.run()assert "hi" in out.getvalue()The same hook drives the test suite under tests/stoat/test_repl.py.
See also
Section titled “See also”slash-commands.md— full slash palette.quickstart.md— install + first run.chimera/stoat/shell_mode.py— the state machine source.chimera/stoat/repl.py— the REPL loop that consumes it.