Commit Graph

371 Commits

Author SHA1 Message Date
Levi Neely 4b96e0cae1 agent: audit logging via OLLIE_AUDIT_LOG=debug
Logger.Sub(tag) derives a child logger with a new tag from the same
writers, with level inherited from the parent but independently
overridable via OLLIE_{TAG}_LOG.

The agent creates an "audit" sub-logger at startup. Key events are
logged at debug level through it:
  audit: call: <tool> <args (truncated)>
  audit: result: <tool> <content (truncated)>
  audit: error: <message>
  audit: turn: start/end with cost and session ID
  audit: compact: messages removed, trigger, session ID

Enable with OLLIE_AUDIT_LOG=debug.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 18:21:13 +02:00
Levi Neely 59ee25b010 agent: per-turn and session cost tracking in USD
Usage events now carry a 4th field (costUSD) so OpenRouter's reported
cost flows through unchanged. For backends that only report token counts
(Anthropic, direct OpenAI, Ollama), cost is computed from a prefix-keyed
pricing table. The higher-fidelity source always wins.

Per turn:
- turnInputTokens/turnOutputTokens/turnCostUSD reset before each turn
- recordTurnCost() stores LastTurnCostUSD, adds to SessionCostUSD
- "costLast=$X.XXXX" emitted as an info event at turn end

Session file "cost" returns costLast/costSession key=value pairs.
Agent interface gains Cost() string.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 18:14:12 +02:00
Levi Neely 0aa44b4a00 agent: warn once at 60% context use before auto-compact at 75%
Emits "context at N% — will auto-compact at 75%" on the turn that
crosses the warn threshold, then suppresses further warnings until
the next compaction resets the flag.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 17:57:38 +02:00
Levi Neely 625257dea1 agent: retry on transient backend errors and mid-stream drops
Previously only HTTP 429 triggered a retry. Network failures, 5xx
responses, and mid-stream drops all hard-failed the turn.

- Add TransientError for 5xx and network-level failures; streamRequest
  wraps client.Do errors and >=500 responses in it
- Merge pre-stream and mid-stream retry into one loop (maxTransientRetries=3)
  covering rate limits, transient errors, and pure stream drops
- On stream drop with ctx cancelled: record partial state and return
  (existing pause behaviour preserved)
- On stream drop without cancellation: close any open <think> block,
  emit retry countdown, restart ChatStream from the same history

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 17:54:27 +02:00
Levi Neely 9fecf5f60f store: add read-only env file exposing OLLIE_ vars
New session file 'env' (mode 0444) returns OLLIE_SESSION_ID and all
OLLIE_ environment variables from the server process. Frontends can
read it to populate shell environments without depending on core.

Also fix pre-existing race in TestSessionFileStoreWritePrompt.
2026-04-26 22:18:17 +02:00
Levi Neely ed62caced5 agent: serialize Submit with submitMu to prevent command/turn races
Commands like /compact arriving via ctl and prompts arriving via prompt
both call Core.Submit in separate goroutines. handleCommand for mutating
commands (compact, clear, agent, backend) modifies session state without
setting currentAction, so IsRunning() returns false and a concurrent
executeTurn can race.

Add submitMu held during the serialized path of Submit. The fast path
(inject/FIFO push while running) remains lock-free.

Also fix pre-existing race in TestSessionFileStoreWritePrompt: replace
time.Sleep synchronization with a channel signal.
2026-04-26 19:40:06 +02:00
Levi Neely c32ea01f60 agent: interrupt is now pause, not cancel
With mid-turn SaveSession in place, interrupt keeps in-memory session
state matching the on-disk state rather than restoring the pre-turn
snapshot. The model resumes naturally from the saved history on the
next turn.

Remove sessionClean — no longer needed now that all interrupted sessions
are kept rather than conditionally discarded.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 19:25:57 +02:00
Levi Neely dc83763c24 agent: save session after each tool round for mid-turn crash recovery
Adds SaveSession callback to agentConfig, called after every state.update()
in the run loop. On restart, the session JSON contains all completed tool
rounds; the model continues naturally from current history.

