Commit Graph

186 Commits

Author SHA1 Message Date
Levi Neely 250ad4b233 agent/new: sub-agent support via rdwr with prompt=
Writing to session/{s}/agent/new with a prompt= key now blocks
until the agent completes its task, then returns the reply and
destroys the transient agent. Without prompt=, behaves as before
(creates agent, returns ID).

Also:
- Move ParsePayload/UnescapeValue to shared ollie/toolsrv package
- Remove duplicate implementations from cmd/toolsrv/internal/server
- Add session.CreateAgentWithParams for direct AgentParams usage
- Eliminate flattenParams/unescapeValue redundancy in fs package
2026-08-14 14:48:12 +02:00
Levi Neely ab4668cb3f virtfs: rename Request to Rdwr
Read, Write, and Rdwr are the three atomic 9P operations:
- Read: non-blocking read
- Write: non-blocking write (fire-and-forget)
- Rdwr: atomic write-then-read (blocking, produces result)

BlockOnce and Stream are special cases of Read.
Rdwr is its own primitive — not a variant of either.
2026-08-14 14:42:19 +02:00
Levi Neely 5dcde264e6 toolsrv: add path-based lock table for cross-agent serialization
All foreground tool calls now acquire a lock based on the tool's
declared scope and file path before execution:

  - scope "read":  no lock (reads never conflict)
  - scope "write": exclusive lock on the file path
  - scope "global": exclusive global lock (serializes with everything)

This ensures writes to the same path serialize regardless of which
agent initiated the call, enabling safe parallel sub-agents within
a session without explicit coordination.

Cross-session serialization (shared toolsrv per host) is left as
future work.
2026-08-14 14:27:21 +02:00
Levi Neely c61fada3e9 extract rebuildAgentRuntime, remove nil guards on things that must not be nil 2026-08-14 08:47:59 +02:00
Levi Neely 785fe0996f fix: rebuild full agent runtime on session resume
Paused sessions restored agents with stub Runtime (nil Backend,
nil Preamble). On resume, now rebuilds the complete runtime:
loads config, builds preamble, creates backend, loads tools.
Added Agent.SetRuntime for this path.
2026-08-14 08:45:54 +02:00
Levi Neely a8fd7a658f fix: nil Preamble on restored paused sessions
The persist path created agents with &Runtime{} (nil Preamble).
If a prompt arrived before resume rebuilt the runtime, Preamble.Set
panicked. Initialize with an empty Preamble.
2026-08-14 08:42:03 +02:00
Levi Neely dc26871bdc proc/list: use fixed-width columns instead of tabs 2026-08-13 22:28:33 +02:00
Levi Neely 0fed1aa124 proc/list: include command in output 2026-08-13 22:24:50 +02:00
Levi Neely d64f6c1d76 proc: rename ListDetachedRaw→ListProcs, use proc/list rdwr (agent-filtered)
ListProcs now does rdwr to toolsrv proc/list with the agent ID.
Returns pre-formatted text. Ctl proc handler uses it directly.
Removed all ListDetachedRaw map-parsing logic.
2026-08-13 22:22:54 +02:00
Levi Neely 9246c44c0e agent: remove proc/detach/tool wrapper methods — inline in ctl handler
Removed from Agent: ListProcs, SignalProc, ProcOutput, DismissProc,
Detach, ListDetached, SignalDetached, GetDetachedOutput,
DismissDetached, BgProc type, DetachedInfo type.

