Skip to main content

Limbo

A minimal terminal AI coding agent.

Install

Requires Python 3.11 or later.

pip install limbo-code

Configure

Create ~/.limbo/config.toml:

[llm]
api_key = "your-api-key"
model = "deepseek-v4-pro"
base_url = "https://api.deepseek.com/v1"

Limbo speaks to LLMs through a provider/model catalog (src/limbo/llm/catalog.py). Each provider declares its API dialect, endpoint, and credential env var; each model carries its own context window, max output tokens, and thinking/reasoning behavior. A client factory (src/limbo/llm/factory.py) picks the client implementation from the provider's API dialect, so non-OpenAI dialects can be added without touching call sites. Models not in the catalog fall back to generic OpenAI-compatible defaults driven by base_url + model + api_key.

Built-in providers:

Provider API dialect Endpoint Key env var
deepseek openai-completions https://api.deepseek.com/v1 DEEPSEEK_API_KEY
moonshotai (Kimi) openai-completions https://api.moonshot.ai/v1 MOONSHOT_API_KEY
kimi-coding (Kimi For Coding) anthropic-messages https://api.kimi.com/coding KIMI_API_KEY
glm (GLM Coding Plan) openai-completions https://open.bigmodel.cn/api/coding/paas/v4 ZHIPUAI_API_KEY
codex (OpenAI Codex) openai-responses https://api.openai.com/v1 (override with your relay) CODEX_API_KEY

Built-in Codex models include gpt-5.5, gpt-5.6-sol/terra/luna, gpt-5.4, gpt-5.4-mini, and gpt-5.3-codex-spark. gpt-5.5 is verified by live test against the target relay (its 1M context is relay-reported); the other entries mirror pi's built-in catalog and are unverified — availability depends on the relay in use, and unknown or unsupported models fall back to the generic OpenAI-compatible defaults. Codex speaks the OpenAI Responses API (POST {base_url}/responses), served by a dedicated client (src/limbo/llm/responses_client.py, plain httpx SSE): system messages become instructions, tool definitions are flattened, and reasoning models stream reasoning summaries and replay encrypted reasoning items across turns. Limbo targets API-key relays, not the ChatGPT subscription backend (OAuth) — point the provider at your relay and make sure its model IDs match the catalog:

[llm]
model = "gpt-5.5"

[providers.codex]
base_url = "https://my-relay.example.com/v1"
api_key_env = "CODEX_API_KEY"

Built-in GLM Coding Plan models: glm-4.7, glm-5.1, glm-5.2 (1M context), glm-5-turbo, glm-5v-turbo (vision), and glm-4.5-air. The Coding Plan is a subscription with its own endpoint and keys — a coding-plan key only works on the /api/coding/paas/v4 endpoints (not the pay-per-token /api/paas/v4 ones) and vice versa. For the international endpoint set [providers.glm] base_url = "https://api.z.ai/api/coding/paas/v4" (see below).

Built-in Kimi models include kimi-k3 (1M context, pay-per-token), and for Kimi For Coding subscriptions: k3 (1M context), kimi-for-coding, and kimi-for-coding-highspeed (256K context). The two use different endpoints and keys — a sk-kimi-* Kimi For Coding key only works with the kimi-coding models (k3, ...) and vice versa.

Switching to a catalog model only requires changing model — the provider's endpoint and key env var are picked up automatically:

[llm]
model = "k3"             # Kimi For Coding; base_url/api_key resolve from the catalog

The anthropic-messages dialect is served by a dedicated client (src/limbo/llm/anthropic_client.py, plain httpx SSE) selected by the client factory. It converts OpenAI-style tool definitions to Anthropic's shape, merges consecutive tool results into a single user turn, and replays assistant thinking blocks with their signature.

For the mainland-China Moonshot endpoint, set base_url = "https://api.moonshot.cn/v1" explicitly (a configured base_url always wins over the catalog — unless a [providers.<id>] override says otherwise, see below).

Per-provider overrides

An optional [providers.<id>] section overrides a single catalog provider without touching the global [llm] settings — e.g. pointing a provider at a relay, renaming its credential env var, or adding extra headers:

[providers.glm]
base_url = "https://api.z.ai/api/coding/paas/v4"  # international endpoint

[providers.codex]
base_url = "https://my-relay.example.com/v1"
api_key_env = "CODEX_API_KEY"     # rename the env var read for the key
headers = { x-relay = "on" }       # extra headers on every request

All fields are optional; unset fields fall back to the catalog. Resolution order for base_url (first hit wins):

  1. [providers.<id>] base_url
  2. [llm] base_url (when changed from the DeepSeek default)
  3. the catalog provider's built-in endpoint