SIGTERM is already handled in olliesrv via InterruptAll + Shutdown, which
cancels all session contexts and lets the loop exit cleanly after the
current state.update(). Worst-case (SIGKILL) loses one tool round, not
the whole turn.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 19:24:28 +02:00
Levi Neely eabe17a1b1 backend: stream extended thinking / reasoning tokens
- Add StreamEvent.Reasoning for incremental thinking deltas
- Add GenerationParams.ThinkingBudget to enable Anthropic extended thinking
- Anthropic: parse thinking_delta events; set Anthropic-Beta header and
  thinking request param when ThinkingBudget > 0
- OpenAI: parse delta.reasoning (o-series) and delta.reasoning_content
  (OpenRouter) fields
- Loop: emit "reasoning" events wrapped in <think>/</think> markers
- FormatEvent: append reasoning content directly to the chat log

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 19:01:27 +02:00
Levi Neely 132174df95 agent: drop error return from Session.update
The function only appends to a slice and can never fail. Removing the
dead error return eliminates the nolint:errcheck suppression on the
interrupted-stream path in loop.go.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 18:56:58 +02:00
Levi Neely 18b5c2c08c tests: improve pkg/store coverage to 85.3%
Add tests for FormatParams/ParseParams round-trip and error paths,
Session.Interrupt, createSession via NewCore injection, and openEntry/
OpenStore not-found paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 18:55:31 +02:00
Levi Neely 386dc64753 tests: add flock/locking coverage for execute package
Covers detectParallelClass, sanitizeLockName, acquireFlock, classifyStep,
SetLockDir, and runReadBatch — bringing pkg/tools/execute from 70.9% to 92.4%.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 18:50:35 +02:00
Levi Neely c7ad2ca68a backend: require OLLIE_BACKEND/OLLIE_MODEL; no silent ollama default
- NewWithName and newFromEnv now error when OLLIE_BACKEND is unset
  instead of falling back to ollama silently
- createSession reads OLLIE_MODEL as fallback when model= is not given
- Update test to assert error on missing OLLIE_BACKEND

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 18:25:39 +02:00
Levi Neely b54c5c08f9 kiro: implement social token refresh via desktop.kiro.dev 2026-04-26 18:11:21 +02:00
Levi Neely cafeff4a1e remove built-in SYSTEM_PROMPT.md loading
System prompt is now assembled via x/prime in the agent prompt array,
same as all other prompt fragments. Remove expandSystemPrompt and
the agentPrompt field — resolved prompt goes directly into preamble.
2026-04-26 15:10:05 +02:00
Levi Neely d0e1b6f6ef config: make prompt executable (string or []string of commands)
Prompt field in agent JSON now accepts either a string (existing
behavior) or an array of strings. Each array element is executed as a
shell command; their combined stdout becomes the agent prompt.

- Add config.Prompt type with custom JSON unmarshaling
- Rewrite resolvePrompt to handle both modes
- Wire resolvePrompt into BuildAgentEnv
2026-04-26 15:00:33 +02:00
Levi Neely dabae7f72b agent: preserve session on interrupt only when state is clean
Keep session state after an interrupt if the last message is a clean
assistant text response (no dangling tool calls). Roll back to pre-turn
snapshot if the history is inconsistent — mid-tool-call interrupts would
send malformed history to the backend on the next turn.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 13:36:43 +02:00
Levi Neely 554697e259 agent: preserve session state on interrupt, discard only on real error
Interrupting a long turn previously cleared the session (first-turn case)
or rolled back messages (subsequent turns). An interrupt is intentional —
keep whatever state was built so the user can continue.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 13:35:13 +02:00
Levi Neely c6d6f2f162 backend: recover DeepSeek DSML tool calls leaking into content stream
DeepSeek's native tool-call format uses <|DSML|invoke> tokens. When
OpenRouter fails to convert them to standard tool_calls chunks, they
arrive as raw content text. Detect the marker, buffer the content, and
parse it into ToolCall structs at [DONE] if no structured tool calls
accumulated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 13:17:29 +02:00
Levi Neely a2e01840b3 store: add s/{sid}/plan — session-scoped markdown task checklist
Session.plan field and SessionFile handler for 9p endpoint s/{id}/plan.
Agents write markdown checklists here via task_add/task_check/task_clear;
peers and parent sessions can read it directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 12:29:58 +02:00
Levi Neely 6abf0cdb3d execute: parallel dispatch via flock; agent: preTool/postTool hooks, turn rollback, mid-turn compact
execute_code auto-batches consecutive tool steps annotated '# ollie:parallel read'
into goroutine fan-outs. Write/global steps serialize. LOCK_SH/LOCK_EX via
syscall.Flock in a per-session lock dir for cross-process safety. Tool scripts
declare their own parallelism profile; no tool names hardcoded in Go.

