Skip to main content

Pila logo

Pila

A minimal, auditable terminal coding agent harness in Python.
The same agent loop backs the interactive TUI, headless CLI modes and an embeddable SDK.

Package Responsibility
pila.ai Providers, streaming, model catalogs, abort signals
pila.agent Agent loop, tools, permissions, budgets
pila.coding Coding tools, sessions, checkpoints, MCP, prompts, CLI
pila.tui Interactive terminal UI
pila.sdk Agent and @tool embedding API

Features

  • Streaming agent loop — text, thinking, tool calls, tool results, usage and errors are emitted as events.
  • Providers — Anthropic, OpenAI-compatible (OpenAI, xAI, Groq, DeepSeek, Mistral, OpenRouter, Ollama, …), Google Gemini, GitHub Copilot, and an offline faux provider for tests and demos.
  • Tools — read, bash/powershell, edit, write, grep, find, ls and websearch. Searches use the in-process ripgrep-rs engine and respect .gitignore.
  • Permissions — plan, ask, accept-edits (default) and bypass, refined by glob rules in permissions.toml. Every decision is recorded in the session and explained to the model when a call is blocked.
  • Budgets — opt-in caps on total tokens or cost: --budget cost=2.50 or --budget tokens=250000.
  • Sessions — JSONL session tree with resume, fork, clone, export, labels and automatic compaction.
  • Checkpoints — write/edit snapshot the files they change; /undo restores them.
  • MCP — stdio and streamable-HTTP servers from settings.json, plus the Context7 documentation server (on by default).
  • Context and skills — AGENTS.md, SYSTEM.md, project and built-in SKILL.md skills, prompt templates, and extensions.
  • Code review skill — a built-in SKILL.md that runs ruff, sqlfluff, hadolint, tofu and tflint only for the file types in scope, then writes report.md.
  • Four run modes — interactive TUI, print, JSON and RPC over stdio.
  • SDK — typed @tool functions and a sync/async Agent with events, approvals, MCP and budgets.
  • Cross-platform — Windows, macOS and Linux. File edits preserve line endings; the shell resolves to bash/sh when available, otherwise PowerShell.

Install

Requires Python 3.12 or newer.

pip install 'pila-ai[cli]'
pila login          # API key, OAuth, or a custom provider
pila

The base package is the embedding SDK: pip install pila-ai has two direct dependencies — httpx2 (HTTP, keyless web search and page reading) and ripgrep-rs (the engine behind grep/find/ls). click, rich and prompt_toolkit live in the cli extra; a missing extra reports pip install 'pila-ai[cli]' instead of failing with an import error.

From a checkout:

uv sync             # installs the CLI and development dependencies
uv run pila

Quick start

pila "explain @src/pila/cli.py"     # TUI with an initial prompt
pila -p "list the TODO comments"    # print the final response
pila --json "summarize main.py" > events.jsonl
pila -c                             # continue the latest session
pila -r <id-prefix>                 # resume a specific session
pila models [provider]              # provider catalogs
pila sessions                       # sessions for this directory
pila --help                         # every flag and subcommand

TUI essentials:

  • / opens the command menu; @ completes files. Tab accepts a completion; Enter fills it so you can keep typing, then submits on the next Enter.
  • /model, /thinking, /permissions, /resume, /fork, /compact and /undo cover the common workflows.
  • !command runs a shell command and adds its output to the conversation; !!command runs it without adding the output to the model context.
  • Esc or Ctrl+C interrupts a turn. Text submitted during a turn is queued as steering; /commands and !shell lines return to the editor.
  • /thinking changes are stored in the session and in settings.json; they are restored on resume.

Permissions

Every tool declares a kind — read, web, edit, shell or mcp — and the mode decides what runs without asking:

Mode Reads & web File changes Shell & MCP
plan allow deny deny
ask allow ask ask
accept-edits (default) allow allow ask
bypass allow allow allow

In the TUI an ask opens an approval panel (y allow once, a allow for the session, n deny). Headless modes cannot prompt, so ask becomes a denial with an actionable message.

Rules refine the mode. They live in two files, merged global first and project last; inside a file the last matching rule wins:

# ~/.pila/agent/permissions.toml or <project>/.pila/permissions.toml
mode = "accept-edits"

[[rules]]
tool = "bash"
match = "rm -rf *"
action = "deny"

[[rules]]
tool = "bash"
match = "git *"
action = "allow"

allow skips the prompt, deny blocks the call, and plan remains read-only: rules may tighten it but not widen it. The effective mode comes from --permission-mode, then permissionMode in settings.json, then mode in a permissions file, then accept-edits. /permissions shows the mode, rules and session grants; /permissions mode ask switches mid-session.

The workspace guard confines read, write, edit, grep, find and ls to the directory Pila was started in: absolute paths, .. segments and escaping symlinks are rejected, and @ completion only lists workspace files. Shell commands are not OS-sandboxed — treat the guard as protection against mistakes and prompt injection, not as a security boundary for untrusted code.

