Skip to main content

Plain Agent

A terminal coding agent for LLM APIs compatible with OpenAI. It streams responses, keeps conversation context, works with files, runs approved commands in an OS sandbox, and provides approved web search and fetch tools.

How it works

flowchart LR
    User[User] --> UI[Textual terminal UI]
    UI --> Agent[Streaming agent loop]
    Agent --> LLM[Compatible LLM API]
    Agent <--> Tools[Tool registry]
    Tools --> Files[Workspace file tools]
    Tools --> Sandbox[Approved Bubblewrap commands]
    Tools --> Web[Approved Exa web tools]

The agent sends the conversation and available tool definitions to the configured model. It streams text into the terminal, executes requested tools, appends their results to the conversation, and repeats until the model returns a final answer.

Agent loop

flowchart TB
    subgraph Input["1 · User input"]
        Prompt([Submit prompt])
    end

    subgraph Context["2 · Context preparation"]
        direction LR
        History[Append prompt to history]
        Compact{Compaction needed?}
        Summarize[Summarize older exchanges]
        History --> Compact
        Compact -- Yes --> Summarize
    end

    subgraph Response["3 · LLM response"]
        direction LR
        Model[Send history and tool definitions]
        Stream[Stream and assemble response]
        Decision{Tool calls?}
        Model --> Stream --> Decision
    end

    subgraph Execution["4 · Tool execution"]
        direction LR
        Validate[Validate tool calls]
        Approve[Request approval when required]
        Run[Run tools]
        Results[Append tool results]
        Validate --> Approve --> Run --> Results
    end

    subgraph Output["5 · Final output"]
        Answer([Render final answer])
    end

    Prompt --> History
    Compact -- No --> Model
    Summarize --> Model
    Decision -- Yes --> Validate
    Results -- Continue agent loop --> Model
    Decision -- No --> Answer
    Answer -- Continue conversation --> Prompt

For more detail, see Architecture for the component design, request lifecycle, and trust boundaries.

Install

Plain Agent is available on PyPI and can be installed with pipx:

pipx install plain-agent

If pipx is not installed, follow the official installation guide.

For local development, this project uses uv to track the Python environment. If uv is not installed, follow the official installation guide.

uv sync

On Linux, install Bubblewrap to enable the run_command tool:

# Debian / Ubuntu
sudo apt install bubblewrap

# Fedora / RHEL
sudo dnf install bubblewrap

# Arch Linux
sudo pacman -S bubblewrap

Plain Agent verifies that Bubblewrap is usable at startup. If it is missing or the kernel does not permit the required user and network namespaces, command execution is disabled and the file tools remain available. There is no unsandboxed command fallback. To keep the sandbox launcher outside the workspace trust boundary, Plain Agent checks only /usr/bin/bwrap and /bin/bwrap; it does not resolve Bubblewrap through PATH.

Configuration

Create a local .env file or export environment variables in your shell. See .env.example for more examples.

For DeepSeek:

export DEEPSEEK_API_KEY="your-api-key"
export LLM_PROVIDER="deepseek"
export LLM_MODEL="deepseek-v4-flash"

For OpenAI:

export OPENAI_API_KEY="your-api-key"
export LLM_PROVIDER="openai"
export LLM_MODEL="gpt-5.4-mini"

You can still set LLM_BASE_URL when you want to override the provider default, such as pointing at a local OpenAI compatible server like Ollama.

Context compaction runs automatically when the estimated conversation history reaches 200k tokens. Set LLM_COMPACTION_AUTO_MAX_TOKENS to change that threshold, or set it to 0 to disable automatic compaction. You can also run /compact in the terminal to compact manually.

Web access

The web_search and web_fetch tools use Exa and are enabled by default. Every query or URL requires explicit approval before Plain Agent connects to mcp.exa.ai. Search returns links and bounded excerpts; fetch returns bounded Markdown content for one HTTP or HTTPS URL. No Exa API key is required.