- flock.go: lockClass, classifyStep (annotation-driven), acquireFlock
- tools.go: LockDirSetter interface
- server.go: lockDir field, SetLockDir method
- pipe.go: runReadBatch with shared-lock goroutine fan-out
- code.go: auto-batching pipeline loop
- core.go: pushLockDir wires session tmpdir to execute server at init;
  preTool/postTool hook callbacks in agentConfig; AutoCompact after each
  tool round; session snapshot/restore on turn failure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 09:00:29 +02:00
Levi Neely ac238f6903 agent: roll back session on turn failure
Snapshot session state before each turn. On any error from run(),
restore to the pre-turn snapshot so the session is clean for the next
attempt. Remove recordInterruption — those messages were appended to
state that gets rolled back anyway, making them dead code.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 07:30:59 +02:00
Levi Neely 0fba869ab7 agent: add preTool and postTool hooks
preTool fires before each tool execution; exit 2 blocks the call and
returns the hook's stderr as the tool error result. postTool fires after;
exit 0 appends context to the result, exit 2 replaces it entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 07:19:35 +02:00
Levi Neely aa890502cd agent: inject postTurn context into session history
Non-blocked postTurn hook output is now appended to session history so
the model sees it at the start of the next turn without triggering
autonomous continuation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 23:53:00 +02:00
Levi Neely 51922b78fc agent: mid-turn compaction and runCompact consolidation
Extract runCompact() to handle the full compact cycle (pre-hook, compact,
spawn-context re-injection, post-hook) consistently across all three call
sites. Wire AutoCompact into agentConfig so loop.go triggers it after each
tool round, not just before turns.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 20:51:53 +02:00
Levi Neely 77dd5c6168 agent: refactor prompt architecture and session lifecycle
- Rename loopConfig→agentConfig, agentCore→agent, loopcfg→cfg, systemPrompt→preamble
  for clarity: the struct represents agent runtime config, the field is its config,
  and preamble accurately describes the compiled system+agent prompt sent as system role

- Separate preamble from agent context: preamble now contains only SYSTEM_PROMPT.md;
  agent prompt and agentSpawn hook output are injected into session history at
  context refresh points instead of being bolted onto the system role

- Replace fireAgentSpawn (mutated preamble once, guarded by agentSpawnFired flag)
  with spawnContext (assembles agent prompt + hook output, returns string for caller
  to inject); remove agentSpawnFired entirely — session==nil is the sufficient guard

- Three refresh points inject spawn context into session history:
  session start, post-clear (session=nil triggers same path), post-compaction
  (auto and manual)