Budgets

Budgets are opt-in and cap only total tokens or cost:

pila -p --budget cost=2.50 "fix the build"
pila -p --budget tokens=200000 "fix the build"
pila -p --budget cost=2.50,tokens=200000 "fix the build"

Tokens count input + output + cache reads. Without --budget nothing is enforced. When a limit is reached the turn ends normally, a limit_reached event is emitted, the transcript records it, and print mode exits 1. /budget shows the counters for the limits you set.

MCP servers

Pila speaks the MCP 2025-06-18 protocol directly, over stdio and streamable HTTP, with no extra dependency. Context7 is the shipped built-in and is enabled by default. It is an ordinary mcpServers entry in settings.json and provides up-to-date, version-specific library documentation without a key. Set "enabled": false in the global or project settings to turn it off persistently, or use --no-mcp for a single run.

pila mcp list                   # built-ins, runtimes, configured servers
pila mcp add context7 --project # pin it on for this project
pila mcp remove context7        # remove a project/global override
Built-in Runtime Adds
context7 http Version-specific library and framework documentation

Custom servers use the same JSON shape:

pila mcp add github --command uvx --arg mcp-server-github --env GITHUB_TOKEN=…
pila mcp add wiki --url https://mcp.deepwiki.com/mcp --header "Authorization=Bearer …"
{
  "mcpServers": {
    "local":  { "command": "uvx", "args": ["some-mcp-server"], "env": { "TOKEN": "…" } },
    "remote": { "url": "https://example.com/mcp", "headers": { "Authorization": "Bearer …" } }
  }
}

Settings are deep-merged in three layers: shipped defaults, the global ~/.pila/agent/settings.json (or $PILA_CONFIG_DIR/settings.json), then the project's .pila/settings.json. ${workspaceFolder} (or {cwd}) in an argument expands to the session directory. Servers connect in parallel with an 8-second budget; failures are warnings and do not stop the run. Tools appear as mcp__<server>__<tool> and go through the normal permission policy. /mcp shows live state; --no-mcp disables the integration for a run.

Checkpoints and undo

Before write or edit changes a file, Pila copies the previous version into ~/.pila/agent/checkpoints/<session>/ (5 MB per file; larger files are recorded as skipped). /undo [count] restores the last count write/edit groups newest-first, deletes files that did not exist before, and adds a note to the conversation. Disable checkpoints with "checkpoints": false in settings.json.

Providers

Provider Credentials
Anthropic ANTHROPIC_API_KEY
OpenAI OPENAI_API_KEY
Google GEMINI_API_KEY
xAI, Groq, DeepSeek, Mistral, OpenRouter XAI_API_KEY, GROQ_API_KEY, DEEPSEEK_API_KEY, MISTRAL_API_KEY, OPENROUTER_API_KEY
Ollama none (local)
GitHub Copilot pila login github-copilot, or GITHUB_COPILOT_TOKEN

Every provider also accepts settings.apiKeys. Sign-in stores credentials in ~/.pila/agent/auth.json

pila login                   # pick OAuth, API key, or custom provider
pila login anthropic         # prompt for the API key
pila login github-copilot    # device flow (asks for the GitHub host)
pila logout [provider]

Pila ships no hard-coded model names. Catalogs are fetched from each provider's models API, cached in ~/.pila/agent/models.json, refreshed after 24 hours, and fall back to the cache when the provider is unreachable. Model selection at startup is: --model, the model stored in a resumed session, model in settings.json, the newest sign-in in auth.json, then a provider environment variable.

Custom OpenAI-compatible providers can be registered through pila login or manually:

{
  "providers": {
    "my-llm": {
      "api": "openai-completions",
      "baseUrl": "https://my-llm.example.com/v1",
      "models": [
        {"id": "gpt-oss-120b", "name": "gpt-oss-120b", "contextWindow": 200000, "maxTokens": 16384}
      ]
    }
  }
}

GitHub Copilot. The device flow works with github.com, GitHub Enterprise Cloud (*.ghe.com) and GitHub Enterprise Server. The catalog is fetched from your account at login. Optional settings: providers.github-copilot.enterpriseUrl and providers.github-copilot.clientId. Environment overrides: GITHUB_COPILOT_TOKEN, GITHUB_COPILOT_ENTERPRISE_URL, GITHUB_COPILOT_CLIENT_ID, GITHUB_COPILOT_API_BASE.

Web search. websearch needs no API key. It tries optional Firecrawl (FIRECRAWL_API_KEY) and Brave (BRAVE_SEARCH_API_KEY) first, then DuckDuckGo, Bing RSS and Wikipedia. A url argument reads one page. Proxy environment variables and the OS certificate store apply through httpx2.

Python SDK

pila.sdk embeds the same agent loop without the CLI or TUI:

from pila.sdk import Agent, tool

@tool
def search_orders(customer: str, limit: int = 10) -> list[dict]:
    """Find recent orders for a customer.

    Args:
        customer: Customer name or id.
        limit: Maximum number of orders to return.
    """
    return db.search(customer, limit)

