Commit Graph

56 Commits

Author SHA1 Message Date
Levi Neely 8e68313142 remove agent/complete.go — completions now go through /generate
- Delete agent/complete.go (110 lines)
- The kate plugin builds the completion prompt inline and calls
  ollie-9p rdwr generate instead of ollie-9p rdwr complete
2026-08-07 07:38:09 +02:00
Levi Neely 1eacb4f42d agent: replace hardcoded model maps with models.yaml config
- Remove defaultCompactionModels map from compaction.go
- Add agent/models.go: load ~/.config/ollie/models.yaml for all backend/model settings
- Completion (Complete()) falls back to models.yaml completion section when env vars unset
- Compaction falls back to models.yaml compaction section per-backend
- Priority chain: agent config > OLLIE_*_MODEL env > models.yaml > session model
2026-08-07 07:24:40 +02:00
Levi Neely 267f438e9e clean up OLLIE_* env vars: replace with XDG conventions
- Remove all OLLIE_*_PATH vars (TOOLS_PATH, CFG_PATH, DATA_PATH, etc.)
  Use XDG_CONFIG_HOME/ollie/* and XDG_DATA_HOME/ollie/* instead
- Replace OLLIE_<TAG>_LOG per-component logging with single OLLIE_LOG={level}
- Remove Route()/RouteRequest/RouteResult (replaced by direct backend selection)
- Update sandbox config to use XDG paths instead of OLLIE_*_PATH tokens
- Update docs accordingly
2026-08-07 07:12:08 +02:00
Levi Neely 517d18d405 agent: inject AGENTS.md from cwd into user context
If AGENTS.md exists in the working directory, its contents are injected
as a user message at session start and after compaction (via spawnContext).
Not part of the system prompt — lives in conversation history.
2026-07-30 09:39:58 +02:00
Levi Neely 10bd60980a toolsrv: dynamic tool discovery, drop global cache
Registry.Summaries() and Load() now scan the tools directory on every
call instead of reading from a startup cache. New tools dropped into
the directory are immediately visible without restarting the server.

When tool_list is called, the OnToolsChanged hook fires and updates
the preamble's '# Available Tools' section in the live agent runtime.
No agent reload or session restart required.
2026-07-30 09:16:01 +02:00
Levi Neely 4ac5fc94be rename Runtime.ExecServer → Runtime.ToolServer 2026-07-29 23:22:32 +02:00
Levi Neely 18beb15698 split: toolsrv/ (server framework) + tools/ (builtin handlers)
toolsrv/ owns the Server struct, execution engine, registry, discovery.
tools/ owns the built-in handlers (Shell, ToolList, SkillLoad, etc.)

Dependency flows one way: tools/ imports toolsrv/.
Server.Dispatch uses a handler map populated via WithBuiltins().
2026-07-29 23:17:22 +02:00
Levi Neely 10e0731a0a merge execute/ into tools/
tools.Server is now a concrete struct (the local execution engine).
tools.Runner is the minimal 2-method interface for polymorphism
(satisfied by both Server and RemoteServer).

Deleted: Dispatcher, CWDSetter, EnvSetter, ToolRestrictionSetter
interfaces. Agent uses inline type assertions where needed.

The execute/ package no longer exists.
2026-07-29 23:01:47 +02:00
Levi Neely d6bc89d3e2 agent: eliminate Dispatcher, use tools.Server directly
Runtime.Dispatcher → Runtime.ExecServer (tools.Server). The Dispatcher
abstraction routed to exactly one server ('execute') — unnecessary
indirection. BuildRuntime now takes tools.Server directly.

Agent.newDispatcher → Agent.newToolServer. AgentCfg/Config updated.
All GetServer('execute') calls replaced with direct ExecServer access.
exec closure simplified: direct CallTool, no tool-name lookup.
2026-07-29 22:50:01 +02:00
Levi Neely 0524f38db0 agent: remove redundant runtime nil checks
Runtime is guaranteed non-nil by the caller (session.New ensures it).
Remove defensive checks that can never fire. execServer() retains
the Dispatcher nil check (agents without tools are valid).
2026-07-29 22:41:58 +02:00
Levi Neely cde16ada08 agent: unexport ExecServer → execServer
No external callers — only used internally by the detach methods
(Detach, ListDetached, SignalDetached, GetDetachedOutput, DismissDetached).
The accessor is now private, no longer leaking the dispatcher interface.
2026-07-29 22:38:08 +02:00
Levi Neely 8acb4a901e agent: add Complete (one-shot ephemeral agent)
TRANSITIONAL: stateless code completion as a package function.
Intent is to unify with Agent struct — a full Agent composes a one-shot.
2026-07-29 22:19:19 +02:00
Levi Neely ec62eb9189 session: remove Interrupt, Queue, Detach, and all remaining agent pass-throughs
Session now has only genuinely session-level methods:
- Agent(), Bus() — access points
- Close(), SetEnv() — lifecycle
- CWD(), SetCWD(), SetSessionID() — filesystem identity
- WaitChange() — field watching for 9P
- SaveSession(), saveSession(), flushSave() — persistence

Everything else (Interrupt, Queue, Detach, Reactions, etc.) lives on Agent.
Callers go through session.Agent() to reach agent operations.
2026-07-29 22:05:46 +02:00
Levi Neely 24b9a0bdd3 session: remove Submit and agent pass-through methods
Session no longer has Submit(), IsRunning(), State(), Reply(), BackendName(),
ModelName(), AgentName(), CtxSz(), Cost(), Usage(), Context(), SystemPrompt(),
GenerationParams(), SetGenerationParams(), CompactionModel(), SetCompactionModel(),
ListModels(), Reactions(), React(), ReactTo(), PopQueue(), ToolCallCount().

Callers access the Agent directly via Session.Agent(). Session retains only
genuinely session-level operations: Agent(), Bus(), Close(), SetEnv(),
Interrupt(), Queue(), InjectSystemEvent(), CWD(), SetCWD(), SetSessionID(),
WaitChange(), persistence, and detach.

Agent.Submit now dispatches slash commands internally (HandleCommand) before
falling through to executeTurn.
2026-07-29 21:57:53 +02:00
Levi Neely 0b3d04c17c session: remove command dispatch from Submit
Session.Submit now forwards directly to Agent.Submit. Slash commands
are agent-level concerns handled by Agent.HandleCommand.

Session-level operations (save, resume, kill, rename, cwd) are ctl
verbs issued via the ctl file — they are NOT slash commands and do
not flow through the prompt path.

This eliminates the artificial session/agent command routing and
makes the prompt path clean: everything in the prompt goes to the agent.
2026-07-29 21:52:56 +02:00
Levi Neely 8e4cdaa4bd session: extract agent/ as separate package
Agent is now in ollie/agent with proper encapsulation:
- Unexported fields, exported methods as the API
- Own constructor (agent.NewAgent)
- Owns: turn execution, history, runtime, hooks, commands, compaction
- Session never reaches into agent internals

Session (ollie/session) is a thin host:
- Owns: persistence, session ID, env, detach delegation
- Delegates all agent operations through exported Agent methods
- handleCommand dispatches to agent.HandleCommand for agent-level commands

Agent-level commands (/model, /backend, /compact, /agent, etc.) live
in agent/commands.go and access internals directly (same package).

Session-level commands (/sessions, /save, /resume, /cwd, /help)
remain in session/commands.go.

Test files temporarily removed pending rewrite against new API.
The fifo_test.go passes as a sanity check.
2026-07-29 21:43:49 +02:00
Levi Neely 6ee949bd7c rename agent/ → session/, absorb session.Session into harness
The agent/ package is now session/ — because a session is the
top-level concept. The package contains:
- harness: implements Core, the session orchestrator
- Agent: the reasoning entity (history, runtime, tools)
- History: conversation accumulator
- Fifo, loop, compaction, hooks, commands

The old session/ package (which just held extracted fields) is
dissolved back into the harness. One package, clean ownership.
2026-07-29 19:13:26 +02:00
Levi Neely 761dd51e24 agent: rename sessionHost → harness, host.go → harness.go
The harness is the Core implementation that wires a session.Session
to an Agent and drives execution. A session has a harness; the
harness runs the agent.
2026-07-29 18:47:41 +02:00
Levi Neely cc9fa0c8cd agent: rename type agent → sessionHost, core.go → host.go
The struct that implements Core is the session host — it owns a
session.Session and an Agent, orchestrating their interaction.
Renaming makes the architecture self-documenting:

  sessionHost (implements Core)
  ├── sess *session.Session  — runtime environment
  └── agent *Agent           — reasoning entity (swappable)
2026-07-29 18:44:13 +02:00
Levi Neely bda9c9cb1d agent: rename reasoning.go → agent.go 2026-07-29 18:42:55 +02:00
Levi Neely 1aebc49d4e agent: rename reasoning → Agent
The inner struct that holds agent-specific state (history, runtime,
backend, tools, hooks) is now called Agent — because that's what it
is. A session owns an Agent; the Agent performs reasoning within
the session's environment.
2026-07-29 18:42:28 +02:00
Levi Neely da4d047aa1 agent: extract reasoning struct (swappable agent state)
Move agent-specific fields into a nested 'reasoning' struct:
- history, runtime, cfg (per-turn params)
- agentName, agentsDir
- baseLayers, promptEnvExtra
- newDispatcher, newBackend
- currentAction, warnedContext, resultCache

The agent struct is now the session host (owns sess + reasoning).
On /agent swap, a new reasoning can be built from the new config
while the session (identity, state, bus, env) remains stable.
2026-07-29 18:35:45 +02:00
Levi Neely 23de2e8267 agent: rename receiver s → a for clarity
The agent struct receiver was historically 's' (from when it was
called 'session'). Now that session is a separate concept, rename
to 'a' to match the type name.
2026-07-29 18:33:21 +02:00
Levi Neely 6396167361 agent: delegate sessionID, cwd, env, uname to session.Session
Continue moving session concerns to the session package:
- sessionID → sess.ID()/SetID()
- cwd → sess.CWD()/SetCWD()
- env/envMu → sess.SetEnv()/Env()
- uname → sess.Uname()

Mutex responsibilities are cleanly split:
- session.Session.mu protects: state, reply, cwd, id
- session.Session.envMu protects: env map
- agent.mu protects: history, runtime (agent-specific state)
2026-07-29 18:31:18 +02:00
Levi Neely 64f3f18573 agent: delegate bus, fifo, state, reply to session.Session
Move session-scoped concerns out of the agent struct:
- Bus() → sess.Bus()
- Queue()/PopQueue() → sess.Queue()/PopQueue()
- State()/setState() → sess.State()/SetState()
- Reply() → sess.Reply()/SetReply()

Remove corresponding fields from agent struct. Agent-level
WaitChange still uses its own changeMu/changeCond since it
observes multiple fields (state, usage, ctxsz, cwd, agent).
2026-07-29 18:28:14 +02:00
Levi Neely 498278665f rename agent.Session → agent.History
The Session type in agent/ is purely a conversation accumulator
(message history, token tracking, compaction state). Rename it to
History to free up 'Session' for the runtime environment concept
in the upcoming agent/session split.
2026-07-29 18:21:34 +02:00
Levi Neely 2ea13cd611 restructure: drop pkg/, split tools/execute into focused packages
- Drop pkg/ prefix (Go anti-pattern): ollie/pkg/X → ollie/X
- Split tools/execute god package:
  - execute/: shell execution, sandboxing, elevation client, remote SSH
  - tools/: interfaces + registry + discovery + schema parsing
  - detach/: background process management (ring buffer, signal)
- Promote internal/sandbox → sandbox/
- Absorb config/ into agent/config.go (agent definition loading)
- Merge remote/ into execute/remote.go (RemoteServer)

All tests pass.
2026-07-29 18:10:25 +02:00
Levi Neely c5b16e739e Restructure: pkg/core interface, internal/ packages, cmd/ollie TUI entry point
- pkg/core: public Core interface (Submit/Prompt/Interrupt) + Event/EventHandler types
- internal/agentcore: AgentCore implementation of Core (session, tools, commands)
- internal/tui: TUI package (readline loop, splitInput, signals, bracketed paste)
- internal/agent,backend,config,exec,mcp,tools: moved from root to internal/
- cmd/ollie: minimal main() that wires agentcore + tui together (~130 lines)
- mkfile: update build target to ./cmd/ollie

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 21:00:04 +02:00
Levi Neely 868ae09364 Add CodeWhisperer/Kiro backend + backend-aware model defaults
- backend/codewhisperer{,_internal}.go: full Amazon CodeWhisperer
  backend implementation — binary AWS event stream decoding, SQLite
  auth for both enterprise OIDC and personal social/GitHub flows, OIDC
  token refresh, and message encoding to the Kiro wire format
- backend/anthropic.go, copilot.go: new backends wired into New()
- backend/new.go: register anthropic, copilot, kiro/codewhisperer cases
- backend/openai.go: add extraHeaders hook for future use
- agent/loop.go: surface non-standard stop reasons as errors instead of
  silently dropping them
- main.go: defaultModelForBackend() sets a sensible default per backend
  (ollama→qwen3.5:9b, openrouter→deepseek/deepseek-v3.2,
   anthropic→claude-sonnet-4-5, kiro→auto); /backend switch now also
  resets the model to avoid stale foreign model IDs causing
  ValidationException; add -prompt flag for non-interactive batch mode

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 20:02:23 +02:00
Levi Neely 939f41013e agent: fix stall state always firing on normal text responses
The "no tools" stall condition (totalToolCalls==0 && hadContent) fired
whenever the bot replied in plain text without calling any tools, which
is the normal completion path. The UI would then see agentStalled before
the done message and never transition back to agentIdle.

Removed the "no tools" stall; only the max-steps limit case is a real
stall worth surfacing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 10:51:10 +02:00
Levi Neely b45f81908e agent: fix compact to proactively summarize older messages
Compact was gated on EvictedMessages(), which only returns messages
that have spilled past the 120k char soft limit. Normal sessions never
hit that threshold, making /compact a permanent no-op.

New approach: compact ALL messages older than the tail window (i.e.
everything computeTailStart puts before the protected tail), regardless
of budget. This makes /compact always useful.

Added OlderMessages(), TailWindow(), and SystemMessages() to
ContextBuilder to support the rewrite cleanly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 10:49:39 +02:00
Levi Neely bc8c6b75b5 session,dedup,compact: persistence, hard errors, summary display, write-then-write
Session persistence:
- Sessions saved to ~/.config/ollie/sessions/{id}.json after each
  completed agent turn and after /compact
- --session <id> flag resumes a saved session, auto-loading its agent
- /sessions command lists saved sessions with agent and goal preview
- /clear and /agent switch generate a new session ID
- Session ID shown at startup

Dedup:
- file_read: overlap check now returns a hard error (was a warning
  prepended to the result); check uses requested range, not actual
  read range, to avoid redundant dispatchFileRead on overlap
- tool calls: duplicate (name, args) now returns a hard error; key
  is only recorded on successful execution

/compact: Compact() now returns the summary text; displayed in the
  UI so the user can verify quality; session saved after compact

Write-then-write:
- After a successful file_write, repopulate fileRanges with the
  written range so a follow-up write to the same region does not
  require a re-read
- Whole-file writes record exact new line count from content;
  range writes record the written [start, end] with totalLines=0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 10:46:01 +02:00
Levi Neely 1c4c3049ac agent: reduce repetition and blind writes with dedup caches and range tracking
- Revised system prompt with concrete prohibitions against verbosity and
  premature stopping
- Added GenerationParams (max_tokens, temperature, frequency/presence penalty)
  threading from agent config through backend ChatStream calls
- Added stall detection: emits "stalled" role on max-steps hit or zero tool
  calls with content, surfaces as "stalled" in status bar
- Added per-session file read range tracking: warns on overlapping re-reads,
  blocks file_write unless the target range was previously read
- Added general tool-call dedup: warns on exact (name, args) repeats for
  non-file tools
- Both caches invalidated on /compact and /clear; file read cache invalidated
  per-path on file_write
- Updated all agent configs with documenting defaults for new generation params

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-04-06 21:29:20 +02:00
Levi Neely 92db8f88cd fix: null content on tool_calls messages; skip empty tool names; rollback on error 2026-04-05 11:53:36 +02:00
Levi Neely bd06d83a0e remove tool output truncation; raise context limits 2026-04-04 22:34:43 +02:00
Levi Neely 974b3b7461 increase context limits and replace tool output truncation with retry hint 2026-04-04 22:27:19 +02:00
Levi Neely e48617fc27 implement /compact and /clear commands
- /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
2026-04-04 21:51:30 +02:00
Levi Neely cd59dce4ca add user confirmation for file_read/file_write; fix display whitespace
- 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
2026-04-04 21:19:38 +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 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 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 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 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 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