- Move all preTurn tool primes (tools-file, tools-reasoning, tools-memory,
  tools-subagent, tools-elevate) to agentSpawn in all agent configs so they are
  injected at session refresh points alongside bd prime, not repeated every turn

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 19:38:18 +02:00
Levi Neely d304014ba3 env: add OLLIE_ELEVATE_SOCKET to managed list and EnsureDefaults
Default to $XDG_RUNTIME_DIR/ollie/elevate.sock (falling back to
/run/user/<uid>/ollie/elevate.sock) so the daemon sets and propagates
the value without requiring explicit configuration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 11:45:46 +02:00
Levi Neely cb99bed7bb sort session index by ID for deterministic ordering 2026-04-24 21:44:42 +02:00
Levi Neely 3cef168806 add :: prefix for info events in chat log
- FormatEvent now prefixes info role with ':: '
- Session rename uses :: prefix
- Update test expectation
2026-04-24 21:33:12 +02:00
Levi Neely a4bb7608c7 expand ~ in cwd across agent core, hooks, and execute server
- Add paths.ExpandHome() helper for tilde expansion
- Apply in NewAgentCore, SetCWD (agent + execute server), and hook runner
- Fixes 'chdir ~/path: no such file or directory' errors
2026-04-24 20:10:42 +02:00
Levi Neely 07d362767d fix: update tests for statewait and hook changes 2026-04-24 14:08:02 +02:00
Levi Neely a7cc80af75 fix: set offset after assistant prefix for clean bfg output 2026-04-24 14:03:08 +02:00
Levi Neely ccbb5168b7 fix: statewait blocking and bfg composability
- Report non-zero size for wait files so FUSE clients read them
- Return current state on timeout instead of nil
- Set ChatOffset after user message so offset points past it
- Comment out hooksRan info events (TODO: route to debug file)
2026-04-24 14:00:27 +02:00
Levi Neely 4a8faf0ae3 store: remove BatchStore; sessions are the only session type
Batch jobs were a separate concept (one-and-done lifecycle, distinct
dirspec) that turned out to be a front-end concern. s/b, s/bfg, and
s/bbg now compose session primitives directly: create session, submit
prompt, loop statewait until idle, extract chat[offset:], kill.

Also renames spec -> cfg throughout (session config file) and removes
the parallel/output fields that previously lived in the batch spec;
parallel is now a shell loop, output=json is a prompt suffix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 08:41:42 +02:00
Levi Neely dacb9ead43 store: replace 9 session files with a single r/w spec file
Remove state, backend, model, agent, cwd, params, cwdwait, usagewait,
ctxszwait as individual files. Add spec (0666) which on read returns a
KV snapshot of all config and current state; on write, dispatches
partial KV pairs to the appropriate handlers (backend/model/agent via
Submit, cwd via SetCWD, generation params via SetGenerationParams).
Read-only fields (state, usage, ctxsz, offset) are silently ignored on
write. Only statewait survives from the *wait set — it has genuine
control-flow semantics; usagewait/ctxszwait/cwdwait are better served
by post-turn hooks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 07:42:33 +02:00
Levi Neely 10dd4ee82f store: SessionStore absorbs BatchStore, unified s/ namespace
SessionStore now embeds a *BatchStore and presents a unified
interface. s/new detects batch format (---  delimiter) and
delegates accordingly. list/stat/del/OpenStore/index all check
both session and batch maps. Shutdown/InterruptAll delegate to
both. Batch scripts (job, q, sched, cleanup) added to session
store file table.

This is the store-side half of Option C: single namespace with
composable file tables. The 9p server changes are in the parent
repo.
2026-04-23 18:49:35 +02:00
Levi Neely 8f856ea186 store: unify SessionFileStore and BatchJobStore via RunFileStore
Introduce FileSpec and RunFileStore as a shared, table-driven
implementation of RunnableStore. Both SessionFileStore and
BatchJobStore are now type aliases for RunFileStore, constructed
from a list of FileSpecs that define per-file read/write/wait
behavior via function pointers.

This eliminates ~94 lines of duplicated store plumbing (stat/list/
open/entryFor methods) while preserving the existing filesystem
layout and all public API signatures.

