Commit Graph

1581 Commits

Author SHA1 Message Date
Ollie Agent 7beef106e0 tool_load: add dynamic tool loader with bash script
Add a tool_load tool that writes a tool name to the session's 9P
tools file via ollie-9p, enabling on-demand tool loading.

Reduce autoLoad lists in default.json and theo.json to bare
essentials — everything else is loaded via tool_load.
2026-08-03 00:15:36 +02:00
Ollie Agent 0a418c20f4 bump kde 2026-08-02 23:59:36 +02:00
Ollie Agent 53ecbe249b fix tool output format; bump kde 2026-08-02 23:48:15 +02:00
Ollie Agent b2fe5f1b4e connection statuses 2026-08-02 23:35:30 +02:00
Ollie Agent d3860304af Add formatted tool output fences 2026-08-02 22:57:17 +02:00
Ollie Agent 2d621d26b9 Update KDE frontend 2026-08-02 22:34:41 +02:00
Ollie Agent 8fc47622c8 Persist renamed sessions and profiles 2026-08-02 22:34:35 +02:00
Ollie Agent 39ce817f51 Remove hard-coded tool autoloads 2026-08-02 22:33:54 +02:00
Ollie Agent 1f2870dd20 fix rename session and agent 2026-08-02 22:16:22 +02:00
Ollie Agent aa6f444fa5 bump kde 2026-08-02 22:10:45 +02:00
Ollie Agent 119e673299 fix naming problems 2026-08-02 22:10:00 +02:00
Ollie Agent 088aa38f1c data/agents/default.json: remove hard-coded shell and reasoning_think from autoLoad
These two are built-in tools always available regardless of autoload.
No need to explicitly load them.
2026-08-02 21:16:43 +02:00
Ollie Agent 9af742acd6 fs/lifecycle: fix nil MkdirAll panic blocking agent creation and autoload
The panic at fs/lifecycle.go:463 was a nil function pointer: fs.Config.MkdirAll
is a func field, not a method, and the struct literal in cmd/olliesrv/main.go
omitted it, defaulting to nil. When CreateAgent called
rs.cfg.MkdirAll(...), it panicked before ever reaching the autoload loop.

- Add MkdirAll: os.MkdirAll to fs.Config in main.go
- Spawn tool server in Create() so empty session/new sessions are
  operational, then reuse it in CreateAgent (else if sess.proc != nil)
- Wrap create agent error with fmt.Errorf for clearer messages
- Add panic stack trace to stderr for debugging
2026-08-02 21:16:12 +02:00
Ollie Agent 8fba664f11 cleanup / unify toolsrv bootstrap 2026-08-02 20:08:45 +02:00
Ollie Agent e25157ffcd multiplex toolsrv, wip zero-tool server 2026-08-02 19:18:39 +02:00
Ollie Agent 7b5573ee89 docs: fix design philosophy — Plan 9/Acme, not Emacs
Ollie's extensibility is the opposite of Emacs: instead of building
capabilities on top of an interpreter, the core provides integration
surfaces (9P, shell) and pushes everything else to the surrounding
system. This is the Acme/Plan 9 model.
2026-08-02 16:45:54 +02:00
Ollie Agent 6c6c133d76 docs: update architecture docs for Aug 1-2 refactors
Covers the major changes from the past two days:

- 9P Declarative EDSL (fs/spec.go + fs/builder.go, fs/session/ → fs/ flattening)
- 9P Server streamlining (fid map, GroupTable, method extraction)
- Multi-Agent Deepening (ID/Name split, UUIDv4, two-step creation, empty sessions)
- Eventwait redesign (global /eventwait ring buffer, structured delta events)
- Chat log [[[type]]]/[[[end]]] block format
- KDE submodule evolution (StreamFsm, responsive tree, emoji states, eventwait)

Updates: AGENTS.md, ARCHITECTURE.md, EVOLUTION.md, MULTI_AGENT.md,
         CORE.md, edsl.md
