Skip to content
archived Visibility internal Owner erik@uvilo.com Approver _ Created _ Updated _

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:

MechanismDescription
Binary guardDetect binary output (null bytes, bad UTF-8, high control-char ratio); return error with correct command suggestion
Overflow modeTruncate at 200 lines / 50KB; write full output to /tmp/uvilo-shell/cmd-{n}.txt; append navigation hint using grep/tail
Stderr attachmentAlways 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.

allowed:
  commands:
    - python3
    - python
  subcommands:
    - pip install
    - pip show
    - pip list
  directories:       # governs WRITE access; reads within /workspace and /tmp are always free
    - /workspace
    - /tmp
  # urls: deferred to v2

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 Session grant in a conversation
  • Cleared by the /end-session command
  • 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):

⚠️ PERMISSION REQUIRED

Command : pip install pymupdf
Reason  : 'pip' is not in the allowed commands list

Respond with one of:
  No           — do not run
  Once         — run this one time only
  Session      — allow for the rest of this session
  Always       — add to shell-permissions.yaml permanently

Re-call with grant:

run(command="pip install pymupdf", grant="always")

Grant behavior:

GrantConfig updatedExecutes
onceNothingYes
session/tmp/uvilo-shell-session.yamlYes
alwaysshell-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

run(
  command: str,    # Full command string, including pipes and chains
  grant?:  str     # Optional: "once" | "session" | "always"
) -> str

Single tool. Working directory fixed to /workspace for security.

Directory permissions

  • Reads within /workspace and /tmp are always free (no permission check)
  • Writes outside the directories allowed 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”):

  1. Clear session grants: delete /tmp/uvilo-shell-session.yaml if it exists
  2. Commit shell permissions: stage any uncommitted changes to Forge/Configs/shell-permissions.yaml and include in the end-of-session commit

instructions.md additions (to be done in implementation session)

  1. TOOL ENVIRONMENT: Add uvilo-shell server section (1 tool, run(command, grant?), permissions behavior, always grant commit behavior)
  2. Starting a project session, step 3: After WIP check — check git_status for uncommitted changes to shell-permissions.yaml and commit if present
  3. Ending a project session: Add steps 3 and 4 (clear session file, stage shell-permissions.yaml)
  4. /end-session command: Update one-liner to mention session grants and shell permissions

Files

FilePurpose
Forge/Configs/mcp-servers/uvilo-shell.pyThe MCP server
Forge/Configs/shell-permissions.yamlPermanent permissions config (initial list)
/tmp/uvilo-shell-session.yamlEphemeral session grants (auto-cleared by /end-session)
Forge/Configs/LibreChat_Service/librechat.yamlUpdated to register the new MCP server

Deferred to v2

  • URL-level permission granularity (e.g. allow curl only 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