added Perl port, updated readme
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
## About
|
||||
|
||||
Bantam is a minimalist, dependency-free AI agent specification with two reference implementations: **Python** (`bantam.py`, ~300 SLOC) and **Go** (`main.go` + `term_*.go`, module `code.luxferre.top/luxferre/bantam`). It provides an agentic loop capable of autonomous tool execution, shell interaction, real-time response streaming, Fibonacci backoff network resilience, and subagent delegation using any OpenAI-compatible completions API.
|
||||
Bantam is a minimalist, dependency-free AI agent specification with reference implementations in **Python** (`bantam.py`, ~300 SLOC), **Go** (`main.go` + `term_*.go`, module `code.luxferre.top/luxferre/bantam`), and **Perl 5** (`bantam.pl`, ~300 SLOC). It provides an agentic loop capable of autonomous tool execution, shell interaction, real-time response streaming, Fibonacci backoff network resilience, and subagent delegation using any OpenAI-compatible completions API.
|
||||
|
||||
The entire philosophy of Bantam is built upon two principles:
|
||||
|
||||
@@ -14,7 +14,7 @@ Because of the second principle, Bantam itself was named after Victorinox Bantam
|
||||
## Usage
|
||||
|
||||
### Prerequisites
|
||||
- **Go 1.21+** (Go implementation, no external dependencies) or **Python 3.7+** (reference implementation)
|
||||
- **Python 3.7+**, **Go 1.21+**, or **Perl 5.14+** (standard library / core modules only)
|
||||
- An OpenAI-compatible API endpoint (or OpenAI API key)
|
||||
|
||||
### Installation (Go)
|
||||
@@ -31,16 +31,16 @@ go build ./... # produces ./bantam
|
||||
go run . prompt.txt
|
||||
```
|
||||
|
||||
The Go port is a single `main.go` plus two small platform files (`term_linux.go`, `term_darwin.go`, `term_windows.go`, `term_other.go`) for the built-in raw-terminal line editor — zero external dependencies, same as the Python version.
|
||||
The Go port is a single `main.go` plus four platform files (`term_linux.go`, `term_darwin.go`, `term_windows.go`, `term_other.go`) for the built-in raw-terminal line editor — zero external dependencies, same as the Python and Perl versions.
|
||||
|
||||
### Running Bantam
|
||||
|
||||
Both implementations read the same `model.cfg` and `system.txt` from the current working directory.
|
||||
All implementations read the same `model.cfg` and `system.txt` from the current working directory.
|
||||
|
||||
1. Configure `model.cfg` with your API settings:
|
||||
```ini
|
||||
endpoint=https://api.openai.com/v1
|
||||
model=gpt-4o
|
||||
endpoint=https://opencode.ai/zen/v1
|
||||
model=deepseek-v4-flash-free
|
||||
temperature=0.7
|
||||
api_key=your_api_key_here
|
||||
stream=true
|
||||
@@ -50,9 +50,10 @@ Both implementations read the same `model.cfg` and `system.txt` from the current
|
||||
```bash
|
||||
bantam # Go (or: go run .)
|
||||
python3 bantam.py # Python
|
||||
perl bantam.pl # Perl 5
|
||||
```
|
||||
|
||||
In interactive mode, prompts can span multiple lines: press **Ctrl+J** to insert a real line break (the cursor moves to the next line), then **Enter** to submit the whole multi-line prompt. The Go port ships its own raw-mode line editor (arrow keys move the cursor, Up/Down browse history, Backspace edits, Ctrl+C/Ctrl+D exit), so this works everywhere without dependencies; the Python port uses `readline` when available and falls back to single-line prompts otherwise.
|
||||
In interactive mode, prompts can span multiple lines: press **Ctrl+J** to insert a real line break (the cursor moves to the next line), then **Enter** to submit the whole multi-line prompt. The Go port ships its own raw-mode line editor (arrow keys move the cursor, Up/Down browse history, Backspace edits, Ctrl+C/Ctrl+D exit), so this works everywhere without dependencies; the Python and Perl ports use `readline` when available and fall back to single-line prompts otherwise.
|
||||
|
||||
Sessions are saved under `~/.bantam/sessions/` and can be managed with these commands:
|
||||
- `/save` — save the entire conversation to a new session file (auto-id like `20260808-190038`) and generate its summary
|
||||
@@ -69,6 +70,7 @@ Both implementations read the same `model.cfg` and `system.txt` from the current
|
||||
```bash
|
||||
python3 bantam.py prompt.txt # Python
|
||||
bantam prompt.txt # Go
|
||||
perl bantam.pl prompt.txt # Perl 5
|
||||
```
|
||||
|
||||
## Rules of Bantam (The Algorithm)
|
||||
@@ -81,13 +83,11 @@ Using these rules, everyone can build their own copy of Bantam from scratch in l
|
||||
2. **Input Processing**: Take user prompt (via command-line file parameter or interactive stdin), append `{"role": "user", "content": prompt}`, and invoke `AL(cfg, messages)`.
|
||||
3. **Agentic Loop (`AL`)**:
|
||||
- Send `messages` and tool definitions to the OpenAI-compatible `/chat/completions` API endpoint with custom `User-Agent` headers.
|
||||
- On network or HTTP failure, retry using Fibonacci backoff delays (`1s, 1s, 2s, 3s, 5s`).
|
||||
- On network or HTTP failure, retry using Fibonacci backoff delays (`1s, 1s, 2s, 3s, 5s, 8s, 13s, 21s, 34s`).
|
||||
- If `stream=true`, parse SSE data chunks (`data: {...}`) in real-time to stream reasoning content (`reasoning_content`) and response text directly to stdout, bracketing the reasoning block with `--- reasoning start ---` / `--- reasoning end ---` markers.
|
||||
- Reconstruct the assistant message. If `tool_calls` exist, trace the call (`[tool call: name(args)]`), validate JSON arguments, execute the requested tool (`shell_exec` or `run_subagent`), trace the result (`[tool result: name]`), append the tool response `{"role": "tool", "tool_call_id": id, "content": result}`, and repeat the loop.
|
||||
- If no tool calls remain or `max_al_iterations` is reached, return the updated messages list.
|
||||
|
||||
---
|
||||
|
||||
### Main program
|
||||
|
||||
1. Read system prompt from `system.txt` (default if missing).
|
||||
@@ -101,7 +101,7 @@ Using these rules, everyone can build their own copy of Bantam from scratch in l
|
||||
|
||||
1. Call OpenAI-compatible Completions API (`POST {endpoint}/chat/completions`) using parameters from `cfg` (`model`, `temperature`, optional `api_key` bearer header).
|
||||
- Set custom `User-Agent` header (`Mozilla/5.0 (compatible; Bantam/1.0)`) to avoid gateway 403 blocks.
|
||||
- Retry network/HTTP errors with Fibonacci backoff delays (`1s, 1s, 2s, 3s, 5s`).
|
||||
- Retry network/HTTP errors with Fibonacci backoff delays (`1s, 1s, 2s, 3s, 5s, 8s, 13s, 21s, 34s`).
|
||||
- If `stream=true`, parse SSE stream (`data: {...}`) for real-time reasoning and text output, bracketing reasoning with `--- reasoning start ---` / `--- reasoning end ---` markers.
|
||||
2. Append the assistant's response message object to `messages`. If non-streaming and response has reasoning tokens (`reasoning_content` or `reasoning`), output them wrapped in `--- reasoning start ---` / `--- reasoning end ---` markers.
|
||||
3. If there are pending `tool_calls` in the assistant response:
|
||||
@@ -115,19 +115,19 @@ Using these rules, everyone can build their own copy of Bantam from scratch in l
|
||||
|
||||
### Model configuration parameters
|
||||
|
||||
(shared by both implementations; `model.cfg` is plain `key=value` with `#` comments)
|
||||
(shared by all implementations; `model.cfg` is plain `key=value` with `#` comments)
|
||||
|
||||
- `endpoint` (base OpenAI-compatible API URL, default `https://api.openai.com/v1`)
|
||||
- `endpoint` (base OpenAI-compatible API URL, default `https://opencode.ai/zen/v1`)
|
||||
- `model` (model name, e.g. `gpt-4o`)
|
||||
- `temperature` (model temperature, default 0.7)
|
||||
- `api_key` (API key / Bearer token, optional; fall back to `OPENAI_API_KEY` env var)
|
||||
- `stream` (stream response tokens in real-time, default `true`)
|
||||
- `color` (ANSI coloring: `auto` (TTY-detected, default), `always`, or `never`; also disabled by `NO_COLOR`/`BANTAM_NO_COLOR` env vars)
|
||||
- `timeout` (HTTP timeout in seconds for LLM API calls, default 300; in the Go port it bounds connection setup and time-to-first-byte, so long streaming responses are not cut off mid-stream, matching the Python port's per-operation socket timeout)
|
||||
- `timeout` (HTTP timeout in seconds for LLM API calls, default 300; in the Go port it bounds connection setup and time-to-first-byte, so long streaming responses are not cut off mid-stream, matching the Python and Perl ports' per-operation socket timeouts)
|
||||
- `shell_timeout` (timeout in seconds for `shell_exec` commands, default 120)
|
||||
- `max_al_iterations` (max tool-call loop iterations per `AL()` invocation, default 1000)
|
||||
|
||||
Both implementations keep the interactive prompt safe against the classic "long line overwrites the prompt" readline bug: the Python port wraps the ANSI escapes in `\001`/`\002` (`RL_PROMPT_START_IGNORE`/`RL_PROMPT_END_IGNORE`) markers, and the Go port's built-in editor tracks the cursor with its own column math (terminal auto-wrap aware) and redraws from the first line of the buffer, so wrapped input stays clean at any terminal width.
|
||||
All implementations keep the interactive prompt safe against the classic "long line overwrites the prompt" readline bug: the Python and Perl ports wrap the ANSI escapes in `\001`/`\002` (`RL_PROMPT_START_IGNORE`/`RL_PROMPT_END_IGNORE`) markers (disabling `Term::ReadLine` ornaments in Perl), and the Go port's built-in editor tracks the cursor with its own column math (terminal auto-wrap aware) and redraws from the first line of the buffer, so wrapped input stays clean at any terminal width.
|
||||
|
||||
When enabled, the interactive console uses a subtle ANSI palette: the pending-request status `...requesting...` is darkened bold, reasoning markers are cyan, reasoning text is dim, `[tool call: ...]` traces are yellow, `[tool result: ...]` headers are green, tool result bodies are dim (red for tool errors/unknown tools), and errors/network retries are red. Tool result payloads fed back to the LLM are never colored.
|
||||
|
||||
@@ -137,7 +137,7 @@ When enabled, the interactive console uses a subtle ANSI palette: the pending-re
|
||||
|
||||
- Parameters: `prompt` (string)
|
||||
- Return value: string
|
||||
- Action: run `AL(cfg, [{"role": "system", "content": system_prompt + "\n\nImportant: this is a child agent"}, {"role": "user", "content": prompt}])` and return the text content of the last `assistant`-role message.
|
||||
- Action: run `AL(cfg, [{"role": "system", "content": system_prompt + "\n\nImportant: this is a child agent"}, {"role": "user", "content": prompt}])` subject to recursion depth limit (`MAX_DEPTH = 5`) and return the text content of the last `assistant`-role message.
|
||||
|
||||
#### `shell_exec` tool
|
||||
|
||||
@@ -149,11 +149,20 @@ When enabled, the interactive console uses a subtle ANSI palette: the pending-re
|
||||
|
||||
- `bantam.py` — Python reference implementation (stdlib only)
|
||||
- `main.go`, `term_linux.go`, `term_darwin.go`, `term_windows.go`, `term_other.go` — Go implementation (stdlib only, module `code.luxferre.top/luxferre/bantam`)
|
||||
- `bantam.pl` — Perl 5 implementation (core modules only)
|
||||
- `model.cfg`, `system.txt` — shared configuration and system prompt
|
||||
- `README.md` — this document
|
||||
|
||||
## FAQ
|
||||
|
||||
### Does Bantam support `AGENTS.md` etc?
|
||||
|
||||
The default system prompt instructs the agent to respect `AGENTS.md`/`GEMINI.md`/`CLAUDE.md` files.
|
||||
|
||||
### Is there any common config place for Bantam?
|
||||
|
||||
No, loading `model.cfg` and `system.txt` is deliberately only supported from the current working directory. This allows natural separation of configs and system prompts per project. In case there's no `system.txt` inside the project, the concise and sensible default system prompt will be loaded. In case there's no `model.cfg` inside the project, Bantam will use the free Big Pickle model from OpenCode Zen with the temperature 0.7. Big Pickle has been chosen as the default because it has no set expiration date, unlike other OpenCode's keyless tiers.
|
||||
|
||||
### Why no MCP support?
|
||||
|
||||
If you need MCP server tools, there's nothing a simple shell wrapper cannot solve in this case.
|
||||
|
||||
Reference in New Issue
Block a user