Skip to main content

shellsim

shellsim is a deterministic, resource-constrained BusyBox-like environment for evaluating agents. Shell programs and Unix-style commands run in-process against an in-memory filesystem; they never execute host programs or use the host filesystem as their working environment.

The resource model is deliberately approximate. Commands use ordinary Rust data structures while reserving modeled memory and charging stable abstract CPU units. This keeps the model predictable, cheap, and easy to tune.

See docs/agent-environment.md for the reviewed gap between the current simulator and a useful Unix-shaped coding-agent harness, plus the ordered implementation roadmap.

Resource model

  • CPU is monotonic fuel. Parsing, executor nodes, dispatch, input, output, and algorithms consume units. Exhaustion stops the evaluation.
  • Memory is modeled concurrent working set. Command reservations are released on return; nested invocations contribute to the same peak.
  • Disk is logical in-memory filesystem size. Content and a fixed 256-byte non-root node overhead count. Mutations that exceed quota roll back atomically, and deletion releases capacity.
  • Output caps materialized stdout and stderr as a safety guardrail.

Defaults are 10,000,000 CPU units, 64 MiB memory, 64 MiB disk, and 4 MiB output. Costs are deterministic rather than cycle-accurate. Results include a cost-model version.

Build and use

cargo build --release

# Ordinary output
./target/release/shellsim -c 'printf "b\na\n" | sort'

# Host file used only as script source; execution occurs in a fresh simulated environment
./target/release/shellsim run script.sh arg1 arg2

# Persistent interactive session; state and resource usage accumulate until exit/exhaustion
./target/release/shellsim shell --cpu 100k --memory 8m --disk 2m --output 64k

# Structured evaluation report
./target/release/shellsim eval \
  --cpu 100k --memory 8m --disk 2m --output 64k \
  -c 'printf "b\na\n" | sort > result.txt; cat result.txt'

# Persistent NDJSON harness session
printf '%s\n' \
  '{"id":1,"op":"execute","source":"printf hello > result"}' \
  '{"id":2,"op":"workspace_diff"}' \
  | ./target/release/shellsim serve --root ./project

# Retain an action, observe its timer wait, then permit virtual-time advancement
printf '%s\n' \
  '{"id":1,"op":"start_execute","source":"printf one; sleep 2; printf two"}' \
  '{"id":2,"op":"poll_action","action_id":0,"work_quanta":100,"advance_time":false}' \
  '{"id":3,"op":"read_action_output","action_id":0}' \
  '{"id":4,"op":"poll_action","action_id":0,"work_quanta":100,"advance_time":true}' \
  | ./target/release/shellsim serve

# Cancel a blocked foreground action while keeping the session reusable
printf '%s\n' \
  '{"id":1,"op":"start_execute","source":"sleep 60"}' \
  '{"id":2,"op":"cancel_action","action_id":0}' \
  '{"id":3,"op":"execute","source":"printf reused"}' \
  | ./target/release/shellsim serve

# Running background jobs can return to the modeled terminal foreground
./target/release/shellsim -c 'sleep 2 & fg %1; echo complete'

# Fork session zero and route an independent action to the branch
printf '%s\n' \
  '{"id":1,"op":"fork_session","source":0}' \
  '{"id":2,"session_id":1,"op":"execute","source":"printf branch"}' \
  | ./target/release/shellsim serve

# Replay a bounded scenario and emit paired request/response transcript records
./target/release/shellsim replay scenario.ndjson --root ./project > transcript.ndjson

# Enable format checks, strict trust, and final assertions with a metadata line
printf '%s\n' '{"scenario":{"version":1,"strict":true,"final_expectation":{"active_action_count":0}}}' \
  '{"op":"execute","source":"make test"}' > strict-scenario.ndjson
./target/release/shellsim replay strict-scenario.ndjson

# Import a host Python project into a fresh VFS and run it in shellsim
./target/release/shellsim-python project/main.py -- arg1
./target/release/shellsim-python project/tests --pytest
./target/release/shellsim-python --json --root project project/main.py

# Embed a persistent simulated environment from Python
python -m pip install shellsim
python - <<'PY'
import shellsim

environment = shellsim.Environment(cpu=100_000)
environment.write_file("/work/main.py", "print(6 * 7)\n")
result = environment.run("python3.14 /work/main.py")
assert result.stdout == b"42\n"
PY

# Run the PyPI package as an ephemeral tool
uvx shellsim -c 'printf "b\na\n" | sort'

# Copy a trusted host project into /work, then modify only the disposable VFS snapshot
uvx shellsim --root ./project -c 'pwd; find . -type f; make test'

# Open a persistent interactive simulator rooted at a host-project snapshot
uvx shellsim --root ./project