No behavioral changes. Tests pass unchanged.
2026-04-23 18:18:37 +02:00
Levi Neely 81fedb862c fix: ensure chat log lines are newline-terminated
assistant events are streamed without trailing newlines, causing
line-buffered 9P reads to lag one turn behind. Close the assistant
block with \n on role transitions and after Submit returns. Set
ChatOffset at EOF before the user event so offset spans the full
exchange. Remove 500-char truncation of tool call args in FormatEvent.
2026-04-22 16:34:06 +02:00
Levi Neely f220d0ee96 feat: add tail script to session files
Adds a synthetic `tail` file (mode 0555) to each session directory
that runs `tail -f chat`, allowing users to follow a session's chat
log from any working directory.
2026-04-22 12:29:24 +02:00
Levi Neely 197c3664c6 fix: resolve ${PWD} in system prompt from session cwd, not process env
expandSystemPrompt fell through to os.Getenv("PWD") which always
returned the process startup directory. Map "PWD" explicitly to the
cwd parameter so the system prompt reflects the session's working
directory.
2026-04-22 12:21:57 +02:00
Levi Neely e0bd5cefe8 docs: update README for refactor (packages, system prompt, sandbox) 2026-04-22 10:35:38 +02:00
Levi Neely 7884921dd0 fix TestWrapCommand flake when superpowerd is running 2026-04-22 08:59:54 +02:00
Levi Neely d89f458473 backend: mark codewhisperer as intentionally untested, remove test 2026-04-21 22:09:27 +02:00
Levi Neely 0fd3bf1cf8 test: paths 100%, execute 96.2% (was 71%)
- pkg/paths: cover CfgDir/DataDir env and default branches
- execute: cover all injectArgs languages, resolveCodeStep, runStage,
  dispatchExecuteCode, executeElevated via fake elevate script,
  elevated paths in runStage/dispatch, language branches (python, perl,
  bc, jq, awk, sed, ed, lua), limitedWriter edge cases, loadSandboxConfig,
  detectLanguage shebangs
2026-04-21 22:00:08 +02:00
Levi Neely 694530a321 sandbox: 100% test coverage
- Test all expandPath branches (HOME, XDG_*, OLLIE_CFG/DATA_PATH)
- Test checkPath symlink resolution and ROX read/write
- Test LoadSandbox reader error
- Test WrapCommand: landrun unavailable, ROX/RWX entries, sort tiebreaker, superpowerd wrapping
- Test isSuperpowerdRunning: all socket dir paths, successful connection
- Make isAvailableFn/isSuperpowerdRunningFn overridable for testing
2026-04-21 21:49:22 +02:00
Levi Neely 2264f3f11d agent: fix backend race, split commands.go, use OLLIE_TMP_PATH
- Add backend.NewWithName(name) to avoid os.Setenv race on OLLIE_BACKEND
- Extract handleCommand into commands.go (core.go 1264 -> 859 lines)
- Replace hardcoded /tmp/ollie with OLLIE_TMP_PATH (guaranteed by env.Ensure)
2026-04-21 21:44:39 +02:00
Levi Neely dee0fda330 agent: emit hook timeout/start-failure warnings as info events
- Add Warning field to HookResult
- runHookCmd sets Warning on timeout and start failure
- Hooks.Run collects warnings across commands
- All hook call sites emit warnings via infoEvent
- Test verifies timeout warning is surfaced
2026-04-21 21:20:55 +02:00
Levi Neely 53442e193b store: cover renameSession OnRename + SetSessionID error (89.2%) 2026-04-21 20:33:21 +02:00
Levi Neely bd6d9a09a8 store: add NewCore factory injection, integration tests (88.9% coverage)
- Add NewCore to SessionStoreConfig and BatchStoreConfig
- When set, bypasses backend.New + agent.NewAgentCore for testability
- Integration tests: createSession, handleNewBatch, runJob, executeJob
- Tests cover named/auto-ID sessions, parallel jobs, duplicates, cancellation, factory errors
- 98 tests total (was 88)
2026-04-21 20:28:20 +02:00
Levi Neely d64a8d8854 store: improve test coverage 58.6% -> 78.2% (51 -> 88 tests)
- SessionFileStore: content, readFile, writeFile, handleCtl, blockingRead, makePublish
- SessionStore: OpenStore, createSession error paths, Interrupt
- BatchJobStore: AppendLog, RunnableID, Interrupt, Cancel, write rejection
- SkillStore: open write path, entry Stat on non-existent
- Enhanced stubCore with recording and controllable WaitChange
2026-04-21 20:18:53 +02:00