agent = Agent(model="anthropic/claude-sonnet-4-5", tools=[search_orders])
result = agent.run("What did Acme order last week?")
print(result.text)
  • @tool derives the JSON schema from type hints (str, int, float, bool, list[…], dict[…], Literal[…], Optional[…]) and reads descriptions from the Google-style Args: section. Async functions are supported.
  • Agent(...) accepts system, append_system, context, extra_guidelines, thinking, temperature, max_tokens, cwd, tools, coding_tools, budget, permission_mode, permission_rules, approval, checkpoints, mcp/mcp_servers, load_project, session and on_event.
  • mcp_servers=[McpServerConfig(...)] connects at construction and exposes mcp__<server>__<tool> tools under the normal permission policy. agent.mcp_warnings reports servers that failed to start.
  • SDK defaults: permission_mode="bypass", no checkpoints, no MCP, no project discovery (load_project=True opts in) and no session file (session="agent.jsonl" persists).
  • run() returns RunResult with text, messages, usage, cost, steps, tool_calls, session_id, stop_reason and limit_reason; ok is False on an error, abort or budget cap.
  • stream() yields the same events as the CLI; arun()/astream() are async equivalents; add_tool() adds tools after construction.

MCP, context and built-in tools combine freely:

from pila.sdk import Agent, McpServerConfig

agent = Agent(
    model="anthropic/claude-sonnet-4-5",
    mcp_servers=[McpServerConfig(name="time", command="uvx", args=["mcp-server-time"])],
    context=["Team conventions live in docs/style.md."],
    coding_tools=["read", "grep", "ls"],
)

An approval callback makes graded modes useful in a service:

from pila.agent.permissions import PermissionRequest

def approve(request: PermissionRequest) -> str:  # "allow", "session" or "deny"
    return "allow" if request.subject.startswith("git ") else "deny"

agent = Agent(
    model="anthropic/claude-sonnet-4-5",
    coding_tools=["read", "bash"],
    permission_mode="ask",
    approval=approve,
)

examples/sdk_agent.py combines a custom tool, a budget, streaming and an approval callback:

uv run python examples/sdk_agent.py --budget cost=0.50 "tour this repository"

Interactive commands

Command Action
/help show commands
/model [pattern] switch model; no argument opens a picker
/thinking [level] set the thinking level
/new start a new session
/resume, /sessions [id] browse and switch sessions
/fork [id] fork before an earlier user message
/clone duplicate the current branch
/export [path] export JSONL branch or static HTML
/tree [id] inspect the session tree
/compact [instructions] compact the conversation
/permissions [mode <mode>] show or switch the approval policy
/budget show token/cost budget and usage
/undo [count] restore files changed by the last write/edit calls
/mcp list configured MCP servers and tools
/name [label] get or set the session label
/session, /stats session statistics
/cwd print the working directory
/login [provider], /logout [provider] manage credentials
/skills, /skill:<name> [args] list or load a skill
/exit, /quit, /q leave
!command, !!command run a shell command with or without model-visible output

Sessions

Sessions are JSONL files under ~/.pila/agent/sessions/ (override with PILA_CONFIG_DIR). Each line is an entry in a session tree: messages, tool results, shell executions, model and thinking changes, labels, and compaction markers. Compaction summarizes older messages and records a firstKeptEntryId, so resume rebuilds the context without resending summarized history.

/clone copies the active branch into a new session file; /fork stops before a chosen user message and returns that message to the editor. /export path.jsonl writes the active branch; any other path writes a self-contained static HTML transcript.

Development

uv sync                  # install/lock dependencies (uv.lock is committed)
uv run pytest            # full suite, fully offline
uv run ruff check .
uv run ruff format --check .
uv run coverage run -m pytest

The suite uses the faux provider (PILA_FAUX_SCRIPT replays a JSON scenario) and mock HTTP transports, so it needs no API keys or network. CI runs lint, format check and tests on Python 3.12, 3.13 and 3.14 across Ubuntu, macOS and Windows (.github/workflows/ci.yml).

Infrastructure is wrapped once: pila/http.py owns the single pooled httpx2 client (keep-alive, per-request timeouts, SSE streams, and a set_client() hook for tests). Providers, catalogs and web search reuse it.

Layout

src/pila/
  ai/            types, models, model API clients, abort, providers
  agent/         agent loop, messages/events, permissions, budgets
  agent/skills/  built-in skills shipped in the package
  coding/        tools, web search, sessions, compaction, checkpoints, MCP, prompts
  sdk/           embeddable Agent, @tool, RunResult
  tui/           interactive app, renderer, completion, selector
  cli.py         Click CLI and headless modes
  http.py        pooled HTTP adapter
examples/        runnable SDK example
tests/           offline pytest suite

License

Apache License 2.0. See the LICENSE and NOTICE files in the distribution.

Metadata

Release history Release notifications | RSS feed

This release

0.1.0 This release

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