Limit values accept k, m, and g binary suffixes. Arguments after -- in eval mode become shell positional parameters.

serve retains one environment across requests. --root performs one trusted, bounded import before request processing. The protocol supports shell actions, base64 file reads and writes confined to /work, stable path-level workspace diffs, checkpoints, VFS reset, listings, and process/resource inspection. One JSON response is emitted for each input line, which makes the request/response stream directly replayable. See docs/implementation.md for the protocol boundary and current limitations.

For development, make format, make lint, and make test are the canonical local commands and the exact entrypoints used by CI. See CONTRIBUTING.md for code, testing, review, and optional pre-commit-hook guidelines.

The JSON report contains the exit status, typed stop reason, limits, aggregate usage, per-command CPU/disk deltas, stdout, stderr, command trace, and unsupported capabilities.

shellsim-python and serve --root share one transactional importer. They treat the host path as trusted harness input, reject symlinks, preserve permission bits, copy the project into /work, then close that boundary before simulated execution starts. A Python directory automatically discovers test_*.py files; --entry FILE selects a script within a directory. Use --root to control which project tree is imported and the standard limit flags to constrain the run.

The PyPI package exposes shellsim.run for one fresh action and shellsim.Environment for a persistent VFS, variables, processes, and cumulative resource budget. Results preserve stdout and stderr as bytes and include resource, unsupported-capability, no-op, partial-command, and invocation telemetry. Environment.mount is an explicit trusted-host operation with the same symlink rejection and rollback behavior as the CLI importer. The extension never installs the standalone binaries' process-wide seccomp filter, so importing or using it does not restrict the embedding Python process. Simulated programs still execute through the capability-free Rust library and cannot reach ambient host resources.

The installed shellsim console command uses the same facade. With a terminal it opens a persistent simulated shell; with piped input it executes that input as shell source, and -c executes one action. --root DIR performs a bounded, symlink-rejecting snapshot copy into /work. Changes made by simulated commands are never written back to the host directory.

Virtual time

An environment owns deterministic monotonic, wall, and process-CPU clocks. Sleeps and deadlines advance the event queue without blocking a host thread; VFS timestamps and Python observe the same timeline. Runnable work has zero virtual duration and is bounded by CPU fuel. Background jobs, pipelines, and nested shells run through the deterministic cooperative scheduler, so independent sleeps overlap in virtual time. See docs/implementation.md for the state, scheduler, and replay contracts.

Persistent shell sessions

An Environment is a session, not a single command. Reusing it across run_script_capture calls preserves the VFS, working directory, variables, arrays, functions, package state, clock/network state, command history, and cumulative resource usage. CPU and output are cumulative fuel, disk tracks current persistent usage, and temporary command memory is released while its peak remains.

exit N, set -e termination, CPU exhaustion, memory exhaustion, and output exhaustion make the session terminal. Later calls return the same terminal outcome without executing or charging more work. Disk-full errors are recoverable: a command can remove files and retry.

The shell subcommand drives one such environment line by line. It shows a prompt on a terminal, preserves state between lines, exits normally on EOF or exit, and prints a reason before exiting with status 137 when a resource is exhausted. It is an action console rather than a resumable terminal: each completed action has closed stdin. Use a pipe or heredoc for command input. The console collects a heredoc through its terminating delimiter before executing the action.

Invoking python without arguments transfers the foreground session to a deliberately-minimal Python REPL. Simple assignments and expressions persist across actions; exit() or quit() returns to the shell. This is a modeled process mode, not access to host CPython.

Bash-ish compatibility

The shell intentionally targets common agent-written Bash rather than the full Bash grammar. It supports functions, indexed and associative arrays, if/case/for/while/until, C-style for ((...)) loops, ((...)), pipelines, &&/||, background jobs, groups and subshells, heredocs and here-strings, command/arithmetic substitution, brace expansion, parameter expansion, globbing, [[...]], and frequently used set options including pipefail.

Standard paths such as /bin/sh and /usr/bin/env resolve to their simulated commands. More specialized Bash behavior, including process substitution, trap pseudo-events, coprocesses, arbitrary process-group mutation, and some descriptor forms, remains outside the faithful subset. Logical children provide isolated shell state, stable PIDs, overlapping virtual-time jobs, bounded pipes, jobs/wait, default and caught signal delivery, fg/bg with STOP/CONT, dynamic ps, and generated /proc views without creating host processes.

Command implementations

Commands receive a uniform environment context:

fn run(env: &mut CommandContext<'_>, args: &[String], io: &mut Io) -> i32

The dispatcher applies each command's coarse base CPU and memory cost. Commands add dynamic costs when useful:

if !env.reserve_memory(input.len() as u64 * 2) {
    return 137;
}
if !env.charge_cpu(input.len() as u64) {
    return 137;
}

New commands should live in a focused module and use only the modeled command context. See docs/implementation.md for the integration checklist, trust levels, resource rules, and the reason native compilers remain outside the simulation.

The current command set includes filesystem and text coreutils, grep, sed, a useful partial awk, hashes and encoders, bounded tar and gzip tools, virtual curl/wget, deterministic Git and Make subsets, shell builtins, minimal package/Python launchers, and simulated system queries such as env, printenv, uname, id, nproc, df, free, and ps. Partial commands are surfaced in evaluation reports instead of being presented as fully faithful implementations.

Disk enforcement lives inside Vfs, so direct command mutations cannot bypass capacity checks. Commands should still surface VfsError::NoSpace with a non-zero status.

Python 3.14 compatibility

python, python3, and python3.14 route to shellsim's safe in-process interpreter. Source goes through a UTF-8/indentation-aware lexer, owned AST, semantic bytecode compiler, and metered stack VM; host CPython is never invoked. The current language slice covers scalar and mutable containers, comparisons and control flow, functions/closures/defaults/*args, classes and bound methods, user inheritance with C3 lookup, int subclasses, constrained metaclasses, comprehensions, suspended generators, exceptions and context managers, assert, decorators, starred assignment/calls, f-strings, VFS-only imports, common iterator/container builtins, and the modeled REPL/script/stdin/shebang entrypoints. Unsupported syntax and APIs fail loudly with a diagnostic.

Native Python modules use an erased value ABI, checked object views, declarative type/module tables, and narrow modeled capabilities. See docs/python.md for the goals, value and object model, extension workflow, compatibility evidence, and explicit frontiers.

The requested stdlib gate is 21/21 exact CPython 3.14 probes for these APIs: sys.executable, os.getenv, collections.defaultdict, itertools.count/islice, heapq.heapify/heappop, bisect.bisect_left, math.sqrt/ceil, string.digits, json.dumps(sort_keys=...), re.sub, functools.reduce, dataclasses.dataclass, typing.List[...], enum.Enum, argparse.ArgumentParser.prog, csv.reader/writer, source-backed Counter, deque, json, os.path, datetime, byte-preserving base64, hashlib, struct, and zlib, import subprocess, and the pytest/unittest.TestCase entry points. These are intentionally partial module slices, not claims of complete stdlib support.

pytest and unittest are VFS-only first runner slices: explicit files, stable definition-order collection, plain zero-argument pytest tests, direct unittest.TestCase classes, tested assertions/ skip/raises controls, and bounded wrappers. Fixtures, decorated tests, plugins, rich parametrization, async fixtures, directory/package discovery, and unlisted flags are rejected explicitly. The 100-row TaskTrove mini corpus is differential-tested with per-row provenance (99 supported, one async frontier), and one complete build-system-task-ordering solution matches CPython 3.14. CPU fuel, modeled memory, output, source/wrapper size, and nesting limits keep this general-purpose slice safe and deliberately slow.

Library API

use shellsim::{Environment, Limits};

let mut env = Environment::with_limits(Limits {
    cpu: 100_000,
    memory: 8 * 1024 * 1024,
    disk: 2 * 1024 * 1024,
    output: 64 * 1024,
});

let (outcome, stdout, stderr) = env.run_script_capture("echo hello");

// Harness actions may attach stdin without giving the simulated command host-terminal access.
let (outcome, stdout, stderr) =
    env.run_script_capture_with_stdin("cat > input.txt", b"hello\n");

An Environment preserves its VFS, working directory, variables, functions, options, and resource usage across actions. Each action receives its own explicit stdin byte stream; an input redirect in the action takes precedence. New environments include /root, /tmp, and /work.

Interp remains as an alias for Environment for source compatibility.

Layout

src/resources.rs       limits, accounting, outcomes, command usage
src/interp.rs          machine Environment and shell-local ProcessState
src/process.rs         bounded logical process identities and lifecycle
src/pseudo_fs.rs       generated read-only /proc and finite /dev views
src/vfs.rs             quota-enforced in-memory filesystem
src/shell.rs           lexer, parser, capture API
src/expand.rs          shell expansion
src/exec.rs            metered executor, pipelines, redirects, control flow
src/commands/          registry, command context, implementations
src/commands/awk.rs    partial record-oriented awk
src/commands/system.rs simulated environment/system queries
src/python/            Python 3.14 lexer, parser, bytecode compiler, and metered VM
src/clock.rs           virtual clock
src/net.rs             virtual route-table network
docs/implementation.md architecture and command integration guide
docs/python.md         Python goals, runtime model, and extension guide
docs/agent-environment.md reviewed agent-harness gaps and roadmap

Run the unit and resource-invariant tests with cargo test.

Release files for shellsim 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for shellsim 0.1.2
File Size Uploaded
shellsim-0.1.2.tar.gz 394.8 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for shellsim 0.1.2
File
shellsim-0.1.2-cp39-abi3-win_amd64.whl CPython 3.9 abi3 Windows x86-64 Details
shellsim-0.1.2-cp39-abi3-manylinux_2_28_x86_64.whl CPython 3.9 abi3 Linux glibc 2.28+ x86-64 Details
shellsim-0.1.2-cp39-abi3-manylinux_2_28_aarch64.whl CPython 3.9 abi3 Linux glibc 2.28+ ARM64 Details
shellsim-0.1.2-cp39-abi3-macosx_11_0_arm64.whl CPython 3.9 abi3 macOS 11.0+ ARM64 Details
shellsim-0.1.2-cp39-abi3-macosx_10_12_x86_64.whl CPython 3.9 abi3 macOS 10.12+ x86-64 Details

Total release size: 10.3 MB

Release files / shellsim-0.1.2.tar.gz

Download URL shellsim-0.1.2.tar.gz
Size 394.8 kB
Tags Source
SHA-256 checksum
How to use checksums
63ab1b8f8dd0a6ad630a60896437bda6cc98d3d65108167b58a918e25ad610a5
BLAKE2b-256 checksum
How to use checksums
c5f9544a2cc2c526f9a5d87193b8bc50f14d2258a08a7c3bbf9db2f8aeddb22b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.

Transparency log

Release files / shellsim-0.1.2-cp39-abi3-win_amd64.whl

Download URL shellsim-0.1.2-cp39-abi3-win_amd64.whl
Size 1.9 MB
Tags CPython 3.9 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
9663170751a1abe934b84f03ee3a9f3d8c2c6474694402ee9ecfa18b7d0615a2
BLAKE2b-256 checksum
How to use checksums
a5d8f95ef5a50db08e340b1508ab26b5002f14b3e981663010588fe30ad3ee06
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.

Transparency log

Release files / shellsim-0.1.2-cp39-abi3-manylinux_2_28_x86_64.whl

Download URL shellsim-0.1.2-cp39-abi3-manylinux_2_28_x86_64.whl
Size 2.1 MB
Tags CPython 3.9 Linux glibc 2.28+ x86-64 abi3
SHA-256 checksum
How to use checksums
7fe869368077b13f6c6f88a78460107665c757594b476a9ffa09fffb7f7de4d7
BLAKE2b-256 checksum
How to use checksums
caa70f4f36c95a8ce51ec0bae564a13f0c62691daec16ef965719ca5977e35b8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.

Transparency log

Release files / shellsim-0.1.2-cp39-abi3-manylinux_2_28_aarch64.whl

Download URL shellsim-0.1.2-cp39-abi3-manylinux_2_28_aarch64.whl
Size 2.0 MB
Tags CPython 3.9 Linux glibc 2.28+ ARM64 abi3
SHA-256 checksum
How to use checksums
2d3bc5efb7da726d904bb673e17a6dee8d76cf5ed0d9163fde53e045251f16bf
BLAKE2b-256 checksum
How to use checksums
94fe605c48cff70a48201facc0bd244948d8cb9f892a598403c775575a652484
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.

Transparency log

Release files / shellsim-0.1.2-cp39-abi3-macosx_11_0_arm64.whl

Download URL shellsim-0.1.2-cp39-abi3-macosx_11_0_arm64.whl
Size 1.9 MB
Tags CPython 3.9 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
375eff214f3401bf28218be03a7644abf42b21118bdd91c9d98c8776a0f80a37
BLAKE2b-256 checksum
How to use checksums
36bcb42a37d2538f5f69069753840eacd9b24455775018f5a21c5a5c05affc18
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.

Transparency log

Release files / shellsim-0.1.2-cp39-abi3-macosx_10_12_x86_64.whl

Download URL shellsim-0.1.2-cp39-abi3-macosx_10_12_x86_64.whl
Size 2.0 MB
Tags CPython 3.9 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
72cda0092555cc014476b7c4ca0685cedcca86b2cfae0d8e4730404ad8bc6d4e
BLAKE2b-256 checksum
How to use checksums
73bda938147c26556a42f935b6eeef17253c5ddf74a4b5ef1fe483200905e8c3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.

Transparency log

Release history Release notifications | RSS feed

0.1.17

6 release files

0.1.16

6 release files

0.1.15

6 release files

0.1.14

6 release files

0.1.13

6 release files

0.1.12

6 release files

0.1.11

6 release files

0.1.10

6 release files

0.1.9

6 release files

0.1.8

6 release files

0.1.7

6 release files

0.1.5

6 release files

0.1.4

6 release files

0.1.3

6 release files

This release

0.1.2 This release

6 release files

0.1.0

6 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page