Skip to main content

AI-Commander

AI-Commander is a ralph-loop AI agent that provides shell access and task planning capabilities to Large Language Models through function calling. It runs a tight agent loop: it plans, executes shell commands, observes the results, and iterates until the requested task is complete — all while you stay in control through an approval gate.

It supports two presentation modes:

  • Default (TUI) — a Textual-based terminal UI with split panels (prompt, console, agent output, status footer) and an /autoapprove toggle for the approval gate.
  • --nogui — the original direct-CLI behaviour (stdout/stderr, no TUI).

AI-Commander TUI


Table of Contents


Features

  • Ralph-loop agent — repeatedly plans, executes, and observes until the task is done.
  • Shell access — runs arbitrary commands and captures their output.
  • Web search — using the duck-duck-go ddgs Python package (installed via requirements.txt).
  • Approval gate — review each command before it runs, or auto-approve.
  • Command timeouts — prevent runaway commands (configurable).
  • Output limits — command output is capped and truncated with a sentinel so the model never receives unbounded text.
  • Context compression — two pluggable algorithms keep the conversation history within the model's prompt budget.
  • OS-level sandbox — optional Landlock sandbox on Linux to restrict writes to the working directory, /tmp, /dev, and /dev/pts.
  • Two presentation modes — full TUI or plain CLI (--nogui).
  • Prompt history — readline-style history with reverse incremental search (Ctrl+R) in the TUI.
  • Session persistence — the complete conversation history is mirrored to a hand-editable JSON file (.aicsession) after every step and reloaded on every startup, so a crash or Ctrl+C loses nothing already produced.

Requirements

  • Python 3.9+
  • An OpenAI-compatible API endpoint (any provider that exposes the /chat/completions interface).

Installation

From PyPI (or from source)

pip install aic-agent

This installs the aic command plus every runtime dependency, including the Textual TUI and the context-compressor-llm context compressor, so both the TUI and LLM-based compression work out of the box. (py-landlock is installed only on Linux; elsewhere the agent simply runs unsandboxed.)

Upgrade with:

pip install --upgrade aic-agent

From a checkout of this repository

git clone https://github.com/ortegaalfredo/AICommander
cd AICommander
python3 -m venv venv
source venv/bin/activate
pip install .

Or, to install in editable mode while hacking on the code:

pip install -e .

Alternatively, install only the dependencies and run the script directly:

pip install -r requirements.txt
python3 aic.py ...

Note: The py-landlock package is only used on Linux and is optional — the agent runs unsandboxed if it is not installed.


Usage

AI-Commander is a single-file script. Run it from the repository root (or just aic instead of python3 aic.py when installed via pip):

python3 aic.py \
  --api-base https://api.example.com/v1 \
  --model your-model-name \
  --api-key YOUR_API_KEY \
  "your task request"

The TUI starts with no request and waits for you to type one in the prompt panel. In --nogui mode a request is required on the command line.

Command-line options

Option Description
--api-base API base URL (required).
--model Model name (required).
--api-key API key (required).
--auto-approve Auto-approve command execution (for testing).
--no-thinking Hide thinking tokens from output.
--timeout Command timeout in seconds (default: 120).
--max-prompt-len Maximum prompt length in tokens (default: 80000).
--max-output-bytes Maximum output bytes returned from commands (default: 10240).
--max-steps Maximum number of agent loop steps before stopping (default: 500).
--debug Enable debug mode (dump conversation history on truncation).
--nogui Run in direct CLI mode without TUI (original behaviour).
--session Session file mirroring the conversation history (default: ./.aicsession).
--no-session Disable session persistence (nothing is written or loaded).
--disable-sandbox Disable the OS-level Landlock sandbox (Linux only).
--compress-alg Context-compression algorithm (context-compressor-llm or truncate).
--compress-target Fraction of the prompt budget retained as headroom (default: 0.4).
--fast Fast mode: force truncate compression and a smaller system prompt, for slow inference systems.
--reasoning-effort Reasoning effort for the LLM (e.g. low, medium, high).
request The task request (positional, required in --nogui mode).

