Uvilo OS Plan Shell
Overview
A single run(command, grant?) MCP tool exposing full bash execution with a two-layer
architecture (Unix execution / LLM presentation) and a built-in permissions system.
Based on design decisions from session 2026-03-16.
Architecture
Layer 1 — Unix Execution (lossless)
- Command routing and chain parsing
- Supported operators:
|,&&,||,; - Raw, unmodified output flows through pipes
- No truncation, no metadata injection at this layer
- Pre-execution permission scan before any command in the chain runs
Layer 2 — LLM Presentation (applied after chain completes)
Applied to the final output before returning to the LLM:
| Mechanism | Description |
|---|---|
| Binary guard | Detect binary output (null bytes, bad UTF-8, high control-char ratio); return error with correct command suggestion |
| Overflow mode | Truncate at 200 lines / 50KB; write full output to /tmp/uvilo-shell/cmd-{n}.txt; append navigation hint using grep/tail |
| Stderr attachment | Always attach stderr on failure — never drop it |
| Metadata footer | [exit:N | Xms] appended to every response |
Permissions System
Hardcoded always-allowed (not in config)
Read-only Unix utilities that pipeline composition depends on:
cat, grep, ls, find, head, tail, wc, sort, uniq, echo,
pwd, which, env, true, false, printf, cut, tr, sed, awk
Permanent config
Forge/Configs/shell-permissions.yaml — version-controlled, human-readable.
Session config (ephemeral)
/tmp/uvilo-shell-session.yaml — outside repo, never committed.
Same structure as the permanent config. Merged with permanent config at check time.
Lifecycle:
- Created on first
Sessiongrant in a conversation - Cleared by the
/end-sessioncommand - Cleared on LibreChat restart (lives in
/tmp, which does not survive restarts) - Never pushed to git
Permission check flow
Pre-execution scan: Before any command in the chain runs, ALL commands are checked against the combined config (permanent + session). If any command is blocked, the entire chain halts — no partial execution.
Permission request format (returned to LLM, presented as markdown to user):
Re-call with grant:
Grant behavior:
| Grant | Config updated | Executes |
|---|---|---|
once | Nothing | Yes |
session | /tmp/uvilo-shell-session.yaml | Yes |
always | shell-permissions.yaml written (NOT committed) | Yes |
always grants write to shell-permissions.yaml immediately but are NOT
auto-committed. The /end-session command stages and commits any pending changes.
Session start check: At the start of each session, check git_status for
uncommitted changes to shell-permissions.yaml. These are “always” grants from a
prior session that weren’t committed at end-of-session. Stage and commit them before
proceeding.
Tool Interface
Single tool. Working directory fixed to /workspace for security.
Directory permissions
- Reads within
/workspaceand/tmpare always free (no permission check) - Writes outside the
directoriesallowed list require a grant - URL-level permissions deferred to v2
/end-session additions
The /end-session command gains two additional steps (inserted before “commit and push”):
- Clear session grants: delete
/tmp/uvilo-shell-session.yamlif it exists - Commit shell permissions: stage any uncommitted changes to
Forge/Configs/shell-permissions.yamland include in the end-of-session commit
instructions.md additions (to be done in implementation session)
- TOOL ENVIRONMENT: Add
uvilo-shellserver section (1 tool,run(command, grant?), permissions behavior,alwaysgrant commit behavior) - Starting a project session, step 3: After WIP check — check
git_statusfor uncommitted changes toshell-permissions.yamland commit if present - Ending a project session: Add steps 3 and 4 (clear session file, stage
shell-permissions.yaml) - /end-session command: Update one-liner to mention session grants and shell permissions
Files
| File | Purpose |
|---|---|
Forge/Configs/mcp-servers/uvilo-shell.py | The MCP server |
Forge/Configs/shell-permissions.yaml | Permanent permissions config (initial list) |
/tmp/uvilo-shell-session.yaml | Ephemeral session grants (auto-cleared by /end-session) |
Forge/Configs/LibreChat_Service/librechat.yaml | Updated to register the new MCP server |
Deferred to v2
- URL-level permission granularity (e.g. allow
curlonly to specific domains) - Per-subdirectory write granularity beyond the top-level allowed list
Background reading
Article: “Unix-style commands outperform MCP Servers” (Manus/Pinix author, 2026)
Key insights that drove this design:
- LLMs and Unix both operate on text streams — CLI is already deeply in LLM weights
- Single
run()tool reduces tool-selection overhead vs. a typed function catalog - Two-layer architecture is a logical necessity: Layer 1 must stay lossless for pipes to work correctly; Layer 2 adapts output to LLM cognitive constraints
- Binary guard prevents PNG-thrashing (20 iterations on garbage tokens)
- Overflow mode: truncate + temp file + navigation hints (
grep/tail) — gives the agent a map instead of the whole territory - Stderr always visible on failure — prevents blind retry loops
[exit:N | Xms]footer: exit code + cost signal on every response