and for the API key: [providers.<id>] api_key[llm] api_key → the environment variable ([providers.<id>] api_key_env rename, else the catalog's default env var).

Note: rule 1 is the single exception to the long-standing "an explicit [llm] base_url always wins" behavior — a per-provider override is more specific than the global setting. If you configure both, the [providers.<id>] value is used for that provider's models.

Optional LLM settings

[llm]
temperature = 0.2        # 0.0 - 2.0, default 0.2
max_iterations = 50      # safety limit on tool-turn loops, default 50
max_tokens = 8192        # output cap; default = the model's catalog value
thinking_effort = "high" # reasoning control; default = provider behavior

thinking_effort is interpreted per model dialect:

  • k3, kimi-for-coding* (Anthropic adaptive thinking): low | high | maxthinking: {type: adaptive} + output_config.effort. Thinking cannot be disabled; temperature is omitted while thinking is enabled.
  • kimi-k3 (moonshotai, OpenAI-style): low | high | max → sent as reasoning_effort. Thinking cannot be disabled on K3.
  • kimi-k2-thinking, kimi-k2.5+ (DeepSeek-style): any non-off value → thinking: {type: enabled}; "off"thinking: {type: disabled} (except kimi-k2.7-code*, where thinking is always on).
  • glm-* (z.ai-style): like DeepSeek-style but with clear_thinking: false so thinking is preserved across turns; glm-5.2 additionally maps low/high/max to reasoning_effort (low clamps to high).
  • gpt-5.* codex models (Responses API): low | medium | high | xhighreasoning: {effort, summary: auto}; 5.6-generation models (gpt-5.6-*) also accept max. Temperature is omitted for reasoning models.
  • Non-reasoning models: ignored.

Reasoning output streams into the chat as muted thinking blocks and is stored on the assistant message so it can be replayed to APIs that require it (Kimi K3 rejects tool-call replays without reasoning_content).

Optional tool settings:

[tools]
bash_enabled = true

Optional UI settings:

[ui]
kitty_keyboard = "auto"   # auto (default) | enabled | disabled

kitty_keyboard controls Textual's kitty keyboard protocol. The default auto disables it only when Limbo runs inside the herdr multiplexer: herdr mirrors a pane's REPORT_ALL_KEYS request to the host terminal (e.g. Ghostty), and Ghostty then encodes IME commits as the physical commit key (space) while dropping the composed text — typing Chinese turns into spaces (pasting is unaffected). With the protocol disabled, herdr keeps IME-compatible host flags and committed IME text arrives as raw UTF-8. Use enabled to always keep the protocol (e.g. in a direct Ghostty/kitty terminal), or disabled to force raw/legacy input everywhere.

Session storage

Conversations are saved as JSONL files in ~/.limbo/sessions/ so you can review or debug them later. Use the --session-dir argument to redirect them to another location.

Old session files are not automatically cleaned up; remove them manually when you no longer need them.

Run

limbo --workdir /path/to/project
limbo --model glm-4.7        # override the configured model for this run

Switching models at runtime

Use /model in the TUI: without an argument it opens a picker that lists catalog models grouped by provider (context window, reasoning capability, and the current model are annotated; providers without a resolvable API key are dimmed with a hint). /model <name> switches directly — unknown names fall back to generic OpenAI-compatible defaults. The picker re-reads config.toml every time it opens, so edits (e.g. a newly added [providers.<id>]) apply without a restart.

A switch takes effect immediately (no restart): the LLM client is rebuilt, and the new model is written back to config.toml (comments preserved via tomlkit) so the next launch keeps it. If the write fails, the switch still applies for the current session. Switching is refused while a turn is in flight.

Safety

Limbo executes every tool call immediately, without asking for confirmation, and has no safety fences: file tools (read, edit, write, grep, find, ls) are not scoped to the working directory — absolute paths and symlink targets outside it are honored as-is — and there is no sensitive-file blocklist, so .env, SSH keys, and the like are read and searched like any other file.

Bash is likewise unrestricted: it is started in the working directory but can cd .., use absolute paths, read or write anywhere, and no command is filtered out — rm -rf and git reset --hard run exactly as given. Only run Limbo with trusted models and in repositories you can afford to modify or lose.

If you need to work with untrusted projects, disable the bash tool entirely:

[tools]
bash_enabled = false

Development

Set up the environment (dev tools are a PEP 735 dependency group, so plain uv sync installs pytest/ruff/mypy — no --extra flag):

uv sync

Run everything (sync + tests + lint + type check) with one command:

make check

Or run the pieces individually. Always use python -m pytest: uv run pytest silently falls through to a global pytest on PATH when the venv lacks pytest, which can run the suite against the wrong source tree.

uv run python -m pytest tests/ -v   # tests
uv run ruff check src tests         # lint
uv run mypy src                     # type check

Download files

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

Source Distribution

limbo_code-0.1.16.tar.gz (1.0 MB view details)

Uploaded Source

Built Distribution

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

limbo_code-0.1.16-py3-none-any.whl (157.8 kB view details)

Uploaded Python 3

File details

Details for the file limbo_code-0.1.16.tar.gz.

File metadata

  • Download URL: limbo_code-0.1.16.tar.gz
  • Upload date:
  • Size: 1.0 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for limbo_code-0.1.16.tar.gz
Algorithm Hash digest
SHA256 85b8bd6944016281c9aa2757f12e23e3a8583f92d4441ce4fd0c793c5ea52388
MD5 26c8f7dc7b67c0c1291e58dd436a558e
BLAKE2b-256 3be04aa9aec8ff2d6e45789706bad753b08995f01eb06e01501fa7d0070295fd

See more details on using hashes here.

Provenance

The following attestation bundles were made for limbo_code-0.1.16.tar.gz:

Publisher: publish.yml on killpanda/limbo-code

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file limbo_code-0.1.16-py3-none-any.whl.

File metadata

  • Download URL: limbo_code-0.1.16-py3-none-any.whl
  • Upload date:
  • Size: 157.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for limbo_code-0.1.16-py3-none-any.whl
Algorithm Hash digest
SHA256 6e598916a4bcc036e44079ff7c33231863a4ea4fe2d7e9750b6c75369b6a78b2
MD5 ba34ba9e115e35530b5cc33227d706a0
BLAKE2b-256 39f406fefc73fd2c89efa600b25e3b34196d26dd51878294531079af13ea46cd

See more details on using hashes here.

Provenance

The following attestation bundles were made for limbo_code-0.1.16-py3-none-any.whl:

Publisher: publish.yml on killpanda/limbo-code

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

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