- /compact: summarizes evicted context messages via LLM call, replaces
them with a single summary system message
- /clear: resets session and display
- ContextBuilder.EvictedMessages(): returns messages outside bounded window
- Session.Compact(): drives the summarization and history replacement
- Stop agentCh pump while waiting for user confirmation (prevents
context cancellation on confirm reply)
- System prompt: hard directive to use file_read/file_write instead
of shell commands for file operations
- Update tool descriptions to match
- file_read and file_write require explicit y/n confirmation before executing
- New agentConfirming UI state with confirm [y/n] status bar indicator
- y/yes approves, n/no denies, anything else denies and falls through to
normal prompt handling
- file_read output includes line numbers for precise file_write targeting
- Fix tool output display: remove per-line squashWhitespace (preserves indentation)
- file_write description hints to preserve formatting
- Pass caller context into Executor.Execute so cancellation reaches
the subprocess immediately
- Use Setpgid + SIGKILL on process group to kill grandchild processes
- Add context to ToolExecutor signature and thread it through dispatch
- Reset agent state to idle after drainAgent
- Rollback incomplete session turn on interrupt
- Split execute_code into execute_code, execute_tool, execute_pipe for
clearer model comprehension, especially on smaller models
- Ollama: accumulate tool calls across all stream chunks (fixes llama3.1
which delivers tool calls only on the done event)
- Ollama: add HTTP 429 / RateLimitError handling
- Ollama: map ToolCallID on outbound messages
- System prompt: inject cwd, current time, available tools
- System prompt: clarify tool usage rules
- Fix spurious spaces in streamed output (remove addStreamingContent heuristic)
- Fix tool output display: preserve newlines, add blank line separation
Each agent config (mcpServers, hooks, prompt) lives in its own file.
'prompt' is injected after the system prompt, allowing per-agent
behaviour without touching the minimal base system prompt.
Startup loads agents/default.json, falling back to the legacy
config.json. The /agent [name] command swaps the active agent at
runtime, rebuilding MCP connections, hooks, tools, and the composed
system prompt, and resetting the session. Agent name is shown in the
status bar.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove use-case-specific instructions (execute_code how-to, planning
workflow, skill discovery) — these dictate behavior inappropriate for a
general-purpose agent and add tokens to every request. Keep only the
terse-output and anti-filler guardrails that directly affect cost and
response quality.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When the model emits a text-only turn containing narration phrases
("let me", "i'll", "i will", etc.) with no tool calls, the loop now
injects an ephemeral "Continue. Act now." user message and runs another
step rather than treating the turn as completion. Capped at 2 nudges
per run to prevent infinite loops. UI shows "[nudge: continuing…]" so
the user can see what happened.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Return RateLimitError from openai backend on HTTP 429, parsing the
Retry-After header (integer seconds or HTTP-date). The agent loop retries
up to 3 times with exponential backoff (5s/10s/20s) when no header is
given, emitting per-second countdown ticks. The status bar renders
"retry {N}s" during the wait.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Re-adds handleCommand (/help, /model, /backend) and wires it into the
enter handler so slash commands are processed without starting an agent.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Five improvements for cost/focus efficiency:
1. Budget now accounts for fixed per-request overhead (system prompt +
tool schemas), which were previously invisible to ContextBuilder and
could silently exceed intended limits by 30-50%.
2. Tail counting now only counts user and plain-assistant (no tool_calls)
messages toward TailMessages quota. Processed tool exchanges between
conversational turns are included but do not crowd out genuine context.
Trailing in-progress exchanges are always preserved and free.
3. BoundedHistory / BoundedHistoryWithNotice deduplicated into a single
buildBounded(injectNotice bool) private helper.
4. O(n²) prepend in the greedy inclusion loop replaced with
append+slices.Reverse.
5. exec.Executor.MaxOutputChars replaces the hardcoded 8000-char const.
Initialised from OLLIE_TOOL_OUTPUT_CHARS env var, defaults to 8000.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Loop struct and New constructor removed; Run is now a package-level
function taking (ctx, Config, State)
- Stop condition de-nested: natural stop (no tool calls → MarkComplete
+ break) and step-limit stop (step >= maxSteps-1 → break) are now
separate, sequential checks instead of a redundant outer/inner pair
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both backends already implement streaming; the non-streaming fallback
was dead code that added complexity.
- Backend interface now requires only ChatStream; the separate
StreamingBackend interface and Chat method are removed
- Both OpenAIBackend and OllamaBackend lose their Chat methods and the
stream=bool parameter on their internal doChat helpers
- Loop.Run is simplified: one streaming path, no streamed flag, no
skippedCalls map, no shouldStop helper, no runStreamStep indirection
- "call" events are now emitted solely in the act phase, not in the
stream phase, so dedup tracking is no longer needed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Some backends (DeepInfra, OpenRouter) occasionally emit null or empty
content for whitespace-only tokens, causing adjacent words to fuse
("normaluse" instead of "normal use"). addStreamingContent inserts a
single space at the chunk boundary whenever neither side carries
whitespace. Safe for LLM streaming because BPE tokens are always
whole — they never split mid-word at the API level. Rewritten to use
utf8.DecodeLastRuneInString / utf8.DecodeRuneInString instead of raw
byte indexing so multi-byte UTF-8 is handled correctly.
Also remaps viewport scroll keys: half-page to alt+d/alt+u (avoids
conflicting with terminal line-editing on ctrl+d/u) and per-line
scroll to down/up arrows plus alt+n/alt+p (Emacs-style, safe in xterm
and plain tty alike).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs that combined to produce unrecoverable 400 errors from backends:
1. loop.go: runStreamStep returned nil on stream-closed-without-done,
causing Run to commit a partial assistant message (with no ToolCalls)
to state even when the backend had already sent tool call frames.
Now returns a real error so state.Update is never called and the
session history stays clean for a retry.
2. context.go: BoundedHistory and BoundedHistoryWithNotice could evict
an assistant[tool_calls] message while its paired tool[result] messages
remained in the tail window, producing a tool message with no preceding
assistant — rejected by strict backends (OpenAI, DeepInfra) with a 400.
Fixed by dropping assistant+tool pairs atomically in the hard-limit loop
and adding a sanitizeHistory pass that strips any remaining orphaned
tool messages before the history is returned.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In the streaming path all tool calls were being added to skippedCalls,
causing the post-stream Act loop to suppress every 'call' emit. The
'-> tool(args)' lines therefore never appeared in the UI.
Fix: emit the 'call' OutputMsg inside runStreamStep once the stream
reports Done and all tool-call arguments are fully accumulated. The
Act loop still skips re-emitting them (correct), while the UI now
sees one 'call' event per tool invocation.
OutputMsg.Usage carries the raw backend.Usage value when Role=="usage",
rather than formatting it into Content. main.go can now read em.Usage
directly instead of parsing the formatted string.
Derive a human-readable backend label from OLLIE_BACKEND and
OLLIE_OPENAI_URL at startup and store it in model.backendName.
For the openai backend, inspect OLLIE_OPENAI_URL to distinguish
known providers (openrouter, together, groq, mistral, anthropic)
from a generic openai-compatible endpoint, so the bar shows e.g.
"openrouter" instead of "openai" when using OpenRouter.
Status bar format changes from:
[model] state | ...
to:
[backend :: model] state | ...
Add a one-line status bar rendered between the viewport and the textarea.
The bar shows:
[model] <state> | last: ↑N ↓N | ctx: ~Nk tokens (N msgs)
Agent state transitions:
idle - no agent running
thinking… - agent is running, no tool call yet this step
tool: foo - agent is executing tool 'foo'
Token usage is updated from the 'usage' agent output event.
Context stats are updated from session.ContextStats() after each agent step.
The 'usage' role no longer appends a display line; it only updates the bar.
OpenAI sends usage in a separate trailing SSE chunk, not in the same
chunk that carries finish_reason. The previous code read wire.Usage
from the finish_reason chunk (where it is always zero) and discarded
all subsequent chunks.
Fixes:
- Add stream_options:{include_usage:true} to the request so OpenAI
actually includes usage in the stream at all.
- Accumulate usage across every chunk instead of reading it once at
finish_reason time.
- On the Done event, use the accumulated usage rather than the
(always-zero) usage from the finish_reason chunk.
- Keep processing chunks after finish_reason until data:[DONE] so
the trailing usage chunk is not silently dropped.
Add agent/context.go with a ContextBuilder that enforces a character budget
(soft + hard) over the assembled history, evicting oldest non-system messages
first and replacing large tool outputs with truncated summaries.
Update agent/session.go to use ContextBuilder in History(), so the loop
always sees a bounded context window regardless of how many steps have run.
The budget defaults are:
- SoftLimit: 24000 chars (~6k tokens at 4 chars/token)
- HardLimit: 96000 chars (~24k tokens)
- MaxToolOutputChars: 2000
- TailMessages: 6 (always keep last N messages verbatim)
All limits are configurable via ContextConfig.
Instructs the agent to be terse: no preamble, narration, summaries,
task restatement, hedging, or self-congratulation. Based on
anvillm OUTPUT_PROTOCOL.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
openrouter is OpenAI-compatible; just set OLLIE_OPENAI_URL to the
OpenRouter endpoint. No separate backend case needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
openrouter is not a distinct backend; it's the openai backend pointed
at a different URL. Updated examples accordingly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove os.Environ() inheritance from MCP subprocess. The child
process receives only the vars explicitly listed in mcpServers[].env,
with values expanded from the parent environment via os.ExpandEnv.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The loop was calling append(os.Environ(), ...) on each iteration,
discarding all but the last extra env var. Start cmd.Env from
os.Environ() once, then append each configured key=value pair.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
os.ExpandEnv() applied to MCP server env values so \${VAR} syntax
works. Config load errors now appear in the startup display instead
of being silently dropped when using the default config path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace log.Printf MCP connection messages with entries in the
initial display slice, so connection status and tool counts appear
in the chat pane rather than on stderr.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>