Example

# Run in the TUI (default)
python3 aic.py --api-base https://api.example.com/v1 \
  --model gpt-4o --api-key sk-... "List all files in this directory"

# Run in plain CLI mode
python3 aic.py --nogui --api-base https://api.example.com/v1 \
  --model gpt-4o --api-key sk-... "Summarize the contents of aic.py"

# Auto-approve all commands (useful for testing / scripting)
python3 aic.py --auto-approve --api-base https://api.example.com/v1 \
  --model gpt-4o --api-key sk-... "Set up a new Python project"

# Use the truncation algorithm and a smaller, faster prompt
python3 aic.py --fast --api-base https://api.example.com/v1 \
  --model gpt-4o --api-key sk-... "List all files"

How it works

  1. The agent receives your task request.
  2. It plans the next step and may emit thinking tokens.
  3. It proposes shell commands, which you approve or auto-approve.
  4. Commands run (optionally sandboxed and time-limited), and their output is fed back to the model.
  5. The loop repeats until the task is considered complete.

All I/O is routed through the EventSink abstraction, so the same agent logic runs unchanged in both the TUI and --nogui modes.

The agent exposes a single tool, execute_bash(command), which runs the command in a pseudo-terminal (PTY). The PTY gives the command a real terminal environment (so interactive tools and tput-style programs work) and lets the agent capture output, enforce a timeout, and kill the whole process group when needed. Output is streamed live to the console panel and the final result is truncated to --max-output-bytes with the literal sentinel output too long: truncated appended when the limit is exceeded.


The TUI

Layout

The default Textual interface is a Norton-Commander-style split screen:

  • Left panel — Agent Output: the model's streamed responses, thinking tokens, and status messages.
  • Right panel — Console Output / Shell: two tabs. Console Output shows the commands the agent runs and their live output; Shell is an embedded interactive shell you can use yourself.
  • Bottom — Prompt input: type a task or a slash command.
  • Status footer: model, current step, live context-window estimate, cumulative token total, tokens/second, session id, auto-approve state, pending-approval indicator, and sandbox status.

The agent runs in a background thread and communicates with the UI through a queue; the UI never blocks on the agent.

Slash commands

Type any of the following in the prompt panel (they are UI controls, so they are not added to prompt history):

Command Aliases Description
/autoapprove /aa Toggle auto-approve on/off for this session. When ON, commands run without asking.
/approve Approve the currently pending command.
/reject Reject the currently pending command.
/suggest <text> Reject the pending command with a steering suggestion that is fed back to the agent (e.g. /suggest use a different approach).
/clear /new Clear the conversation history and start a fresh session (resets tokens, step count, and session id). The session file is emptied too, so nothing is resurrected on the next startup.
/session Show the session file, session id, instance number, and what is currently saved on disk.
/reload Re-read the session file, picking up edits made to its messages array outside AI-Commander (requires a stopped agent).
/stop Stop the running agent.
/quit /exit Stop the agent and exit the application.

Note: When a command is pending approval, you can also approve/reject it with the modal dialog (Y/N keys) or the on-screen buttons. The /approve, /reject, and /suggest commands are an alternative to the dialog.

Keyboard shortcuts

Key Action
Up / Ctrl+P Previous prompt in history (readline-style).
Down / Ctrl+N Next prompt in history.
Ctrl+R Open reverse incremental search over prompt history.
Tab Switch between the Console Output and Shell tabs.
Ctrl+C / Escape Stop the agent and quit.
Y / Enter Approve a pending command (in the approval dialog).
N / Escape Reject a pending command (in the approval dialog).

Prompt history

Prompt history is persisted to ~/.aic_history (up to 1000 entries) and loaded at startup. Up/Down navigation preserves your in-progress line (readline semantics), and Ctrl+R opens a modal reverse search that filters history newest-first; Ctrl+R cycles to the next older match, Enter accepts a match back into the prompt, and Escape/Ctrl+G cancels.


