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>
Bring back ollie/mcp and ollie/tools packages. Config MCPServers
field restored. MCP tools are discovered at startup and added to
the agent tool list alongside execute_code. Dispatch routes by
server name for MCP tools, falls back to built-in execute_code.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add Usage{InputTokens, OutputTokens} to backend.Response. Both
OllamaBackend (prompt_eval_count/eval_count) and OpenAIBackend
(prompt_tokens/completion_tokens) populate it. The loop emits a
'usage' OutputMsg after each Chat call; the UI displays it as
[↑N ↓N tokens].
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Configure viewport keymap to use page-based scroll keys only, leaving
up/down arrows free for textarea cursor movement. Viewport is now
initialized once with the custom keymap and resized (not recreated)
on window resize. Key messages are routed to both viewport and textarea.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Small models were writing markdown code blocks describing what they
would run instead of actually calling the tool. Explicitly forbid
this pattern in the system prompt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Model was putting discover_skill.sh in the 'code' field and running
it as inline bash. Explicitly state that 'tool' field is required for
discover_skill.sh and load_skill.sh, and that 'code' must not be used
for them.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous wording implied execute_code was always required after
planning. Clarified that the model should begin executing immediately,
using execute_code only if the step requires it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Emit a 'call' message before each tool execution so the UI shows
execute_code(...) with full args before the result. Result lines
are now indented with '= ' to visually pair with the call.
System prompt now explicitly says to call execute_code immediately
after planning, without pausing for confirmation, to prevent smaller
models stalling after the planning step.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace high-level principles with explicit rules: when to call
execute_code, exact invocation syntax for all three modes, and
a numbered sequence (discover skills → plan → execute) that
smaller models can follow without inferring intent.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Consistent with OLLIE_OPENAI_URL; makes it clear the key only applies
to openai/openrouter backends.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OLLIE_API_URL was shared across backends, causing the ollama backend
to use the OpenRouter URL when OLLIE_BACKEND was overridden on the
command line while the env file still set OLLIE_API_URL.
Replace with OLLIE_OLLAMA_URL and OLLIE_OPENAI_URL so each backend
only reads its own URL setting.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Agent must discover and load relevant skills before acting on any task,
re-running discovery when domain shifts. Agent must also produce a
numbered plan before executing, and revise it if steps fail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fall back to the default path silently if the file does not exist.
Only fatal on a missing/invalid config if the path was explicitly
provided as a CLI argument.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Env file is optional; existing environment variables take precedence.
Supports KEY=VALUE format with # comments and blank lines ignored.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extract renderDisplay() to word-wrap each line at the viewport width
before setting content. Re-wrap on terminal resize. Fixes tool output
and long bot responses being truncated at the viewport edge.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BeadState duplicated what execute_code already provides via tool scripts.
Removed it entirely. Added SystemPrompt field to agent.Config, prepended
as a system message each loop iteration. Built-in prompt covers
execute_code usage, sandbox behavior, and skill discovery.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drop ollie/mcp, ollie/tools, ollie/ollama packages. Config now only
carries hooks. main.go wires a single execute_code tool directly to
the agent loop — no MCP discovery or dispatch needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
main.go now uses backend.New() for LLM selection (OLLIE_BACKEND env),
agent.Loop for observe/decide/act/update/terminate, and ollie/exec for
built-in execute_code. Multi-turn sessions persist across user inputs.
MCP tools route through mcpExecutor; unknown tools fall back to built-in.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements the core agent loop with two state backends:
- Session: ephemeral in-memory, lives for one process lifetime
- BeadState: bead-backed via 9beads tool scripts (claim/read/complete)
Loop is backend-agnostic; takes a ToolExecutor and emits OutputMsgs.
MaxSteps defaults to 1 for simple prompts; callers set higher for tasks.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extracts execute_code into a shared ollie/exec package so anvillm-mcp
and ollie's agent loop can both use it. Includes sandboxed execution,
dangerous-pattern validation with per-Executor rate limiting, 9P tool
reading, and pipeline construction.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>