- TypeScript 88%
- JavaScript 6.3%
- Just 2.6%
- Shell 2%
- Dockerfile 1.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| bin | ||
| container | ||
| extensions/sloptrap | ||
| src | ||
| test | ||
| themes | ||
| .containerignore | ||
| .gitignore | ||
| .sloptrap.example.yaml | ||
| AGENTS.md | ||
| ARCHITECTURE.md | ||
| CHANGELOG.md | ||
| chart.mermaid | ||
| justfile | ||
| LICENSE | ||
| OPERATIONS.md | ||
| package.json | ||
| README.md | ||
| RELEASING.md | ||
| SECURITY.md | ||
| SUPPORT.md | ||
| tsconfig.json | ||
sloptrap
sloptrap is a streamlined Pi coding CLI with Codex-inspired durable goals and a fail-closed Podman tool sandbox. Pi runs in a disposable trusted control container, its ChatGPT OAuth session stays in a dedicated rootless Podman volume, and model-driven file and shell operations run in separate rootless containers.
The launcher disables Pi's built-in tools and extension discovery, then loads only this repository's extension. If that extension or its sandbox cannot start, there is no automatic host-tool fallback.
Security limitations
The model tool containers are isolated from the host's credentials, Podman socket, and normal home directory, but the Pi control container is part of the trusted computing base. It has the rootless Podman socket to create tool containers and a persistent volume containing OAuth and session state. A compromise of Pi, the bundled extension, its dependencies, or the boundary patches can therefore compromise those control-plane assets. The isolation model reduces the effect of model-directed workspace commands; it is not a sandbox for the control plane itself.
exec_command starts offline. When a command explicitly requests network: true, it receives the host network namespace after the configured or session approval described below. That permits access to public, LAN, VPN, and localhost services and can transmit every project file that is not explicitly masked. The permission is not an allowlist, a proxy, or a per-destination approval. Review the selected project's masks before enabling it.
Masks are literal exclusions. Before every normal Pi launch, sloptrap runs an offline Titus scan of unmasked current workspace files and asks whether each affected file should be masked. Existing masks and .git are excluded from that scan; a file left exposed is asked about again on the next launch. Titus findings are advisory pattern matches, and sloptrap never displays matched values or snippets. The sandbox intentionally has write access to every non-masked path beneath the selected project. Do not rely on a container boundary to protect secrets that are present in that project.
What is implemented
/goal <objective>with durable, branch-aware state and uncapped automatic continuation/goal pause,/goal resume,/goal edit, and/goal clear- persistent model-managed plans:
task_listand atomictask_update; use/tasksto view or clear the active plan - advisory
/plan [request], interactive multi-questionquestion(with optional comments),/ctx, and/status - Codex-style
apply_patchblocks for bounded contextual changes, exact single-fileedit, and colored per-file change counts, plusexec_commandandwrite_stdinfor supervised command sessions - rootless Podman with all capabilities dropped,
no-new-privileges, read-only rootfs, resource limits, and an offline long-lived sandbox - only the current project is mounted; configured secret files/directories are overlaid with empty mounts
- Git runs inside the sandbox
exec_commanduses fresh network-enabled containers only when itsnetwork: trueoption is approved for the session or enabled by trusted project configuration- direct Wayland/GPU execution is disabled until it can be placed behind a nested compositor and software-rendering boundary
- bridge commands stream a throttled, last-10-line preview while running; their final bounded output remains available to the model
- OpenAI native web search is available only when trusted project network access is enabled; image generation remains disabled
- project configuration is strict and trusted by exact SHA-256 digest before image builds or sessions
The implementation is independent of the legacy skz-sloptrap-old launcher.
Container-first usage
The recommended entry point needs only Bash, rootless Podman, and Git on the host. Install once from this checkout; Node, npm 12, Pi, and the CLI itself run in a disposable container:
systemctl --user enable --now podman.socket
cd /path/to/skz-sloptrap
just install
cd /path/to/the/project-you-want-pi-to-edit
sloptrap
Normal startup performs preparation automatically and reports progress while preparing the trusted control image. If .sloptrap.yaml is absent, the launcher creates a minimal schema-version-1 file. When packages is absent, the startup wizard asks for an optional space-separated list of Debian packages to install in the project sandbox. When inference is absent, it then asks whether to use OpenAI or the fixed local llama.cpp endpoint at http://127.0.0.1:8080/v1/models; local models are queried and displayed before selection, and both choices are written back to .sloptrap.yaml. The configuration wizard then presents the complete project capabilities—including inference, packages, network, and masking—and records the exact normalized digest after approval. For a changed configuration, it shows only differences from the last trusted configuration. GUI access is disabled in this release. A missing sandbox image is then built before Pi starts.
To reset one project, run just project-clean from that project directory. It removes stopped tool containers and Pi session history for that project and any trusted parent/child project paths, removes the current project's sandbox image tag, and invalidates trust for the current path plus trusted ancestors and descendants. Sibling project trust is preserved. Running sloptrap containers are never modified: the command lists all of them and prints an explicit podman rm --force ... command if you choose to stop and prune them. It preserves .sloptrap.yaml and the shared OAuth login. The next just pi reviews the configuration and rebuilds automatically.
When OpenAI is selected without existing credentials, startup prepares /login openai-codex in the editor; press Enter to begin. The container entry point automatically selects Pi's device-code flow, so it does not expose or depend on a localhost callback port. Open the displayed URL and enter its short code. The resulting credentials and refresh token are stored in the rootless Podman volume sloptrap-state, so later CLI containers reuse the login without prompting.
For local use, start llama.cpp on host loopback port 8080 before launching sloptrap. The launcher refreshes /v1/models and /props on every start, uses the reported runtime context rather than the model's larger training limit, and fails closed if the saved model disappears. Qwen3.8 reasoning levels map to off, low, medium, and xhigh; the model-card sampling profiles, preserved reasoning, developer messages, and tool calling are enabled. The current text-only llama.cpp launch does not advertise vision, so sloptrap does not expose image input for it.
The same volume also holds Pi sessions and trusted configuration digests. Each host project gets a stable, path-derived working directory inside the CLI container so sessions and trust do not collide across repositories. Sessions therefore survive sloptrap container lifecycles: use sloptrap resume to continue the latest project session, or sloptrap resume <session-id-or-path> for a specific session. Pi's /resume command and sloptrap --resume open its interactive session picker. The wrapper deliberately fixes the volume name and derives the target project from its physical working directory. This prevents project-controlled shell environment variables from redirecting either security boundary. Change into the intended project directory before invoking sloptrap.
sloptrap reserves exactly 10% of the selected context window for automatic compaction and retains at least 32k recent raw tokens. This leaves a proportional response budget for each model and context limit; Pi performs the checkpoint once the model/tool loop settles. Compaction is provider-specific. OpenAI uses the Codex Responses compaction_trigger protocol: sloptrap persists the returned opaque compaction item and replays it unchanged on later turns and later compactions, while retaining a bounded set of real user messages as Codex does. If native compaction fails or omits opaque state, the session is left unchanged. The local provider instead creates a checkpoint summary below 8k tokens and appends deterministic anchors for durable goal/plan state, cumulative file operations, and verbatim discarded user requests. Larger compaction.keepRecentTokens values in Pi settings are preserved.
The wrapper rebuilds the CLI image through Podman's cache on startup, captures its immutable image ID, and launches that ID with a fixed environment. The project is visible to this control container only through a read-only, non-recursive bind. Pi's project settings, migrations, resource discovery, and Git footer discovery are disabled there by version-sensitive build patches that fail if the pinned upstream implementation changes. The wrapper does not parse or copy host/repository Git configuration. It does not mount an SSH agent or HTTPS credential helpers from the host into any model tool container.
Pi telemetry is disabled in the containerized control plane.
Diagnostics, support, and operations
Run sloptrap doctor for a concise local health check. sloptrap doctor --bundle
creates a local sloptrap-diagnostics.* directory containing a reviewed-support
manifest. It is opt-in and intentionally excludes workspace files, project and
configuration contents, OAuth/session state, environment variables, logs, and
command arguments. Review diagnostics.json before sharing it. See
SUPPORT.md for safe reporting and supported-environment scope,
OPERATIONS.md for recovery runbooks, and
SECURITY.md for vulnerability reporting and incident handling.
sloptrap loads its bundled Sloptrap dark theme automatically. It uses neutral gray tool labels and output, high-contrast green/red diffs, and the Sloptrap syntax palette for code blocks and file tool output. The pre-session configuration wizard and final secure-sandbox shutdown feedback use the same terminal palette.
Live elapsed times are rendered in the terminal footer, not into prior transcript cards. The transcript remains append-only except while a card receives streamed output; this keeps one-second working and command timers bounded to the visible footer rather than replaying terminal scrollback in long sessions. Active work and jobs take priority in narrow terminals; secondary usage telemetry is shown only when space permits. In regular mode, the pinned Pi renderer updates a verified visible footer directly and safely falls back to its normal renderer after a resize or any uncertain viewport state.
Caller host commands
In the interactive terminal, prefix a command with ! to run it yourself as the
host user in the selected project's real directory. sloptrap always asks for a
confirmation first, bounds and sanitizes the combined output, then records that
output in the conversation for the model to inspect. For example, !git status --short lets you share the host Git state without granting the model a host
shell. Use !!command to run the same caller-owned command without adding its
output to model context.
This is intentionally not an agent tool. The request passes over a private, per-session launcher broker that is mounted only into the trusted control container; model tool containers never receive its FIFO paths or authentication token. Host commands have the caller's normal authority, including network and credentials available to that user, so approve them as carefully as ordinary terminal commands.
The footer converts provider-reported USD cost to euros with two decimal places using the pinned ECB reference 1 EUR = 1.1392 USD. The rate is intentionally static because the long-lived sandbox remains offline.
Requirements
- Linux with rootless Podman
- Bash
- a ChatGPT Plus/Pro account or a compatible llama.cpp server on
127.0.0.1:8080
Install
To install the container-first launcher without npm, run this from the cloned repository:
just install
It selects the first writable directory from ~/.local/bin, /usr/local/bin, and the platform-standard alternatives, installs the runtime tree under the corresponding lib/sloptrap directory, and links sloptrap into that bin directory. Override the destination with INSTALL_DIR=/path/to/bin just install; use just uninstall with the same INSTALL_DIR to remove it.
On first launch, sloptrap asks for additional Debian packages, masked workspace paths, network-bridge availability, and the model provider. It then reviews the resulting configuration and builds a missing sandbox automatically. Enabling the network bridge authorizes exec_command with network: true for that trusted project without additional per-command prompts; otherwise that option asks once for the current session. Use sloptrap clean when you want the next launch to review and rebuild the current project.
Inside containerized Pi when OpenAI was selected:
- Run
/loginand choose OpenAI (ChatGPT Plus/Pro) · device code. The device-code method starts automatically. - Run
/modeland selectgpt-5.6-solwhen present. If Pi exposes onlygpt-5.6, OpenAI currently routes that alias to the Sol snapshot. - Run
/statusto confirm the model and sandbox. - Try
/goal Implement and verify a small change.
The launcher stores OAuth and session state in sloptrap-state. It never mounts the host home, Pi auth state, SSH agent, Podman socket, or credential stores into a model tool container.
The installed sloptrap runtime must live outside the selected model-writable project. To work on sloptrap itself, run just install first and invoke that installed launcher; the source-tree launcher deliberately refuses this self-hosting layout.
Configuration
.sloptrap.yaml is optional. When present, it must declare schemaVersion: 1. Defaults use a digest-pinned Debian image, mask .env and .env.local, and limit the sandbox to 8 CPUs, 8 GiB RAM, and 512 processes. .sloptrap.example.yaml is the fully annotated template; the following is the same complete schema.
schemaVersion: 1
# Digest-pinned image for model tool containers.
baseImage: docker.io/library/debian@sha256:f324c7ff54321e8d9c588493a20244965938ce0aa50bbd1022d38010e9ffc4b1
# Debian packages installed in the sandbox image (maximum 64).
packages:
- build-essential
# Project-relative paths hidden from model tool containers. A trailing slash is
# an ephemeral directory; a sized directory gets that tmpfs quota.
mask:
- .env
- .env.local
- secrets/
- path: src/.venv/
size: 1G
# Trusted true permits exec_command calls that explicitly request network access.
network: false
# Must remain false; GUI execution is unavailable in this release.
gui: false
# Select exactly one provider. OpenAI uses device-code login.
inference:
provider: openai
# Local llama.cpp alternative. endpoint is optional and defaults to
# http://127.0.0.1:8080; it must be an http(s) server root (not a /v1 URL),
# without query, fragment, or embedded credentials.
# inference:
# provider: local
# modelId: "loaded-model-id" # Must appear at endpoint/v1/models.
# endpoint: "https://llama.example.test"
# Per-container resource ceilings, not reservations.
resources:
cpus: 8 # 0.25 through 64
memory: 8g # 256m through 64g; k, m, or g
pids: 512 # Integer 32 through 4096
Resource values are per-container ceilings, not reservations. A session with only its long-lived sandbox uses only the resources its processes actually consume; starting a temporary background or bridge container does not pre-divide or reserve the configured RAM. Background-container proliferation remains capped independently.
Masks are literal project-relative paths, not globs. A trailing slash explicitly reserves an ephemeral writable directory (secrets/); its default tmpfs quota is 32m. A directory can opt into a different quota with { path: directory/, size: 1G }; units K, M, and G are case-insensitive, with values accepted from 1m through 8g. A size is invalid for file masks: existing regular files and unadorned absent paths remain /dev/null mounts that discard sandbox writes (.env). Existing targets retain their real file or directory type, so prior configurations remain valid. Directory tmpfs quotas are per-container maximums rather than reserved memory; written pages count toward that sandbox's memory limit. Podman creates an empty, type-correct host sentinel for an absent target to establish the mountpoint; all model-created contents remain in the tmpfs or are discarded, never written to that sentinel. Sentinels created for a session are removed when it exits only if their exact inode is still empty and unchanged. Mounted targets cannot be removed or replaced by model code. .git is intentionally visible unless explicitly masked. Unknown keys, path escapes, shell metacharacters in package names, and invalid resource values are rejected. Changing the base image or packages changes the deterministic sandbox image tag.
For local inference, endpoint replaces the default llama.cpp server root only; sloptrap derives endpoint/v1 for inference, endpoint/v1/models for model discovery, and endpoint/props for capability discovery. The endpoint is trusted configuration: changing it requires a new configuration review. It supports endpoints that provide llama.cpp's model catalog and /props capability metadata; it does not add API keys or arbitrary provider adapters.
Workspace and approval model
One writable offline sandbox is retained for the active Pi session. Reloads, forks, resumes, and session changes remove and recreate it rather than inheriting an incompletely verified container. /plan is a read-only conversational mode: it disables task_update, explores the workspace, and finishes with a decision-complete <proposed_plan> artifact. Accepting it through the handoff or /plan implement[-new] restores normal tools and supplies the proposal once as implementation context; an execution checklist is created only when it is genuinely useful. The root filesystem remains read-only, and the long-lived tool container never gets a network interface.
When a regular top-level AGENTS.md exists in the selected project, sloptrap adds its bounded contents (at most 8,192 conservative token bytes) to each agent turn as project guidance. Before a model tool targets a nested subtree, sloptrap automatically loads the applicable root-to-subdirectory AGENTS.md chain invisibly and restarts that model turn with the guidance in context. This keeps unrelated nested guidance out of every turn while applying local guidance before the operation. It does not search parent directories or follow symlinks, and ignores empty or binary files. The descriptor opened with O_NOFOLLOW is also the descriptor that is read, preventing an instruction-file symlink swap between validation and use. AGENTS.md cannot override sloptrap's runtime and safety rules or grant additional capabilities.
sloptrap reserves a 4 GiB /tmp tmpfs for transient agent work. Agents are instructed to keep scratch files, downloads, clones, build artifacts, virtual environments, and dependency caches there rather than polluting the project. Temporary managed installs use /tmp/sloptrap-tools; its bin directory is already on PATH. The pinned image's Rustup installation remains read-only at /usr/local/rustup; choose the Rust version through baseImage, rather than downloading a replacement toolchain during a session.
baseImage must be a digest-pinned image reference (@sha256:...); mutable tags are rejected. network is an opt-in trusted project capability reviewed with the rest of .sloptrap.yaml. With network: true, exec_command with network: true runs without further prompts in fresh rootless host-network containers with no mounted host credentials or Podman socket. When the configuration leaves network disabled, that option presents one interactive approval that applies to the rest of the current session. Independent calls run concurrently through the shared 32-operation scheduler; command sessions default to 15 minutes and callers can request a shorter timeout. These containers share the host network namespace and can reach its public, LAN, VPN, and localhost services. They use the normal temporary sandbox tool environment, with /tmp/sloptrap-tools/bin on PATH; an optional workspace-relative workdir selects a nested project directory. The long-lived tool container remains offline. gui: true is rejected in this release.
The agent can consult the built-in sloptrap_help tool for the authoritative configuration schema and network/restart workflow without gaining read access to trusted /opt/sloptrap control-plane files.
When the OpenAI provider and trusted network: true configuration are both selected, sloptrap enables the Responses API's native web_search tool. This is server-side OpenAI egress rather than a model container capability, so it is not enabled through a one-off command-session approval. Image generation remains disabled. For other providers or offline projects, current information must be retrieved through an explicitly approved exec_command invocation with network: true.
render_exec is deliberately unavailable in the first release. Directly exposing a host compositor socket and GPU render node to model-selected native code is not treated as a safe sandbox boundary; a future implementation must interpose a nested compositor and avoid the host GPU driver surface.
exec_command launches a supervised session-scoped, offline hardened container and waits briefly for completion before returning a session ID. A later completion never creates an unsolicited model turn; the agent polls it through write_stdin with empty input. Up to 32 foreground/session/bridge operations can be active; excess operations wait in a bounded FIFO queue. No more than 16 command-session containers may run. Configured CPU, memory, and PID values are ceilings for each container and do not reserve resources for containers that do not exist. Each session retains bounded file size (8 GiB), descriptors, and core dumps. The model can inspect logs, send input to TTY sessions, stop, or kill them; all remaining sessions are stopped on shutdown.
Foreground shell and bridge commands show an elapsed timer while running. All commands stream output into the tool display at most ten times per second. The display keeps the last ten lines until expanded, while the final bounded output remains in the model's tool result. A program that buffers because its stdout is a pipe must still use its own line-buffered or unbuffered mode (for example, stdbuf -oL -eL). Tool failures are returned to the model as real errors rather than successful-looking output.
The base image includes common Unix utilities including sed; sloptrap also exposes a structured sed tool for deterministic transformations. Alt+Enter uses Pi's native follow-up queue, which displays pending messages and restores them to the editor with Alt+Up for editing. Unsubmitted non-empty drafts cleared with Escape or Ctrl-C are preserved in local prompt history and session state, so they can be recalled with the usual history navigation after restarting or returning to the session.
Distribution status
sloptrap is distributed from its Git repository under the ISC-style OpenBSD license in LICENSE. The npm package is private and exists only for container builds and verification. Complete the provenance and verification checklist in RELEASING.md before publishing a source release.
Containerized tests
The justfile is the human-facing interface. Run just for help and the full recipe list. No host Node installation is used by the verification recipes:
just check
just integration
# or everything:
just ci
For repository maintenance, just clean removes only reproducible artifacts (node_modules, build output, coverage, temporary TypeScript output, and npm package tarballs). It preserves sources, lockfiles, and .sloptrap.yaml. Before tagging a source release, run just release-check to inspect the exact internal npm package contents used by the container build.
The test image pins Node 24.21.0 by digest and npm 12.0.2. The first script performs a clean install from the packaged immutable lock, offline read-only typecheck/unit run, and production audit. The second builds the actual sandbox and tests its capability, network, filesystem, auth-path, and masking boundaries. just release-check also fails unless the package contains the launcher, control/sandbox definitions, bridge code, and immutable dependency lock.
Automated tests do not exercise ChatGPT OAuth or make a paid model request; login and model selection remain explicit interactive checks.