2026-08-02 16:43:13 +02:00
Ollie Agent bb152f401d create: set f.entry after creating files
After Tcreate on a file, the fid is open and should be usable for
read/write without a separate Topen. Set f.entry via root.Open()
after creation so subsequent reads/writes on the same fid work.
2026-08-02 16:23:08 +02:00
Ollie Agent b23785a4d9 Fix directory read: entry not required for dirs
Tree.Open() returns an error for directories (is a directory), so
f.entry stays nil after Topen on a dir. The entry == nil guard in
read() was firing before the isDir branch, breaking directory reads.
Moved the guard after the isDir check — directories use buildDirData
directly and never need entry.
2026-08-02 16:22:13 +02:00
Ollie Agent e1e03e50ae Inline handle into Start; rename Serve→Start, Shutdown→Kill
- handle() is gone — the dispatch + panic recovery is inlined into
  the per-request goroutine in Start(). Panic recovery is lifecycle,
  not protocol handling.
- Serve → Start: accepts a connection and runs the 9P loop
- Shutdown → Kill: stops accepting, cancels sessions, waits for
  goroutines to drain.
- 2 methods remain on *Server: Start, Kill. Server is purely lifecycle.
- All 9P protocol handlers are package-level functions.
2026-08-02 16:18:32 +02:00
Ollie Agent 447156ce13 Extract attach and open from *Server; pass groups and log explicitly
- attach() is now a function taking (*olog.Logger, *GroupTable, ...)
- open() is now a function taking (*fs.Tree, *GroupTable, ...)
- Both captured in the handler map via closures over s.sink, s.groups
- 3 methods remain on *Server: Serve, handle, Shutdown
2026-08-02 16:13:23 +02:00
Ollie Agent 3242f108f9 Store opened file on fid; read/write use f.entry instead of path re-resolution
- Added entry (fs.File) field to fid struct
- open() stores the result of rootTree.Open() on f.entry after
  permission check succeeds
- read() uses f.entry instead of calling root.Open(path[1:]) —
  fids survive renames because the File object references the
  tree entry by pointer, not by path
- write() uses f.entry for RequestRespMode writes — no longer
  needs root parameter at all; it's a pure connState+fid function
2026-08-02 16:11:57 +02:00
Ollie Agent 05e0aba155 Server owns the fid map: validate newfids, GC stale fids
- attach: reject if fc.Fid already in use (was silently overwriting)
- walk: reject if fc.Newfid already in use (both clone and full walk)
- open: validate fid path exists; delete stale fid if path is gone
- stat: validate fid path exists; delete stale fid if path is gone
- wstat: after rename, GC all fids on this connection that reference
  the old path tree (was previously updating paths — now just deletes)

The server fully owns the fid map. The client proposes fid numbers,
the server validates and accepts or rejects. Stale fids (pointing to
renamed/deleted paths) are garbage collected when detected.
2026-08-02 16:08:23 +02:00
Ollie Agent 8601562962 Extract GroupTable from Server; replace permission boolean dance with bitmask OR
- GroupTable is a standalone type with Add/Remove/IsMember, owns its
  own mutex and map. Server now holds groups *GroupTable.
- Removed AddGroup/RemoveGroup/InGroup from *Server (3 fewer methods).
- Permission check in open() now computes effective bits as a mask:
    bits := perm & 7                      // world bits
    if in group { bits |= (perm >> 3) & 7 }  // group bits
    if is owner { bits |= (perm >> 6) & 7 }  // owner bits
    if !hasPermBits(mode, bits) → denied
- No boolean accumulator, no tiered if/else, just bitmask OR.
- 6 remaining Server methods (down from 23).
2026-08-02 15:59:50 +02:00
Ollie Agent 0b4daf016a Replace checkPermBits error-return with hasPermBits bool
- hasPermBits uses a 4-entry mask array indexed by mode&3 to select
  the required permission bits: OREAD→4, OWRITE→2, ORDWR→6, OEXEC→1