The approval gate

By default every command the agent proposes must be approved before it runs. This keeps you in control of what executes on your system.

  • Approve — the command runs.
  • Reject — the command is skipped and the agent is told it was skipped.
  • Reject with a suggestion — the command is skipped and your suggestion is injected into the conversation as a user message steering the agent (e.g. /suggest use a different approach).

You can toggle auto-approve at any time with /autoapprove (or /aa), or start with --auto-approve. When auto-approve is ON, all commands run without asking — useful for testing and scripting, but be ready to /stop if a command misbehaves.


Context compression

Long agent sessions accumulate conversation history. When the estimated token count of the history exceeds the prompt budget, AI-Commander compresses it so the request fits inside the model's context window. Compression is triggered automatically at the start of each agent step and is a no-op when the history is under budget.

How the budget works

The prompt budget is --max-prompt-len (default 80,000 tokens). For models that accept a max_tokens request parameter (all non-gpt models), the budget is reduced by the output reservation (max_tokens, 8,000) so the prompt leaves room for the completion inside the context window:

prompt_budget = max_prompt_len - max_tokens   (for non-gpt models)
prompt_budget = max_prompt_len                (for gpt models)

Token counts are estimated locally with a conservative heuristic (~3 characters per token plus per-message overhead). Exact numbers come from the API's usage stats once each call finishes and are shown in the status bar.

The truncate algorithm

--compress-alg truncate (or --fast) is the simpler, cheaper algorithm. It always preserves the system prompt (index 0) and the first user instruction (index 1) so the agent never forgets its primary objective. It works in two passes:

  1. Condense oversized tool outputs in place (oldest first), replacing verbose command results with <condensed tool output>.
  2. Drop the oldest messages (from index 2 onward) if condensation alone isn't enough.

Both passes stop once the retained history fits within --compress-target (default 40%) of the budget, leaving headroom so several new turns fit before the next compression. Truncating down to the full budget would make every following prompt exceed the limit again and re-compress on each step, slowly losing more history than necessary.

The context-compressor-llm algorithm

--compress-alg context-compressor-llm (the default) uses the context-compressor-llm package, which implements a Factory.ai-style anchored-summary incremental compressor. When the non-system log exceeds the budget it:

  1. Evicts the oldest prefix of the conversation.
  2. Folds it into a persistent AnchoredSummary via an LLM call on the evicted segment only (so the summarizer call stays short on slow-prefill systems).
  3. Retains the newest suffix — an append-only, KV-cache-friendly layout.

The system prompt is kept byte-identical at the front of the retained context so unchanged prefixes are never re-prefilled (important on systems with slow prompt processing / prefix caching). The first user instruction is also preserved verbatim, and a fallback user message is appended if aggressive compression would leave no user query at all (some endpoints reject that).

If the context-compressor-llm package is not installed, this algorithm falls back to truncate.

Choosing an algorithm

Algorithm Cost Context quality Best for
truncate Cheap (no extra LLM calls) Loses detail from old tool outputs and messages Fast local inference, short tasks, --fast mode
context-compressor-llm One LLM summarizer call per compression Preserves a running summary of evicted history Long tasks, slow-prefill systems, KV-cache-friendly setups

Use --compress-target to control how much headroom is retained (default 0.4 = 40%). A higher value keeps more history but triggers compression sooner; a lower value compresses harder but loses more context.


Session persistence

AI-Commander mirrors the complete conversation history to a JSON file and reloads it on every startup, so nothing already produced is lost to a crash, a kill, a closed terminal, or Ctrl+C.

  • Where.aicsession in the current working directory by default; override with --session <path> or turn persistence off with --no-session. Because the file lives in the working directory, it stays writable inside the Landlock sandbox.
  • When it is written — after the user request, before every LLM call, right after each assistant turn, after every tool result, and again on exit (atexit plus SIGTERM/SIGHUP handlers). Writes are atomic (temp file → fsyncos.replace), so the file is never half-written.
  • What is reloaded — the message history verbatim, plus the session id, step count, and cumulative token usage. --nogui resumes the conversation where the previous run stopped; the TUI restores history at startup and shows the session file in the status bar.
  • A corrupt file is never destroyed — unparseable JSON is moved to .aicsession.corrupt.<timestamp> and the run starts clean.