The ctl handler now calls toolsrv client methods directly.
RefreshTools replaced by SetToolsPreamble + inline ListTools call.
renderTools exported as RenderTools.
2026-08-13 22:14:37 +02:00
Levi Neely c767ee10c9 ctl proc top: include STATUS column 2026-08-13 22:09:09 +02:00
Levi Neely de33f954e2 toolsrv: proc/list now rdwr with agent filtering
Write agent ID to filter, or empty for all. Returns pid\tstate\ttool.
Added ListProcsForAgent to State.
2026-08-13 22:08:15 +02:00
Levi Neely 48add8f7bb remove proc/ directory from agent namespace (processes live in toolsrv) 2026-08-13 22:01:06 +02:00
Levi Neely 6e5b1aa5f0 system prompt: remove proc/ from agent table (managed via ctl) 2026-08-13 21:59:34 +02:00
Levi Neely 6d4e7e237d system prompt: remove tools file from table (it's a ctl command, not a file) 2026-08-13 21:59:04 +02:00
Levi Neely fd4a3afa4f move tool catalog to toolsrv: add /all file, tools_all ctl command
- Remove /tools from olliesrv root (toolsrv owns all tool state)
- Add /all file to toolsrv namespace (lists all available tools on disk)
- Add ListAllTools() to toolsrv client library
- Add tools_all ctl command to agent (reads from toolsrv/all)
- Update system prompt with tools_all usage
2026-08-13 21:58:08 +02:00
Levi Neely a1e78bca80 remove tool_load: use ctl directly
tool_load was a built-in intercept in the agent loop — the only
'tool' that didn't run in toolsrv. Removed entirely:
- Intercept in loop.go (25 lines)
- Script + .meta in data/tools/
- autoLoad references in agent configs

Loading tools is now exclusively via ctl (which already existed):
  echo 'tool_load X' | ollie-9p write .../ctl

System prompt updated to show the ctl pattern.
2026-08-13 21:44:46 +02:00
Levi Neely 324247b6a5 fs: remove dead streamChat and mergeCtx 2026-08-13 21:09:53 +02:00
Levi Neely 4bb08e860b toolsrv: fix shutdown ordering — close conn before draining
On context cancellation, close the connection first to unblock
ReadFcall. Then wait for in-flight handlers, drain the response
channel, and wait for the writer to finish. No writes hit a
closed socket.
2026-08-13 21:08:01 +02:00
Levi Neely d9026499fd toolsrv: suppress write errors during shutdown 2026-08-13 21:04:19 +02:00
Levi Neely bcaac146d2 server: on BlockOnce timeout, fall back to non-blocking Read
Frontends rely on getting the current state on timeout (heartbeat).
When BlockOnce returns empty (5s timeout), the server now calls the
plain Read handler as fallback. Files without a Read handler (like
feed) return nothing — consumer unaffected.
2026-08-13 20:59:05 +02:00
Levi Neely dd0021fd9a fix: restore sessions after 9P listener is ready
ConsumeFeed dials the 9P socket. Moving session restore to after
the listener starts ensures the socket exists when feed consumers
connect.
2026-08-13 20:55:02 +02:00
Levi Neely dcd119c853 virtfs: refactor Stream to accept (readFn, signalFn)
Same pattern as BlockOnce: framework handles the blocking loop.
readFn takes a base string, returns (data, nextBase, error).
signal fires when new data may be available.

Chat stream handlers now use Stream(a.ChatRead, a.ChatSignal).
StreamRaw retained for custom handlers.

streamChat() in support.go is now dead code (replaced by ChatRead).
2026-08-13 20:50:27 +02:00
Levi Neely 794673a5d0 virtfs: refactor BlockOnce to accept (readFn, signalFn)
BlockOnce now takes a value-reader and a signal source. The framework
handles the blocking loop: read → compare hash to base → if different
return → else wait on signal or timeout.

On timeout (ctx.Done), returns empty (not error) so clients re-open
cleanly.

BlockOnceRaw retained for queue-style handlers (bypass/pending,
proc/wait) that manage their own blocking.

EventValue adapter wraps a <-chan Event into BlockOnce-compatible
(readFn, signalFn) pair via a thin goroutine.
2026-08-13 20:44:50 +02:00
Levi Neely b5cb8c397d feed: BlockOnce with base init, ConsumeFeed via lib9p client
- BlockOnce handler initializes base to current hash on fresh open,
  then blocks until hash changes. Same pattern as statewait.
- ConsumeFeed is a plain function: dials 9P, reads feed in a loop
  (open → block → data → close → repeat), submits to agent.
- Context cancellation closes the 9P client, unblocking Read().
- Called as go ConsumeFeed(ctx, ag) from AddAgent and session resume.
2026-08-13 20:26:08 +02:00
Levi Neely e2695d6f50 feed: export Feed field, remove wrapper methods, use direct access everywhere 2026-08-13 20:09:28 +02:00
Levi Neely 1537e35685 feed: inline handlers, remove FeedBlockingRead wrapper 2026-08-13 20:06:32 +02:00
Levi Neely 721b49ffa2 feed: use BlockOnce for read side, dedup via base comparison
Feed is now read-write (0666):
- Write: store data, signal via notifyChange
- Read (BlockOnce): block until content hash differs from base

Dedup lives in the read layer (same pattern as statewait). The
internal consumer goroutine and external 9P clients both call
FeedBlockingRead — same codepath, same dedup logic.

Consumer started from AddAgent (if ctx available) and session resume.
2026-08-13 20:05:46 +02:00
Levi Neely dfacdfed94 agent: add feed file — change-detecting streaming input gate
Feed is a write-only file in the agent namespace. Writes are
deduplicated against the previous value; the internal consumer
(blocking on WaitChange/WatchFeed) only wakes when genuinely new
data arrives.

Wiring is external and source-agnostic:
  # human → observer (poll git):
  while :; do git diff HEAD; sleep 5; done | ollie-9p write .../feed
  # agent → observer (stream chat):
  ollie-9p read .../coder/chat | ollie-9p write .../observer/feed

Uses the agent's existing signalCh/notifyChange plumbing — no new
channel infrastructure. Consumer goroutine spawned at agent creation
(AddAgent) and session resume, tied to session context.
2026-08-13 20:02:06 +02:00
Levi Neely 5459e7fa2a fix: XDG default fallback in prompt resolver; deduplicate tool headers
- envLookup: provide XDG spec defaults (XDG_CONFIG_HOME, XDG_DATA_HOME,
  XDG_CACHE_HOME) when env vars are unset, so agent profile prompts
  resolve correctly on systems without explicit XDG vars.
- renderTools: skip generated '## name' header when the tool's prompt
  text already starts with a markdown header, eliminating duplicates.
2026-08-13 15:45:35 +02:00
Levi Neely 6a19c135eb prompts: drop tool path from system prompt, use absolute path in copilot example 2026-08-13 12:04:52 +02:00
Levi Neely 83b0b59a0a prompts: remove /home/user path examples that bias tilde expansion
- file_grep.meta: replace /home/user/project with /abs/path
- system_prompt.md: use $XDG_CONFIG_HOME instead of ~
- agent-copilot.md: use relative path in code block example
2026-08-13 12:02:27 +02:00
Levi Neely 1ac50d97e1 agent: drain FIFO after interrupted/failed turns
Fix holes where queued prompts were orphaned:
- After turn loop exits (interrupt, toolsrv unavailable), drain remaining FIFO items
- On panic recovery, pop next FIFO item and re-submit in a new goroutine
- fifo file write triggers Submit when agent is idle
2026-08-13 11:59:11 +02:00
Levi Neely 9b98a958cc fix: treat meta cmd as shell command string, prepend tools dir to PATH
- ResolveTool now returns cmd field as-is instead of trying to resolve
  it as a binary path. This allows meta-only tools to use full shell
  command strings with env vars, pipes, and subshells.

- Prepend ~/.config/ollie/tools to PATH when executing tools so cmd
  fields can reference other tools by name.
2026-08-12 18:44:16 +02:00
Levi Neely 23f3a6092b refactor: eliminate TurnCtx, make loop functions Agent methods
- Delete TurnCtx struct (was progressively populated with optional nil fields)
- Convert run(), streamResponse(), execToolCalls(), execBatch(), execOne(),
  trackErrors(), retryCountdown() to Agent methods
- Extract autoCompact() and popInject() as Agent methods
- turn.go now installs a turn-scoped output handler wrapper instead of
  building TurnCtx closures
- context.Context is now passed as plain context.Context, not smuggled
  with unrelated state

Add history compaction tests:
- TestBuildCompactedHistory_OrphanedToolWalkback: verifies walk-back logic
  prevents orphaned tool messages at hot zone boundary
- TestBuildCompactedHistory_NoOrphanAtBoundary: verifies tool messages
  always have preceding assistant with tool_calls
2026-08-12 18:06:19 +02:00
Levi Neely 0a4149218a fix: set agent ID on toolsrv connection after resume 2026-08-12 17:10:22 +02:00
Levi Neely bc7f51a9dc refactor: push proc completion from toolsrv to agent prompt
- toolsrv pushes <system-proc-complete> to agent prompt when bg proc exits
- Remove bgTracker and CollectInterrupts from olliesrv
- Add Cmd, AgentID, SessionID fields to Proc
- Add OnProcExit callback to State
- Add command field to Proc.Stat() output
2026-08-12 16:39:43 +02:00
Levi Neely 6eb3bf960b refactor: query toolsrv directly for proc list
- ListProcs() now queries toolsrv directly instead of local tracker
- Remove ownership verification from SignalProc/ProcOutput/DismissProc
- Clean up stale procs from local tracker when they disappear from toolsrv
- Document local tracker quirk in TODO.md for future cleanup
2026-08-12 16:06:22 +02:00
Levi Neely 1d5e9c4f8d fix: background proc output capture and status display
- Detach background proc context from request context so processes survive
  after the 9P request returns (fixes premature SIGTERM)
- Add /proc/list file to toolsrv showing all procs with state
- Show status (running/exited (N)) in agent proc list
- Keep exited procs in tracker until explicitly dismissed
- Skip 'list' entry in ListDetachedRaw
2026-08-12 14:10:08 +02:00
Levi Neely 85435447bf fix: clean shutdown without connection errors
- Use read deadline + context check instead of forcibly closing connections
- ReadBypassPending now takes context and exits cleanly on cancellation
- Remove forced conn.Close() loop from Kill() - context cancellation suffices
2026-08-12 12:40:40 +02:00
Levi Neely 9057d36f05 docs: clarify tool usage in system prompt, add cancellation flow doc
- Explicitly tell model not to invoke tool scripts via filesystem path
- Add concrete examples of native tool calls vs shell
- Document context cancellation flow from interrupt to process kill
2026-08-12 12:20:34 +02:00
Levi Neely aedfdec564 fix: set agent ID on tool server after /agent switch
SwitchProfile creates a new tool server connection but wasn't calling
SetAgentID on it, causing 'agent ID required' errors when listing tools.
2026-08-12 11:08:37 +02:00
Levi Neely edd5ade471 refactor: simplify bypass - toolsrv is the broker, olliesrv is just a client
- Remove olliesrv's bypass broker machinery (pendingCh, EvaluateRequest, etc)
- Session bypass loop now just reads from toolsrv's bypass/pending and notifies
- Notification handler writes directly to toolsrv's bypass/resolve (fire and forget)
- Remove bypass/ directory from olliesrv's 9P namespace
- Remove session/*/bypass file (policy can be added back to toolsrv later if needed)

The flow is now:
1. toolsrv blocks tool execution, exposes request via bypass/pending
2. olliesrv reads from toolsrv, shows D-Bus notification
3. User clicks approve/deny, olliesrv writes to toolsrv's bypass/resolve
4. toolsrv unblocks and executes (or denies)
2026-08-12 11:01:29 +02:00
Levi Neely 46bd0dd999 fix: toolsrv 9P server now handles BlockOnce/Stream reads correctly
- Added BlockingReadMode() and StreamMode() handling to toolsrv's handleRead()
- Made NextPending() context-aware in both toolsrv and olliesrv bypass packages
- This fixes the bypass approval flow via 9P - reads now properly block until
  a request is available and respect context cancellation
2026-08-12 10:49:08 +02:00
Levi Neely d59f49ee54 feat: context cancellation for tool calls
Wire context cancellation through the toolsrv 9P server so that
canceling a CallTool context properly terminates the running tool.

Changes:
- virtfs: Add Close() to File interface with CloseFn for Request handlers
- toolsrv/p9.go: Handle requests concurrently to allow Tclunk during
  blocking Twrite; call entry.Close() in handleClunk
- toolsrv/internal/server/proc.go: Check ctx.Done() while waiting for
  tool completion in NewProc
- Integration tests for context cancellation chain

The cancellation chain: client cancels ctx → fid.Close() → Tclunk →
handleClunk calls entry.Close() → closeFn cancels reqCtx → NewProc
sees ctx.Done() and returns ctx.Err()
2026-08-12 10:00:44 +02:00
Levi Neely 31681cb4e7 Add context cancellation to bypass.Submit
bypass.Submit now takes a context and returns early when cancelled.
This allows the stop command to properly cancel pending bypass requests
that haven't been approved or denied yet.

The full context chain is now:
1. stop command -> Interrupt -> cancel actCtx
2. actCtx cancellation -> closes CallTool fid
3. toolsrv proc/new sees closed fid -> propagates ctx cancellation
4. ExecuteTool -> executeBypassDirect -> bypass.Submit
5. bypass.Submit returns ctx.Err() when context is cancelled
2026-08-12 09:32:21 +02:00
Levi Neely 5dc7cdc2ea Make Request() always context-aware
Remove RequestCtx variant - all Request handlers now take context.
This is required for proper cancellation of blocking operations
like tool execution when the client disconnects or stop is called.
2026-08-12 09:30:39 +02:00
Levi Neely 861cb47d13 Add RequestCtx for context-aware request handlers
- Add RequestCtx field to FsNodeDecl for handlers that need context
- Add RequestCtx() helper function in virtfs/decl.go
- Update builder.go to handle RequestCtx in validation and Open
- Use RequestCtx in toolsrv proc/new and proc/new.bg handlers

This allows the context to be cancelled when the 9P fid is closed,
enabling proper stop/interrupt of running tool calls.
2026-08-12 09:26:02 +02:00
Levi Neely 3a68b0be53 Fix bypass broker and stop command
- Wire BypassBroker into fs.Config before creating root tree
- Move defer bypassBroker.Close() outside block scope (was closing immediately)
- Rewrite /bypass/ FS nodes to match toolsrv's expected interface:
  - bypass/pending: blocking read returning JSON {id,cmd,cwd,env}
  - bypass/resolve: write JSON {id,approved,error}
- Add NextPending() method to broker with channel-based blocking
- Add JSON tags to Request struct for proper serialization
- Start bypass loop for restored sessions (was only for new sessions)
- Add context cancellation support to CallTool (closes fid on cancel)
2026-08-12 09:16:59 +02:00
Levi Neely 9bb3cd76b8 Remove sudo mechanism from bypass system
The sudo credential broker was never functional and added complexity
without value. This removes:

- Sudo field from bypass Request structs (broker, client, toolsrv)
- Sudo parameter from EvaluateRequest interface and implementations
- Sudo/ResetsCounter fields from MetaFile and Variant structs
- sudo: true from system_logs.meta variants
- All sudo documentation from writing-tools.md, tool-registry.md,
  core.md, evolution.md, and misc.md

Bypass remains fully functional for sandbox escapes. Tools that need
elevated privileges should handle that internally or be run manually.
2026-08-12 08:58:41 +02:00