To disable both web tools:

export PLAIN_AGENT_ENABLE_NETWORK="false"

This setting controls only the web tools provided with Plain Agent. The run_command sandbox remains offline.

Run

After installing with pipx:

plain-agent

For local development from a repository checkout:

uv run plain-agent

Enter a prompt in the terminal. Use /compact to summarize older conversation history, or enter exit or quit to close the application.

Command sandbox (Linux)

Every run_command request requires user approval and still runs through Bubblewrap after it is approved. The approval prompt shows the requested mode and an unambiguous representation of the exact argument vector using shell quoting. Backslashes and characters outside the printable range are escaped so command arguments cannot rewrite the terminal prompt. Approval is a user decision; Bubblewrap is the independent OS enforcement boundary.

Commands are passed as an argument array and never receive an implicit shell. For example, ["rg", "TODO", "."] runs directly, while shell syntax must be explicit as ["bash", "-lc", "printf '%s\\n' *.py"].

Two modes are available:

  • read-only is the default and mounts the workspace without write access.
  • workspace-write permits persistent workspace changes, except protected paths.

Both modes have networking disabled, including host loopback. The sandbox starts with an empty filesystem view and exposes the workspace, required system runtimes mounted as read only, a minimal set of /etc files, isolated /proc and /dev filesystems, and temporary memory backed /tmp, /run, and HOME directories. The environment is cleared; only a filtered PATH, locale, terminal, and color settings are retained. API keys and arbitrary parent variables are not inherited by commands.

In workspace-write, existing .git and .venv directories are rebound as read only. .agents, .codex, and .sandbox are hidden. .env plus recognized private key and certificate files are masked in both modes, as are existing pathname Unix sockets. File tools running in the Plain Agent process continue to use their existing workspace permission checks and are not run through Bubblewrap.

Additional toolchains can be exposed as read only with a list of absolute, existing paths separated by the operating system path separator:

export PLAIN_AGENT_SANDBOX_ADDITIONAL_READ_ROOTS="/opt/toolchain:/home/me/.local/share/special-runtime"

Each extra root expands the confidentiality boundary: sandboxed commands can read everything below it. Paths are canonicalized and deduplicated before use. Avoid exposing home directories or credential stores.

The command sandbox is deliberately offline. Package downloads, remote Git operations, and calls to local network services fail. Linux is the only supported command sandbox platform in this milestone; macOS and Windows keep run_command disabled. Seccomp syscall filtering is planned as the next Linux hardening step after the filesystem and network policy is stable.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

plain_agent-1.0.1.tar.gz (55.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

plain_agent-1.0.1-py3-none-any.whl (47.9 kB view details)

Uploaded Python 3

File details

Details for the file plain_agent-1.0.1.tar.gz.

File metadata

  • Download URL: plain_agent-1.0.1.tar.gz
  • Upload date:
  • Size: 55.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for plain_agent-1.0.1.tar.gz
Algorithm Hash digest
SHA256 c5e33f7b9b8dbea9937e7093a3e858d8c3f3df36f3e8c83d72a263bc4e05ec99
MD5 71386c823fd739315a0594d5f54f7236
BLAKE2b-256 dbbb43c8f6b69c1360198cd22077bb264bb523c66f10ee79e6f6e3bd73e741ed

See more details on using hashes here.

File details

Details for the file plain_agent-1.0.1-py3-none-any.whl.

File metadata

  • Download URL: plain_agent-1.0.1-py3-none-any.whl
  • Upload date:
  • Size: 47.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for plain_agent-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 bdc223f3f475a565dd6ca31ec026d6ba8ec300cf0d2ad19bdb8a9e5b69a993d6
MD5 87e9785370734b51e0a6929b4a3bd3e3
BLAKE2b-256 4475772d59d4ee5d246917115a583e6a977b03115ab27deef7ad95409c97b13f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page