Move spawn.go from toolsrv/ to cmd/olliesrv/internal/toolclient/:
- Process, ProcessKeeper, Spawn, SpawnRemote now in toolclient package
- toolsrv/ now only contains client code (Dial, Conn, etc.)
This clarifies the architecture:
- toolsrv/ = client SDK for connecting to toolsrv
- cmd/toolsrv/ = the toolsrv server
- cmd/olliesrv/internal/toolclient/ = olliesrv's toolsrv process management
Removed ProcessKeeper tests from toolsrv integration tests since they
now belong to toolclient.
tool_load must be handled specially by the agent runtime because tools
run inside toolsrv's sandbox and cannot load other tools into
themselves. The runtime now intercepts tool_load calls and directly
invokes ToolServer.LoadTool().
This is the only built-in tool - all others are external scripts.
Add /session/{sid}/agent/{aid}/tools file:
- Read: list loaded tools for this agent
- Write: load a tool by name
Also add missing tool_load to justfile install.
HandleCtl was only handling load/unload commands. The client sends
'env KEY=VALUE' and 'cwd PATH' via SetEnv() and SetCWD(), but toolsrv
was returning 'unknown command' errors. This broke tool_load and any
tool that depends on OLLIE_SESSION_ID/OLLIE_UNAME env vars.
Bug: spec9p.go was double-processing payloads - parseKV unescaped
\n to newlines, then rebuilt the payload without re-escaping,
then fs9p.go's parsePayload tried to unescape again. Result:
multiline content like file contents was truncated to first line.
Fix: Pass raw payload data through to fs9p.go. Escaping/unescaping
happens once in parsePayload.
Also:
- Add proper one-pass unescape function (handles \\n correctly)
- Add TestIntegration_MultilineContent to verify fix
- Add InputSchema verification to TestIntegration_ToolExecution
- Pass session ID via --session-id flag (not env var) so tool registry is configured when olliesrv spawns toolsrv
- /tools now returns JSON array with full ToolInfo including InputSchema (fixes Bedrock validation error requiring type: object)
- Implement SpawnRemote: deploy ~/.config/ollie via tarball over SSH, set XDG_CONFIG_HOME so tools/*.meta are found on remote
- Add --no-auth flag for debugging Tauth issues
Tests now skip gracefully in CI environments where ~/.config/ollie/tools
doesn't exist, instead of failing with 'no tool registry configured'.
Also added OLLIE_SESSION_ID to test server startup.
- Parse ToolResult JSON the same way agent/runtime.go does
- Verify Content structure, Type field, and IsError flag
- Add TestIntegration_ToolExecutionError for failed commands
- Tests now verify the wire format matches what olliesrv expects
After Write(), the file offset is at the end of the written data.
For request-response files (like proc/new), we need to read the
result from offset 0, not from the current offset.
Changed CallTool() to use ReadAt(buf, 0) instead of io.ReadAll()
which uses Read() and inherits the wrong offset.
Same fix was already applied to token reading in Dial().
Added TestIntegration_ToolExecution to verify the full tool
execution path works end-to-end.
Replace HMAC-based registration with proper 9P Tauth flow:
- First client to connect sets the secret via Tauth afid write
- Server stores secret in memory, returns session token
- Subsequent clients must provide matching secret
- Secret never in env vars or disk, only transmitted over socket
Changes:
- server9p.go: Authenticate() replaces RegisterAgent/GetAgent
- client9p.go: Dial() does Tauth handshake (write secret, read token)
- spawn.go: generates secret, no longer passes via TOOLSRV_SECRET env
- auth9p.go: simplified to just GenerateSecret()
- spec9p.go: removed /register file, token validation at connection level
- cmd/toolsrv/server.go: handleAuth/handleAuthWrite/handleAuthRead
Security model:
- Session generates secret on Spawn()
- All agents share session's secret via ProcessKeeper
- Socket permissions (local) or SSH (remote) protect transport
- Secret dies with toolsrv process, new secret on respawn
Added integration tests verifying:
- First connection sets secret
- Reconnect with same secret works
- Wrong secret rejected
- ProcessKeeper reconnect/respawn behavior
- Concurrent connections
- server.go: Full 9P2000 protocol handling
- version, auth, attach, walk, open, read, write, clunk, stat
- Uses fsedsl.Tree for all operations
- Handles directories and files
- Supports rdwr files (RequestRespMode)
- main.go: Remove placeholder serve9P, use new implementation
The toolsrv binary now serves a working 9P filesystem with the
namespace defined in toolsrv/spec9p.go:
/register, /ctl, /tools, /proc/*, /info
- spec9p.go: fsedsl-based namespace specification
- ToolsrvCtx with Server, Agent, Proc
- Handlers as package-level functions
- Follows fs/spec.go pattern
- server9p.go: simplified to state management only
- Agent registration with HMAC tokens
- BuildTree() creates fsedsl tree
- Removed redundant 9P protocol handling
The 9P protocol serving will use the same pattern as olliesrv
(cmd/olliesrv/server.go), which takes a *fsedsl.Tree and handles
all 9P message types generically.
New files:
- fs9p.go: FS9P struct with /proc-based execution model
- /proc/new (rdwr): blocking tool execution
- /proc/new.bg: background/detached execution
- /proc/<pid>/out, wait, stat, ctl: process management
- /tools, /ctl, /info: tool registry and host info
- Payload format: key=value lines, newline delimited
- exec9p.go: Decoupled tool execution
- ExecuteTool: standalone function for tool execution
- executeSandboxed: sandbox execution without Server deps
- executeBypassDirect: bypass broker without Server deps
- fs9p_test.go: Unit tests for FS9P
Design notes:
- Background execution is the internal model
- Foreground (/proc/new rdwr) blocks until completion
- No per-connection state (cwd/env passed per-call or at attach)
- Session ID scoping via registry, not env propagation
This is the foundation - actual 9P serving and integration pending.
The ctl tool_load command was loading tools into the registry but
not notifying the agent to refresh its preamble. Add RefreshTools()
method and call it after loading a tool via ctl.
Paths starting with / are treated as root-relative, ignoring
the current context prefix. This allows accessing root-level
files/directories even when in a session or agent context.
Example: o myproj/coding ls /bypass # list root bypass/ dir
Simplify context parsing:
- o <cmd> Root level
- o <session> <cmd> Session level
- o <session>/<agent> <cmd> Agent level
The slash disambiguates between session and agent context,
eliminating the need for 'session' and 'agent' keywords.
Examples:
o myproj ls # session level
o myproj/coding prompt # agent level
Refactor o CLI to use explicit context selectors:
- o <cmd> Root level
- o session <sname> <cmd> Session level
- o agent <sname> <aname> <cmd> Agent level
Environment variables $session and $agent can substitute for
positional args for backwards compatibility.
Update acme scripts to use new interface:
o agent $OLLIE_SESSION $OLLIE_AGENT prompt
Add acme-ollie-ensure helper that:
- Creates 'acme' session if it doesn't exist
- Creates agent named by cwd hash (like kate's approach)
- Updates agent cwd if it already exists
All acme commands now call acme-ollie-ensure before sending
prompts, so they work without manual session setup.
Add acme scripts for interacting with ollie:
- AskFile: describe the current file
- Explain: explain selected code
- Document: add documentation comments
- Fix: fix selected code
- Refactor: refactor selected code
- AddTests: write unit tests for selection
- SendVerbatim: send selection as-is
Scripts use the acme 9P filesystem to read selections and pipe
prompts to ollie via 'o prompt'.
Also update justfile to install/uninstall the acme scripts.
- backend: add CWD field to GenerationParams for environment state
- kiro: pass session CWD to kiroCurrentEnvState instead of hardcoded '/'
- agent/runtime: set GenParams.CWD from session cwd in BuildRuntime
- o new: create sessions and agents via 9P filesystem
- o prompt: support piped stdin for non-interactive use
- doc/boot-sequence.md: init chain from main → fs.NewRoot → session.Init
- Event protocol: comprehensive table on Event struct (all roles, semantics)
- RPC server moved from cmd/ollie-remote into toolsrv/rpcserver.go
(binary is now a 93-line thin wrapper)
- SetSessionEnv folded into NewAgent (no post-construction wiring needed)
- AgentCfg → AgentParams (distinguish from AgentConfig JSON schema)
- ToolResult/ToolResultContent shared types in toolsrv/rpcwire.go
- toolsrv/shell.go → toolsrv/exec.go (name matches purpose)
- Resume unified: single path, always through Keeper
- AGENTS.md: fix fs/builder.go → fsedsl/builder.go, handlers_*.go
- fsedsl/Tree: document dual-mode (EDSL lazy vs Manual Mount)
- Truncation stack documented at defaultToolResultMaxBytes
- Integration test rewritten to use srv.ServeRPC directly
The inFence state machine was dropping all lines between fence
delimiters, not just the delimiters themselves. Tool output, assistant
code blocks — everything inside fences was being eaten.
Fix: remove only [[[...]]] marker lines and bare fence delimiter lines
(``` / ```lang). Preserve all content between them.
When a streaming chunk contains only markers/fences, stripMarkers
returns empty bytes. The 9P server interprets a zero-length Rread as
EOF, closing the client's FD.
Fix: loop in streamAgentChatText, advancing the offset past
marker-only chunks until real content arrives.
README: mermaid diagram now shows o CLI as the foundation layer
between text frontends and 9P. KDE/Kate connect directly to 9P.
ellie.el (both versions): add ellie--9p-rdwr wrapper, use ctl rdwr
for systemprompt (no longer a standalone file).