When the 'agent <profile>' ctl command switches profiles, the tool
registry now clears old tools and loads the new profile's autoLoad
list. Previously, switching profiles left the old tools loaded.
Changes:
- registry: add ClearAgent(agentID) to remove all tools for an agent
- server/proc: add ClearAgent wrapper and 'clear <agentID>' ctl command
- toolclient: add ClearTools() method to ToolsrvConn
- agent: SwitchProfile now returns *AgentConfig for tool reload
- fs/spec: agent ctl handler clears and reloads tools after switch
Remove lazy tool loading (load-on-call). Tools must now be explicitly
listed in the agent's autoLoad config. Calling an unloaded tool fails
with a clear error message.
Package structure improvements:
- embedding/index.go: generic Index type for semantic matching
- skills/skills.go: uses embedding.Index internally, keeps Skill type
- agent/skill_match.go: matchSkills() for skill discovery
- agent/tool_match.go: matchTools() for tool hints (new file)
Tool hints now match only loaded tools, not all tools on disk.
This makes agent capabilities explicit and auditable.
- Add quirks package for stupid model behavior workarounds
- ShellInvokesNativeTool blocks shell(cmd="tool_name") patterns
- Add client_9p tool: native wrapper for ollie-9p operations
- Block ollie-9p in shell — use client_9p instead
- Update all prompts to use client_9p, not shell+ollie-9p
- Clarify 9P namespace is complete (tools are NOT in 9P)
- Registry.All() lists all available tools for validation
When an agent calls a tool that exists but isn't loaded, toolsrv now
automatically loads it instead of returning an error. This eliminates
the round-trip of a separate load call.
- Remove Registry.Exists() (superseded by auto-load logic)
- Fire OnToolsChanged callback after auto-load so olliesrv can update
tool definitions for subsequent turns
Timeout fix:
- Add Timeout field to ToolInfo (protocol) and MetaFile (metadata)
- proc.go respects tool-declared timeout before falling back to 30s default
- subagent_spawn.meta declares timeout=0 (no timeout) so the tool is
never killed prematurely while waiting for the sub-agent to finish
- Tool schema declares timeout with 'do not set' guidance to prevent
the LLM from adding a short timeout
Premature response fix:
- Inject behavioral prefix into sub-agent prompt: complete all work
before responding, report results not intentions
- Sub-agent's final text is returned to parent; this instruction ensures
it contains accomplished work, not a plan
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
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.
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.
- 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
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.
- 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
- 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
- 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
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()
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.
- 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.
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.
This refactors the bypass (sandbox escape) mechanism to work with remote
toolsrv deployments. Previously, bypass used a Unix socket which only
works when toolsrv runs locally. Now:
1. toolsrv exposes bypass/{pending,resolve} 9P files
- pending: blocking read returns next bypass request as JSON
- resolve: write JSON {id, approved, error} to complete request
2. olliesrv reads bypass/pending in a loop per session
- Evaluates requests through the existing bypass broker
- Policy check, rate limiting, user notification all stay in olliesrv
- Writes approval/denial back to bypass/resolve
3. When approved, toolsrv executes the command directly (no sandbox)
- Execution happens on toolsrv's host (local or remote)
- Output streams back through the normal tool call path
This enables bypass to work when toolsrv is remote:
- User sees the approval notification locally
- Command executes on the remote host outside its sandbox
Architecture:
toolsrv (remote) olliesrv (local)
┌─────────────────┐ ┌──────────────────┐
│ sandboxed cmd │ │ bypass broker │
│ ↓ │ │ - policy │
│ bypass.Submit() │──────│ - notification │
│ ↓ │ 9P │ - rate limit │
│ wait for result │←─────│ - user approval │
│ ↓ │ └──────────────────┘
│ execute direct │
└─────────────────┘
The package defines the Server type and its namespace — 'fs' was a
leftover name from when it only held the filesystem spec. Now it's
the server definition. File renamed spec.go → server.go to match.