The status line at startup reports what happened, e.g. [SESSION] Persisting session to /project/.aicsession or [SESSION] Restored 14 message(s) from .aicsession (session 20260828_134851, step 4).

The session file format

Plain, indented, UTF-8 JSON — deliberately simple enough to read and edit:

{
  "aic_session_version": 1,
  "session_id": "20260828_134851",
  "updated_at": "2026-08-28T13:52:58",
  "reason": "tool_result",
  "model": "gpt-4o",
  "api_base": "https://api.example.com/v1",
  "cwd": "/home/me/project",
  "instance_number": 1,
  "step_count": 4,
  "usage": {
    "input_tokens": 5321,
    "output_tokens": 812,
    "cached_tokens": 4096,
    "billable_tokens": 2037
  },
  "messages": [
    { "role": "system", "content": "You are an expert planning..." },
    { "role": "user", "content": "summarize this repo" },
    { "role": "assistant", "content": null, "tool_calls": [
      { "id": "call_1", "type": "function",
        "function": { "name": "execute_bash",
                      "arguments": "{\"command\": \"ls\"}" } } ] },
    { "role": "tool", "tool_call_id": "call_1", "content": "aic.py\nREADME.md" }
  ]
}

messages is the raw OpenAI message list, saved exactly as the agent holds it (same roles, tool_calls, and tool_call_ids). reason records which checkpoint produced the file (user_request, before_llm_call, assistant_message, tool_result, loop_terminated, process_exit, signal_15, interrupted, cleared, …), which makes it easy to tell where a crashed run stopped.

Editing a session by hand

Because the messages array is stored verbatim, a session can be reshaped with any text editor or JSON tool:

# drop the last 6 turns, then resume
python3 - <<'EOF'
import json
d = json.load(open(".aicsession"))
d["messages"] = d["messages"][:-6]
json.dump(d, open(".aicsession", "w"), indent=2)
EOF
python3 aic.py --api-base ... "continue from here"
  • Change, insert, or delete turns; edits are picked up on the next startup, or immediately inside the TUI with /reload.
  • A bare JSON array of messages is accepted in place of the full object, so you can paste a transcript straight into the file.
  • Invalid content is repaired rather than rejected: a missing system prompt is prepended, orphan tool messages are dropped, and a trailing assistant turn with no tool calls is removed (the API rejects prompts ending on an assistant turn). Each repair is reported so nothing changes silently.
  • If the file ended on an assistant turn that did issue tool calls (the process died mid-command), the tool calls are answered with a [SESSION RECOVERED] notice stating the command was not re-executed — the agent is told not to assume it had any effect, rather than replaying it.

Multiple instances in one directory

Each process claims its session file with an flock on a sidecar .aicsession.lock (the data file itself is atomically replaced, so the lock lives beside it). Launching a second AI-Commander in the same directory warns and shifts to a numbered file instead of clobbering the first:

[SESSION] WARNING: another AI-Commander instance is already using .aicsession
          in this directory; this instance uses .aicsession2 instead (instance 2)

Third instance → .aicsession3, and so on. The lock is released by the kernel when a process exits or dies, so a crashed run can never permanently squatter a name. --session <path> participates in the same numbering (<path>2, <path>3, …).

Crash recovery semantics

Failure What the file holds On restart
Ctrl+C / unhandled exception History through the last completed step, re-saved in finally Resumed verbatim
kill / terminal closed Same, via the SIGTERM/SIGHUP handlers Resumed verbatim
kill -9 / power loss / segfault Everything up to the last completed assistant turn or tool result Resumed; an in-flight tool call is closed with a "not re-executed" notice
Hand-broken JSON Previous good file kept, bad copy saved as .corrupt.* Starts clean

