- Add UserPrompts Prompt field to AgentConfig (same JSON format as prompt).
- Resolve userPrompts via resolvePrompt() in BuildRuntime, store in Runtime.UserPrompt.
- Prepend resolved user prompts to every user message at executeTurn start.
- Document userPrompts in data/agents/README.md.
- Remove per-step stripCold from run(); call once per turn in executeTurn
after accumulator reset and before preamble injection.
- Replace expensive deep-clone snapshot with pre-turn length truncation.
- Delete unused cloneMessages() helper.
- Remove per-step preamble prepending in loop.go; inject once at turn start
in turn.go instead of re-sending thousands of tokens every tool step.
- Re-inject preamble after overflow retry compaction where it was lost.
- Replace stripCold() N sequential LLM calls with one batched call that
summarizes all cold-zone tool results via a single JSON response.
Sub-agents now clone the parent agent's conversation history into
their initial context. The tool script passes parent=$OLLIE_UNAME
automatically. Mechanically identical to session restore — uses
RestoreHistoryFromMessages on the parent's Messages().
Add Sub-Agents section explaining agent/new with prompt= key.
Include examples for single and parallel sub-agent spawning.
Update 9P namespace table to show agent/new as rdwr.
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.
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.
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.
- 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
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.
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.
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.
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).
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.