- Zero branches, zero error paths — returns bits&mask == mask
- open() uses OR semantics: checks owner, group, world independently;
  any tier granting access is sufficient
- checkPerm removed entirely; permission logic is pure boolean math
2026-08-02 15:55:25 +02:00
Ollie Agent 325ad224be Extract checkPerm to package-level function, resolve perms in open
- checkPerm no longer needs *Server: it takes uid, perm, and inGroup
  directly. The caller (open) resolves uid/gid via fileOwnerGroup,
  perm via makeStat, and group membership via InGroup.
- checkPerm is now a pure function: (uname, mode, uid, perm, inGroup)
  → returns only on permissions, needs nothing from Server state.
- open is the remaining method that resolves these values.

server.go: 10 remaining Server methods.
2026-08-02 15:50:31 +02:00
Ollie Agent 8a13174b4f Extract 9 rootTree-only methods to package-level functions; replace switch with handler map
Converted walk, create, read, write, stat, clunk, remove, makeStat,
and buildDirData from *Server methods to functions taking *fs.Tree
as first parameter. Exactly the same pattern as fileOwnerGroup.

Also:
- Replaced the switch in handle() with a s.handler map, built once
  in New(). All 11 9P message types (Tversion through Tremove) now
  dispatch through the map.
- Extracted flush() as a package-level function (was inlined in switch)
- handle() is now: recover + map lookup. No inline cases, no switch.
- server.go: 802 lines, 10 remaining Server methods (down from 23)
- 20 package-level functions handle the actual protocol logic
2026-08-02 15:44:19 +02:00
Ollie Agent 54403a1ce2 Inline readFile/writeFile; remove unused FileTree alias
- readFile was dead code (defined but never called)
- writeFile was a 7-line helper called only once in clunk;
  inlined as direct s.rootTree.Open + e.Write
- FileTree type alias now unused after both deletions
2026-08-02 15:33:06 +02:00
Ollie Agent 082ba1c511 fileOwnerGroup: convert from method to package-level function
fileOwnerGroup only needed s.rootTree, not the full *Server. Making
it a function taking (*fs.Tree, string) removes one more method from
Server, makes the dependency explicit, and makes it unit-testable.
2026-08-02 15:31:33 +02:00
Ollie Agent 3279725128 Inline three thin wrapper methods in server.go
- openEntry (one-liner delegating to rootTree.Open): inlined into
  the two call sites in read() and write()
- statPath (one-liner delegating to rootTree.Stat): inlined into
  three call sites (fileOwnerGroup, walk, makeStat)
- InterruptAll (one-liner delegating to fs.InterruptAll): deleted,
  it was dead code with no callers anywhere

Also cleaned up: removed unused File type alias and unused 'os' import.

server.go: 825 -> 810 lines. Server method count drops by 3, but more
importantly, zero forwarding boilerplate remains.
2026-08-02 15:27:50 +02:00
Ollie Agent 1e126ce76a Replace imperative 9P filesystem with declarative EDSL
The old fs/session/ package (14 files) and cmd/olliesrv/elevate_tree.go
(imperative FileTree implementations) are gone. Replaced by a single
declarative EDSL in fs/spec.go that declares the entire 9P namespace.

cmd/olliesrv/server.go simplified from 1430 to 825 lines:
- Deleted pathType() (110 lines) — tree.Stat() now handles all path resolution
- Deleted isSessionFile(), dirMode(), filePerm() — all from tree's info
- Deleted fsRoute/routes/route/routeDir — single rootTree, no route table
- Deleted elevateTree — EDSL handles /elevate/ in spec.go
- Deleted handleWrite(), readDNS(), helpPath() — dead code
- Deleted all 5 per-fid result fields — EDSL Request handlers manage state
- Deleted all path-based intercepts in read()/write() — entry.RequestRespMode()
- Deleted all path-depth hacks in makeStat() — custom Stat() closures in EDSL
- Deleted all content-length compute logic — info.Size() from tree
- Removed imports: agent, backend, elevate, paths, toolsrv, json