At most the single in-flight turn — never earlier history — can be missing, and the only thing never replayed automatically is a shell command that had not finished when the process died.


Sandboxing (Linux)

By default, on Linux, the agent attempts to enable an OS-level Landlock sandbox using py-landlock. When active, writes are restricted to the current working directory, /tmp, /dev, and /dev/pts. The status is shown in the agent output panel (green when active, red when unsandboxed).

What the sandbox restricts

  • Writes are limited to the current working directory (recursively), /tmp, /dev, and /dev/pts.
  • Reads and execution are allowed anywhere.
  • Network access is preserved (so curl, wget, and the OpenAI client still work).
  • /dev and /dev/pts (plus the IOCTL_DEV right, Landlock ABI v5) are required for PTY allocation.

The sandbox is applied before the agent thread starts, so the Landlock domain covers the whole process — including every command the agent runs.

Enabling / disabling

  • The sandbox is attempted automatically on Linux unless --disable-sandbox is given.
  • If py-landlock is not installed, the agent simply runs unsandboxed.
  • The sandbox is best-effort (strict=False): on older kernels or unusual configurations it degrades gracefully rather than failing hard.

When it is not applied

The sandbox is only applied on Linux. On other platforms (macOS, Windows) the agent runs unsandboxed and reports that status at startup.

Tip: The status bar and the startup banner show whether the sandbox is active. If you see [red]Sandbox: off[/red], either you passed --disable-sandbox, py-landlock is missing, or you are not on Linux.


Fast mode

--fast is designed for local inference and low-latency setups. It:

  • Forces the truncate context-compression algorithm (no extra LLM summarizer calls).
  • Uses a smaller, faster system prompt that drops the verbose persistence policy, internet-access recipe, PTY notes, and "think carefully" guidance.

This reduces prompt size and latency at the cost of less-detailed operating instructions for the model.


Debugging

  • --debug dumps the full conversation history to commander-debug.txt at each step (before the LLM call), including per-message roles, tool calls, and estimated token counts. Useful for diagnosing context-compression or prompt-format issues.
  • The status bar shows a live estimate of the current context window and the cumulative session token total (excluding prompt-cached tokens, which are typically not billed).
  • The tests/ directory contains a test suite for truncation and core functionality. See tests/test_truncation.py for usage.

Project structure

├── aic.py            # The entire agent (single-file implementation)
├── requirements.txt  # Python dependencies
├── README.md         # This file
├── tests/            # Test suite (truncation + core helpers)
└── LICENSE           # Apache 2.0

License

This project is licensed under the Apache License 2.0.

Download files

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

Source Distribution

aic_agent-1.0.1.tar.gz (87.8 kB view details)

Uploaded Source

Built Distribution

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

aic_agent-1.0.1-py3-none-any.whl (63.9 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: aic_agent-1.0.1.tar.gz
  • Upload date:
  • Size: 87.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.3

File hashes

Hashes for aic_agent-1.0.1.tar.gz
Algorithm Hash digest
SHA256 7a1fc58d65582026cbfe5d0f7caf1ee84c24758b200dba1696d61769cdf7ca97
MD5 b311ab18474e4701dcd2f01cd00a972f
BLAKE2b-256 5bfa5bb195386f282297d26aeb7218bd2ddfc117472184891422d2cfc20879f9

See more details on using hashes here.

File details

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

File metadata

  • Download URL: aic_agent-1.0.1-py3-none-any.whl
  • Upload date:
  • Size: 63.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.3

File hashes

Hashes for aic_agent-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 c88c819388683844ae0db3bf2b4a2af52243cde05ce79aff1830c8fbd8acf2e0
MD5 2e31b91a9749a25702a6c5691bd4b4d5
BLAKE2b-256 5c4409931045fa20151dd251f0c77e1036cacc900bb00034a1a46c0dccd08d56

See more details on using hashes here.

Release history Release notifications | RSS feed

1.0.2

2 files

This release

1.0.1 This release

2 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