A minimal terminal AI coding agent
Project description
Limbo
A minimal terminal AI coding agent.
Install
Requires Python 3.11 or later.
pip install -e .
Configure
Create ~/.limbo/config.toml:
[llm]
api_key = "your-api-key"
model = "deepseek-chat"
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):
[providers.<id>] base_url[llm] base_url(when changed from the DeepSeek default)- 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_urlalways 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|max→thinking: {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 asreasoning_effort. Thinking cannot be disabled on K3.kimi-k2-thinking,kimi-k2.5+ (DeepSeek-style): any non-offvalue →thinking: {type: enabled};"off"→thinking: {type: disabled}(exceptkimi-k2.7-code*, where thinking is always on).glm-*(z.ai-style): like DeepSeek-style but withclear_thinking: falseso thinking is preserved across turns;glm-5.2additionally mapslow/high/maxtoreasoning_effort(lowclamps tohigh).gpt-5.*codex models (Responses API):low|medium|high|xhigh→reasoning: {effort, summary: auto}; 5.6-generation models (gpt-5.6-*) also acceptmax. 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
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.
File tools (read, edit, write, grep, find, ls) are bounded to the
current working directory and reject paths that escape it, including via
symlinks. The boundary check resolves the path before each operation, so a
symlink swapped between the check and the operation (a time-of-check-to-time-of-use
race) could escape the workdir. This is a known limitation for the MVP.
Bash is an exception: it is started in the working directory but is not
sandboxed. Commands can cd .., use absolute paths, and read or write outside
the workdir. In addition, commands that match dangerous patterns such as rm
or git reset --hard are rejected outright.
The pattern list is configurable but cannot be disabled from the UI. Bash
commands are filtered with a simple heuristic, but that filter can be bypassed
by subshells (bash -c 'rm -rf /'), command substitution ($(rm -rf /)),
variable indirection, options before the command name
(git -C /foo reset --hard), variable assignments before the command name
(VAR=1 rm -rf /), and similar shell constructs. Only run Limbo with
trusted commands 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
Run tests:
pytest tests/ -v
Run linting and type checks:
ruff check src tests
mypy src
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file limbo_code-0.1.9.tar.gz.
File metadata
- Download URL: limbo_code-0.1.9.tar.gz
- Upload date:
- Size: 975.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
23483e7ff2653207fcd007f3eb6d933301c1292ff2ce21d799fea3b91f1e14b9
|
|
| MD5 |
d64b9d6a0cadef1e8be16be2b5bedbea
|
|
| BLAKE2b-256 |
ab7cf78f1e0fe28b21d3619a84eacdbe6a93f3cb053accbe7187b4019e04372d
|
Provenance
The following attestation bundles were made for limbo_code-0.1.9.tar.gz:
Publisher:
publish.yml on killpanda/limbo-code
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
limbo_code-0.1.9.tar.gz -
Subject digest:
23483e7ff2653207fcd007f3eb6d933301c1292ff2ce21d799fea3b91f1e14b9 - Sigstore transparency entry: 2289910211
- Sigstore integration time:
-
Permalink:
killpanda/limbo-code@c574bfd393feafa4c7c5a147bff0d2f225318310 -
Branch / Tag:
refs/tags/v0.1.9 - Owner: https://github.com/killpanda
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c574bfd393feafa4c7c5a147bff0d2f225318310 -
Trigger Event:
push
-
Statement type:
File details
Details for the file limbo_code-0.1.9-py3-none-any.whl.
File metadata
- Download URL: limbo_code-0.1.9-py3-none-any.whl
- Upload date:
- Size: 126.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2af58480e554f325c2dc6238ea70674e0613ddb079312d0a708af0bcb67c838b
|
|
| MD5 |
202da0b45e863ea7ad448801fbd1952d
|
|
| BLAKE2b-256 |
d41c0a90e0e83646015d19cef22ea4348dee37e0bca246976f94bce5911dc2ce
|
Provenance
The following attestation bundles were made for limbo_code-0.1.9-py3-none-any.whl:
Publisher:
publish.yml on killpanda/limbo-code
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
limbo_code-0.1.9-py3-none-any.whl -
Subject digest:
2af58480e554f325c2dc6238ea70674e0613ddb079312d0a708af0bcb67c838b - Sigstore transparency entry: 2289910278
- Sigstore integration time:
-
Permalink:
killpanda/limbo-code@c574bfd393feafa4c7c5a147bff0d2f225318310 -
Branch / Tag:
refs/tags/v0.1.9 - Owner: https://github.com/killpanda
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c574bfd393feafa4c7c5a147bff0d2f225318310 -
Trigger Event:
push
-
Statement type: