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.

Metadata

Release files for plain-agent 1.0.1

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

Source distribution (sdist)

Source distribution for plain-agent 1.0.1
File Size Uploaded
plain_agent-1.0.1.tar.gz 55.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for plain-agent 1.0.1
File Interpreter ABI Platform
plain_agent-1.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 103.0 kB

Release files / plain_agent-1.0.1.tar.gz

Download URL plain_agent-1.0.1.tar.gz
Size 55.1 kB
Tags Source
SHA-256 checksum
How to use checksums
c5e33f7b9b8dbea9937e7093a3e858d8c3f3df36f3e8c83d72a263bc4e05ec99
BLAKE2b-256 checksum
How to use checksums
dbbb43c8f6b69c1360198cd22077bb264bb523c66f10ee79e6f6e3bd73e741ed
Upload date
Uploaded using Trusted Publishing?
What is 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}

Release files / plain_agent-1.0.1-py3-none-any.whl

Download URL plain_agent-1.0.1-py3-none-any.whl
Size 47.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bdc223f3f475a565dd6ca31ec026d6ba8ec300cf0d2ad19bdb8a9e5b69a993d6
BLAKE2b-256 checksum
How to use checksums
4475772d59d4ee5d246917115a583e6a977b03115ab27deef7ad95409c97b13f
Upload date
Uploaded using Trusted Publishing?
What is 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}

Release history Release notifications | RSS feed

This release

1.0.1 This release

2 release files

0.1.7

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.1

2 release files

0.1.0

2 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