If fidOK was false for the event file, we fell through to the
default stream handling path which doesn't work for events.
Now we return 'bad fid' error instead of falling through.
The pubsub library had issues:
- Published to literal '*' topic (nonsensical)
- Used TrySend which drops events
- Complex hierarchical wildcard publishing
New implementation:
- Simple eventHub with map of subscribers
- PublishEvent fans out to all subscribers (blocking send)
- SubscribeEvents returns channel, cleaned up on ctx cancel
- SubscribeEventsFiltered filters client-side with MatchTopic
- Removed simonfxr/pubsub dependency
The subscription was being cancelled after the first read completed
because we used the per-request context. Now using the connection
context so the subscription lives until the fid is clunked or
connection closed.
The pubsub library's SubscribeChan uses non-blocking TrySend which
drops events when the channel is full. Switch to Subscribe with a
blocking callback to ensure no events are lost.
Also increased channel buffers from 64 to 256.
Plain reads (without writing a filter first) now get a per-fid
subscription with '*' filter, identical to 'echo * | rdwrs event'.
This fixes event loss — the old EventStream path overwrote events
when multiple arrived before the reader consumed them.
The feed file was documented but never used by any frontend
or script. The observer agent pattern was never adopted.
- Remove feed.go, FeedWrite, ConsumeFeed
- Remove WatchFeed constant
- Remove feed file from 9P namespace
- Remove ConsumeFeed goroutine spawns from session
- Update docs (architecture-9p, architecture-ide, architecture, usage)
The event stream now covers real-time observation patterns better.
The event stream with filtering replaces statewait:
- echo filter | rdwrs event
Removed:
- statewait file from agent namespace
- All non-historical references in docs and code
The state file remains for simple polling reads.
- state: immediate read of current agent state
- statewait: blocks until state changes
Clearer semantics than overloading statewait with both behaviors.
EventValue was storing only the latest event and using hash comparison,
which caused events to be overwritten if they arrived faster than the
client could read them.
Now eventwait uses Stream mode with EventStream which delivers each
event as it arrives. Events won't be lost due to rapid arrival.
Server changes:
- Session tracks pending bypass request and exposes methods
- New 9P files: session/{sid}/bypass (read/write), bypasswait (blocking)
- Publish bypass.request events for GUI listeners
GUI changes:
- Handle bypass.request events from eventwait
- Show inline amber banner with command and cwd
- Approve/Deny buttons resolve via 9P
Desktop notifications still work in parallel for non-GUI usage.
When reading statewait with the same state value, the server now waits
500ms instead of 5 seconds before returning. This makes state polling
much more responsive.
Background procs with bypass were hanging because the caller blocked on
<-startedCh waiting for the process to start, but bypass.Submit blocks
until approval. Now we signal started immediately when entering bypass
path (with Process: nil), so the agent sees the proc ID right away.
Also handle term/kill when proc.proc is nil — cancel the context to stop
the operation (e.g., abort a pending bypass request).
executeBypassDirect was missing the streamOut parameter, so background
processes running via bypass never had their output written to the proc
buffer. Now both bypass and sandboxed paths receive the streaming writer.
Two bugs:
1. GUI sends 'agent=<profile>' but server only recognized 'profile='
- Added 'agent' as alias for 'profile' in parseAgentNewRequest
2. Model dropdown showed cost columns appended to model names
- /models format: backend<tab>model[<tab>in<tab>out...]
- Parser took everything after first tab as model name
- Now stops at second tab to extract just the model name
CreateEmpty now returns (session, created, error): if a session with the
given name already exists it is returned untouched instead of erroring,
and only a freshly created session receives the provided cwd/workflow/
variant. The o script no longer suppresses errors from session/new, so
real failures surface while re-creating an existing session stays a
no-op success.
Landlock cannot add rules for sockets (S_IFSOCK) or FIFOs (S_IFIFO).
Previously, attempting to add a path like /run/user/1000/wayland-0
would fail with EINVAL.
Now these special file types are detected and skipped gracefully.
The field is rejected by models that don't support thinking/reasoning
configuration. Check model name before setting the field; 'auto' and
unknown models skip it to avoid ValidationException errors.
Make the namespace explain itself instead of requiring prior knowledge.
- ctl is self-describing: reading it (empty write) or writing 'help'
returns the valid verbs with one-line descriptions; an unknown verb
errors with the valid list. Refactor dispatch to an ordered []ctlCmd
carrying descriptions; drop the undocumented '.' alias and the
drift-prone hardcoded help verb. Keep 'i' (drop 'inject') for fast
injects. o's ctl usage now reads the live listing.
- Errors carry severity + remediation. backend.ClassifyError maps the
typed errors to transient/config/fatal with a one-line fix; the error
event renders [[[error:<severity>]]] and a 'remediation:' line so a
human knows whether to wait or intervene.
- Add a human status file: 'thinking · 12s', 'calling shell · 3s',
'idle' — distinct from the machine-facing raw state. Wire the TUI bar
to it.
- Bare 'o' shows an overview of running sessions/agents with status, so
you don't need to know any names to get oriented.
Tests: dispatch help/unknown/routing, ClassifyError severity table.
reasoningEffort (low|medium|high) is now the single human-facing control
for model deliberation. Backends that speak a discrete level (OpenAI,
OpenRouter, Copilot, Gemini, Kiro) send it verbatim; Anthropic, which
needs a numeric budget, translates via documented thresholds
(low=4096, medium=8192, high=16384). thinkingBudget remains an advanced
explicit override that wins when set.
- Add EffortLow/Medium/High, threshold consts, ValidReasoningEffort,
EffortThinkingBudget, and GenerationParams.ResolvedThinkingBudget.
- Anthropic: derive budget from effort, grow max_tokens above the budget
(Anthropic requires budget < max_tokens), and force temperature=1 when
thinking is enabled (both are Anthropic requirements).
- Validate reasoningEffort at config load so typos fail loudly instead
of silently no-opping.
- theo: drop explicit thinkingBudget/inflated maxTokens; reasoningEffort
high now implies the 16384 budget.
- Document thresholds and per-backend behavior in data/agents/README.md;
fix stale autoLoad/maxSteps references.
- Add unit tests for the mapping and Anthropic thinking behavior.
Reasoning effort is part of an agent's behavior definition, so set it
on all 14 profiles keyed to role (planning/review high, general medium,
lightweight assist low). Also rename the ThinkingBudget json tag from
the confusing bare "reasoning" to "thinkingBudget", distinct from the
reasoningEffort string knob. No config migration needed — no profile or
backends.conf used the old "reasoning" key.
The autoLoad name implied an automatic tool-loading path that no longer
exists; tools now come only from agent config plus the /tool_load ctl
command. Rename the AgentConfig.AutoLoad field (json autoLoad) to Tools
(json tools), rename LoadAutoLoadTools to LoadTools, and update all 14
agent JSON profiles and the tool-not-loaded error message.
Regression guard for tool-free model compatibility: an agent with an
empty tool set must produce requests with no "tools"/"tool_choice"
field. Covers OpenAI, OpenRouter, Ollama, and Anthropic.
GitSpawn class (Sep 2026): a repo's .git/config can set core.fsmonitor to
a command that executes on any git index refresh, silently and as the
user. Ollie never spawns git in its own plumbing (repo detection is
os.Stat), but model-run git inside a malicious repo would fire it.
- exec: force core.fsmonitor=false via GIT_CONFIG_* in sandboxed tool env
- turn: wrap repo AGENTS.md in <context> markers (untrusted data,
filtered by KDE chat rendering)
- AGENTS.md: document the no-git-in-plumbing invariant (lesson 16)
Add per-model pricing to /models output. Format:
backend<tab>model[<tab>in<tab>out<tab>cache_read<tab>cache_write]
Pricing sources:
- Anthropic: hardcoded from official pricing, includes cache rates
- OpenRouter: parsed from API response pricing field
- Other backends: static lookup table fallback, marked with (e)
Changes:
- backend: Add ModelPricing/ModelInfo types, ModelLister interface,
static price table (Claude, GPT, Gemini, DeepSeek), LookupStaticPricing()
- openai: Parse pricing from API, implement ModelsInfo()
- anthropic: Implement ModelsInfo() with hardcoded cache rates
- fs/cache: Use ModelsInfo when available, fall back to static lookup,
format prices per 1M tokens with (e) suffix for estimates
- agent/cost: Use shared LookupStaticPricing instead of duplicate table
The old word-boundary check falsely triggered when tool names appeared
in arguments (e.g., git commit messages mentioning native tools).
New logic: split on shell separators and only flag when a tool is the
first token of a sub-command or a path ending with /toolname.
Allows user to background the current foreground tool process
by writing 'detach' to the agent ctl file. The agent can then
continue working without waiting for the tool to complete.
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.