New fs/ package:
- spec.go — single EDSL declaration of the entire namespace
- builder.go — BuildTree() validates and compiles spec into *Tree
- tree.go — configurable FileTree implementation
- fsnode.go — Dir(), Leaf(), TemplateDir(), NodeOption constructors
- agentfiles.go / sessionfiles.go / rootfiles.go / elevatefiles.go
  — handler implementations wired by the EDSL
- lifecycle.go — Create, Kill, Rename, Shutdown, InterruptAll
- persist.go, procfiles.go
- edsl.md — documentation of the EDSL
2026-08-02 15:22:17 +02:00
Ollie Agent 1fd11f62af update kde submodule: fix fence eating surrounding content 2026-08-02 11:13:31 +02:00
Ollie Agent d5fbd86def update kde submodule: StreamFsm refactor + chatblock FSM fix 2026-08-02 11:11:07 +02:00
Ollie Agent 7b7ccfe056 fix: suppress spurious [[[end]]] on reasoning blocks
Don't set streamingRole='reasoning' when processing reasoning_think
events. Since no [[[reasoning]]] header is written to the log, the
role-transition logic would emit a double [[[end]]] when the next
role (assistant) fires. Also suppress streamingRole for the tool
role's reasoning_think name check.
2026-08-02 11:10:55 +02:00
Ollie Agent e2a37cba3c fs/session: fix dangling pointer in eventRing cond initialization
The closure returned eventRing by value, copying the struct.
e.cond.L was set to &e.mu (the local variable's mutex), but
after the copy evRing.cond.L still pointed to the ORIGINAL e.mu
while evRing.mu was a different mutex. This caused
sync.Cond.Wait to unlock the wrong (already-unlocked) mutex,
panicking every consumer connecting to /eventwait.

Fixed by using *sync.Cond with sync.NewCond, making evRing a
pointer to prevent any copying of the struct with its cond.
2026-08-02 10:29:37 +02:00
Ollie Agent e254f4b942 fs/session: fix double-unlock panic in WaitEvent
sync.Cond.Wait() unlocks the mutex internally during the wait and
re-locks it on return. Using defer Unlock caused a double-unlock
panic when the GUI connected to /eventwait.

Also added context.AfterFunc to broadcast the cond on cancellation,
so blocked readers don't hang indefinitely if the connection drops.
2026-08-02 10:27:52 +02:00
Ollie Agent fd5b215268 eventwait: redesign as global /eventwait with structured delta events
BREAKING CHANGE: /session/eventwait is removed. Use /eventwait instead.

The old event system had several problems:
1. eventwait lived inside /session/ (per-session) but events are global
2. It returned the entire session index on every event, growing unboundedly
3. No structured event data — consumers couldn't tell WHAT happened

New design:
- Global /eventwait at the root of the 9P namespace
- Events are structured delta lines: '<scope> <action> <path>'
  e.g. 'S new session/foo', 'A kill session/foo/agent/bar'
- Ring buffer capped at 100 events (bounded memory)
- Offset-based blocking reads (consumers track position)
- Per-event granularity: S=new/kill/rename, A=new/kill/rename

Call sites updated:
- Session create → 'S new session/{name}'
- Agent create → 'A new session/{name}/agent/{aid}'
- Session kill → 'A kill ...' per agent + 'S kill session/{name}'
- Session rename → 'S rename session/{old} session/{new}'
- Agent rename → 'A rename session/{name}/agent/{old} ...'

KDE GUI updated to use ollie-9p read /eventwait instead of
plan9port 9p read session/eventwait.
2026-08-02 10:25:33 +02:00
Ollie Agent 8d89ac31eb fs/session: fix agent files incorrectly reported as directories in rootOpen
The rootOpen function in root.go had a bug where paths like
'session/<id>/agent/<aid>/state' were split into 4 parts by
strings.SplitN(name, "/", 4). An if len(parts) == 4 guard
incorrectly returned 'state: is a directory', turning every agent
file into a directory error.

Removed the erroneous guard so the code falls through to
NewAgentTree(...).Open(parts[3]), which correctly opens the agent's
synthetic file.
2026-08-02 10:09:07 +02:00
Ollie Agent d9c2c4c7a3 olliesrv: classify /session descendants before routeDir
The generic routeDir(path) check ran before session-specific path handling.
Because /session is a mounted tree, every descendant under /session/... was
being classified as a directory, including regular files like state,
statewait, cfg, prompt, etc.

Fix pathType() to handle /session/... first, and only fall back to the
generic routeDir check afterward.
2026-08-02 09:58:22 +02:00
Ollie Agent a01829991f olliesrv: route agent paths by FindAgent, not active agent
The 9P server path classifier still validated /session/{id}/agent/{aid}
against sess.Core.Agent().Name(), which leaked single-agent semantics into
multi-agent routing. This broke per-agent paths even when the per-agent
file trees were otherwise correct.

Fix route/type validation to resolve the requested agent with FindAgent(),
and use that specific agent for /proc PID checks.
2026-08-01 22:03:41 +02:00
Ollie Agent beadf8463a update kde submodule (statewait no longer lists agent dir) 2026-08-01 21:53:03 +02:00
Ollie Agent bf4bf87810 update kde submodule (stop tree rebuild on selection changes) 2026-08-01 21:48:06 +02:00
Ollie Agent c5285ac259 fs/session: restore normal blocking semantics for statewait
statewait is a blocking wait file with a 5-second timeout, not an
indefinite long-block stream like chat. Remove LongBlock=true so the
9P server uses the normal blocking path (WithTimeout 5s) instead of
WithCancel-only behavior.

Also remove temporary statewait debug instrumentation.
2026-08-01 21:45:39 +02:00
Ollie Agent d12cebe40e update kde submodule (selection/statewait debug logs) 2026-08-01 21:24:40 +02:00
Ollie Agent 0e04449612 update kde submodule (session click clears agent selection) 2026-08-01 21:22:34 +02:00
Ollie Agent 7d530c162b update kde submodule (only switchAgent starts per-agent streams) 2026-08-01 21:20:57 +02:00
Ollie Agent c6d959e6b0 update kde submodule (explicit agent selection for statewait/chat) 2026-08-01 21:18:21 +02:00
Ollie Agent 1a600fb9eb fs/session: add debug logging to statewait handler
Logs the agent name and base value each time statewait is
read, and whether WaitChange returned ok or !ok.
2026-08-01 21:10:55 +02:00
Ollie Agent a7359fffcf fs/session: statewait must use h.ag.WaitChange, not Core.WaitChange
Session.WaitChange delegates to a.Agent() (the active agent),
so statewait was blocking on the WRONG agent's state changes.
Agent.WaitChange uses per-agent changeCond, so it properly
blocks until this specific agent's state actually changes.
2026-08-01 21:06:26 +02:00
Ollie Agent 3f61a8929a fs/session: wire agentHelper to the correct agent, not the active one
agentHelper (serving a specific agent's 9P directory) was using
h.sess.Core.Agent() everywhere — which looks up by activeAgentName.
For multi-agent, this is wrong: reading session/{sess}/agent/{aid}/state
should return the state of that specific agent, not the active one.

Fix:
- Added ag *agent.Agent field to agentHelper
- NewAgentTree now takes *agent.Agent parameter
- All 33 references to h.sess.Core.Agent() changed to h.ag
- Updated all callers in root.go to pass the resolved agent
- Agent rename handler uses FindAgent instead of active agent
2026-08-01 21:03:34 +02:00