Commit Graph

65 Commits

Author SHA1 Message Date
Levi Neely efa68f798b ! shell shortcut; fix tool output whitespace; file_write formatting hint 2026-04-04 21:01:59 +02:00
Levi Neely 2445ea0d5e add file_read/file_write tools; refactor dispatch into per-tool functions
- Add file_read: line-range aware file reading (start_line, end_line)
- Add file_write: full overwrite or line-range replacement
- Extract dispatchExecuteCode/Tool/Pipe into separate functions
- Prefer file tools over shell for file operations (system prompt)
2026-04-04 20:56:07 +02:00
Levi Neely 70a50d4ed8 fix interrupt handling: propagate context to subprocess tree
- 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
2026-04-04 18:18:02 +02:00
Levi Neely c2cbc3e806 split execute_code into three tools; improve ollama backend and system prompt
- 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
2026-04-04 17:57:55 +02:00
Levi Neely b00d970c74 default agent with superpowers 2026-04-04 12:15:43 +02:00
Levi Neely 500815c4c9 wrong filename 2026-04-04 12:05:30 +02:00
Levi Neely f201a0a737 agent installation; OLLIE_DEFAULT_AGENT 2026-04-04 12:01:57 +02:00
Levi Neely 40abd7a096 config,main: agent configs in ~/.config/ollie/agents/<name>.json
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>
2026-04-04 11:45:11 +02:00
Levi Neely 16c17f79e1 main: replace system prompt with minimal cost/behavior guardrails
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>
2026-04-04 11:35:26 +02:00
Levi Neely 078428881c agent: revert auto-nudge — model choice is the right fix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 11:30:57 +02:00
Levi Neely 76c5e696d5 agent: auto-nudge when model narrates intent without acting
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>
2026-04-04 11:28:11 +02:00
Levi Neely ab575ee385 backend,agent,main: retry on HTTP 429 with live countdown
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>
2026-04-04 11:25:05 +02:00
Levi Neely b3c4ea0ad4 main: restore slash command handling lost during streaming refactor
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>
2026-04-03 23:56:49 +02:00
Levi Neely f11d1a4d09 exec: remove unused IsPermissionError
No longer called anywhere after the sandbox fallback was removed from anvillm-mcp.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 21:11:22 +02:00
Levi Neely 5aea341255 context: fix budget accounting, tail counting, and output limits
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>
2026-04-03 20:27:16 +02:00
Levi Neely b7d46015d7 agent: simplify loop — Run as function, fix stop condition
- 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>
2026-04-03 20:13:14 +02:00
Levi Neely 474eb84e2d agent,backend: drop non-streaming path
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>
2026-04-03 20:11:10 +02:00
Levi Neely f804001d37 main: fix word fusion in streamed output and remap scroll keys
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>
2026-04-03 20:06:44 +02:00
Levi Neely 45a32ed4e7 agent: fix conversation history corruption on stream interruption
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>
2026-04-03 19:59:45 +02:00
Levi Neely 7edaf27132 disable workspacebase tempdir for now 2026-04-03 19:37:40 +02:00
Levi Neely b8f4b9a14a relax execute_code default sandbox 2026-04-03 19:33:27 +02:00
Levi Neely ffc3f07725 agent: emit 'call' events during streaming
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.
2026-04-01 13:35:59 +02:00
Levi Neely 84ad7451a7 agent: add Usage field to OutputMsg
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.
2026-04-01 13:28:12 +02:00
Levi Neely ace6afb853 main: show active backend in status bar
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 | ...
2026-04-01 13:26:22 +02:00
Levi Neely 8a2f2feecf main: add status bar with model, agent state, token usage, and context stats
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.
2026-04-01 13:20:54 +02:00
Levi Neely cd95e2f065 backend/openai: fix streaming token counts always zero
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.
2026-04-01 13:16:31 +02:00
Levi Neely 833f9e0a00 agent: add bounded context builder to prevent prompt explosion
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.
2026-04-01 13:10:56 +02:00
Levi Neely 71ff058b1a Fix: skip [0 0 tokens] when counts are zero 2026-04-01 12:56:35 +02:00
Levi Neely d60a1b9215 Add streaming support for bot output and tool execution
- backend: StreamingBackend interface + StreamEvent type
- backend/ollama: ChatStream with line-by-line JSON-LD decoder
- backend/openai: ChatStream with SSE delta parser + tool arg accumulation
- agent/loop: runStreamStep reads deltas, emits OutputMsg per chunk,
  deduplicates call/tool display between stream and outer loop
- main: agentMsg tea type streams incremental updates to the TUI;
  plain string buf replaces strings.Builder (avoids copy-by-value panic);
  drainAgent cancels in-flight goroutines cleanly on user interrupt
2026-04-01 12:52:58 +02:00
Levi Neely c31d157756 Fix: move modelName resolution after backend creation; rename install artifact to ollie 2026-04-01 11:12:56 +02:00
Levi Neely 401814c43f README: note anvillm dependency
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 01:05:55 +02:00
Levi Neely dbe8ac3d17 Add output protocol to system prompt
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>
2026-04-01 00:59:52 +02:00
Levi Neely 2286ebded5 Remove openrouter backend alias
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>
2026-04-01 00:57:18 +02:00
Levi Neely 10fa7439ee Fix README: backends are ollama and openai only
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>
2026-04-01 00:56:54 +02:00
Levi Neely 062ff2afee config json sample 2026-04-01 00:55:57 +02:00
Levi Neely b5389e96c7 Update README to reflect current architecture
Covers: agentic design, execute_code built-in, backend selection,
env file, config.json with MCP servers and hooks, UI keybindings,
token usage display, skill system.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 00:55:21 +02:00
Levi Neely fd4bb235b5 MCP subprocess inherits only whitelisted env vars
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>
2026-04-01 00:53:10 +02:00
Levi Neely d64e49e893 Fix MCP subprocess env: start from os.Environ(), append extras
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>
2026-04-01 00:51:32 +02:00
Levi Neely 6c38a1147c Expand env vars in MCP config; surface config errors in chat pane
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>
2026-04-01 00:45:43 +02:00
Levi Neely fd696b99f5 Show MCP startup results in chat pane
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>
2026-04-01 00:39:03 +02:00
Levi Neely 01b128fdf5 Restore MCP server support alongside built-in execute_code
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>
2026-04-01 00:34:09 +02:00
Levi Neely f9ba6c8bce Track and display token usage per request
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>
2026-04-01 00:31:03 +02:00
Levi Neely 75e00d438c Add chat history scrolling via PageUp/PageDown and ctrl+u/ctrl+d
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>
2026-04-01 00:24:19 +02:00
Levi Neely b59b9d9e31 Prohibit narration: call execute_code, don't describe it
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>
2026-04-01 00:11:15 +02:00
Levi Neely 715d68f974 Fix skill discovery instructions: must use 'tool' field, not 'code'
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>
2026-04-01 00:05:13 +02:00
Levi Neely 75cadfeb53 Soften planning prompt: execute_code only when needed
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>
2026-04-01 00:02:54 +02:00
Levi Neely cf56df2ba0 Show tool invocations in chat; fix planning loop stall
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>
2026-04-01 00:02:31 +02:00
Levi Neely f2f61ee6be Make system prompt more prescriptive for smaller models
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>
2026-03-31 23:55:43 +02:00
Levi Neely b529c23d75 Rename OLLIE_API_KEY to OLLIE_OPENAI_KEY
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>
2026-03-31 23:51:01 +02:00
Levi Neely ebb726e384 Split OLLIE_API_URL into backend-specific vars
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>
2026-03-31 23:50:05 +02:00