Personal extensible coding agent — small core, big seams. Raw ReAct loop today, plugin system / MCP / TUI tomorrow, without rewriting the core.
Built with Python + Rich. Any OpenAI-compatible provider via settings.json.
cp settings.example.json settings.json # set base_url / api_key / model
uv run funcode "list files and read pyproject.toml"
uv run funcode # REPL (/exit, /clear, /tools, /thinking, /verbose, /compact, /context)
uv run funcode -y "..." # auto-approve write/dangerous tools
uv run funcode -v "..." # expanded thinking + full tool output
uv run funcode --no-thinking "..." # hide thinking blocks
uv run funcode --no-stream "..." # one-shot fallback (no SSE)settings.json also loads from ~/.config/funcode/settings.json.
web.tinyfish_api_key (or $TINYFISH_API_KEY) enables web_search — free key at
https://agent.tinyfish.ai/api-keys. web_fetch needs no key.
read, write, edit, bash, glob, grep, web_fetch, web_search
Slash commands + skills are one unified registry (funcode/commands/ —
Command / CommandProvider / CommandRegistry, mirroring core/tools.py).
Plugins register with one add_provider call; loop unchanged.
- Project:
.funcode/commands/*.md,.funcode/skills/*/SKILL.md - Generic:
.agents/commands/*.md,.agents/skills/*/SKILL.md(shared across agents) - Compat:
.claude/...,.opencode/commands/...(read-only interop) - Global:
~/.config/funcode/commands/*.md,~/.config/funcode/skills/*/SKILL.md - File wins over builtin; project wins over global.
/helplists all with source.
---
description: Review code for correctness bugs
---
Review $ARGUMENTS. Recent commits: !`git log --oneline -10`. See @src/main.py.Placeholders: $ARGUMENTS, $1/$2/..., !`shell` (output injected,
read-only git-style cmds allowed freely), @path (file injected, cwd-confined).
Type / for command completion, @ for file search (needs prompt_toolkit,
else plain input). /providers adds a provider + saves the key
(project vs global settings.json, chmod 600, never echoed).
REPL input is a Claude-style menu: / opens the command list with live
fuzzy filtering (↑↓ navigate, Enter runs, Tab completes the name,
Esc dismisses). An exactly-typed /command always beats the highlight
on Enter. Input history persists to ~/.config/funcode/history.
Token-by-token SSE streaming with live Markdown preview. Assistant text renders
as plain Markdown, tools collapse to two lines (⏺ Read pyproject.toml /
⎿ 22 lines), thinking is collapsed by default (⋯ thought for 3s · 5 lines).
/thinking [show|hide|full|collapse]— toggle thinking display (REPL, display only)/verbose— expand thinking + full tool output (REPL)/compact [focus]— summarize history into one checkpoint, keep last turns/context— breakdown (system · tools · messages) plus budget lineagent.stream / show_thinking / verboseinsettings.jsonfor defaults
Token use is estimated per turn (tiktoken when installed, else ~chars/4) and
shown after each reply as ctx ~used/window (%), like OpenCode's
30.0K / 200K (15%). Like Claude Code's status line, a fresh session shows
the window only (128k window) — live numbers appear once the first API call
exists. /context adds the breakdown (system · tools · messages) plus the
auto-compact threshold, mirroring Claude Code's Autocompact-buffer row.
Auto-compact fires at
auto_compact_fraction × min(context_window, compact_cap) — default
0.85 × min(window, 300k), so 1M models compact at ~255k and 128k models at
~109k. Unknown models default to 128k; override with agent.context_window.
Summaries persist as summary checkpoints in the session JSONL, so resume
replays compacted — not full — history.
The loop only knows abstractions — never SDKs:
funcode/core/provider.py—LLMProviderABC (providers/openai_compat.pyis the only file importingopenai, works with any OpenAI-compatiblebase_url)funcode/core/tools.py—ToolABC (+risk: read|write|dangerous),ToolProviderABC,ToolRegistry(aggregates providers, validates args, fires hooks),@tooldecorator (typed function → Tool with inferred JSON schema)funcode/core/events.py—EventBus(pre_tool_use/post_tool_use, blockable — permissions, logging, future plugins are just subscribers)funcode/core/loop.py— minimal ReAct loop + grounding rulesfuncode/context/agents_md.py—AGENTS.md/CLAUDE.mdinjected as prompt layerfuncode/ui/rich_cli.py— Rich renderer (live Markdown, collapsed thinking)funcode/ui/summaries.py— per-tool one-line summaries (pure, no Rich)
New tools: subclass Tool (or use @tool) + add to a ToolProvider.
New providers (MCP, plugins): implement ToolProvider / LLMProvider — loop unchanged.
sessions + resume → slash commands + skills ✅ → MCP client → plugins → TUI.