Commit Graph

37 Commits

Author SHA1 Message Date
Ollie Agent 7dfd769c25 server: fix BlockingReadMode truncation, separate stream/blocking paths
BlockingReadMode had the same 512-byte truncation bug: it returned EOF
on offset>0 before the full content was delivered. Now caches content
on the fid like normal reads.

StreamMode is separated into its own path since it has fundamentally
different semantics (each Tread blocks for the next chunk, no caching).
2026-08-11 11:45:47 +02:00
Ollie Agent cfc526b0d7 server: cache read content on fid for offset-based paging
In 9P, clients issue multiple Treads at increasing offsets to read
files larger than the initial buffer (io.ReadAll starts at 512 bytes).
The server was calling entry.Read() on every Tread, which for Request
files cleared the result after the first call, causing truncation.

Fix: cache the Read() result on the fid (readCache). Subsequent Treads
at higher offsets serve from the cache via readSlice. The cache is
cleared on write (for rdwr files) so the next request-response cycle
gets fresh data. This mirrors how dirCache already works for directories.
2026-08-11 11:44:30 +02:00
Levi Neely 1fc051e3bf refactor: move olliesrv and toolsrv packages to cmd/*/internal/
Move server-only packages under their respective cmd directories:

olliesrv:
- agent/ -> cmd/olliesrv/internal/agent/
- backend/ -> cmd/olliesrv/internal/backend/
- bypass/ -> cmd/olliesrv/internal/bypass/
- fs/ -> cmd/olliesrv/internal/fs/
- prompts/ -> cmd/olliesrv/internal/prompts/
- session/ -> cmd/olliesrv/internal/session/

toolsrv:
- Server-only code (exec9p, fs9p, server9p, spec9p, auth9p) -> cmd/toolsrv/
- sandbox/ -> cmd/toolsrv/internal/sandbox/
- Keep shared client code (client9p, spawn, registry, meta) in toolsrv/
- Add toolsrv/types.go for shared types (ToolResult, ToolResultContent)

This enforces package boundaries - code in cmd/*/internal/ cannot be
imported by external packages, while shared code remains importable.
2026-08-10 20:16:44 +02:00
Ollie Agent 3b83b9d373 all: delete dead interfaces, kill globalRootState
- agent: delete 'state' interface (one implementation: *History). run()
  now takes *History directly.
- agent: delete 'backendNamer' interface. resolveCompactionModel takes
  backend.Backend directly.
- fs: delete Shutdown/InterruptAll/Lookup wrappers that took *Tree they
  never used. Callers (olliesrv) now call session.* directly.
- fs: delete globalRootState. sessionBindings uses ctx.Root.Data.(*RootState)
  via the HandlerCtx that was always available.
2026-08-08 14:58:47 +02:00
Levi Neely 66850e822f cleanup: remove dead code from olliesrv
- Remove unused GroupTable.Remove() method, rename Add to Populate
- Remove unused toolPrompt field from Server struct and Config
- Remove unused ModelCache type alias
- Remove unused toolsrv import
- Flatten if true {} blocks (debugging remnants)
- Convert fcallTypeName switch to map lookup
- Remove unused Tree.Unmount() method

-44 lines
2026-08-04 10:19:12 +02:00
Levi Neely 6d902ba7e9 9p: default log level to warn, not debug
The 9P logger was hardcoded to LevelDebug, causing all 9P operations
to print timing info to stdout. This blocked the server on terminal
output, hurting GUI responsiveness.

Now uses NewLogger('9p') which:
- Defaults to the sink's level (warn)
- Can be overridden with OLLIE_9P_LOG=debug

Also updates kde submodule with incremental session tree updates.
2026-08-03 19:52:07 +02:00
Levi Neely f83b1f0f89 olliesrv: fix double-fire on Request files opened ORDWR
For Request-mode files (session/new, complete, generate, route), the
write handler fires immediately during Twrite. On clunk, the server
was also flushing the (empty) writeBuf to Write(), causing the handler
to fire a second time. This created duplicate sessions.

Skip the clunk-time writeBuf flush for Request-mode files since they
already handle writes inline during Twrite.
2026-08-03 09:20:49 +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 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 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 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 951fdc8133 session: support multiple agents in data structures
- session.Session: r *agent.Agent → agents []*agent.Agent
  - Agent() returns agents[0] for backward compat
  - Added Agents(), AgentAt(), AgentCount(), AddAgent()
  - Close/SetEnv/SetSessionID iterate all agents
  - Other methods use Agent() (primary agent)
- fs/session: AgentLog *AgentLog → AgentLogs map[string]*AgentLog
  - Added AgentLog() (returns first) and SetAgentLog(id, al) methods
  - Updated create.go, persist.go, root.go, olliesrv/server.go
2026-08-01 20:29:23 +02:00
Ollie Agent b038e82931 fix: rdwr on agent/new now returns agent name
The rdwr pattern needs per-fid state to work correctly (like session/new).
- Added agentNewResult field to fid struct
- Intercept writes to /session/{id}/agent/new and store result on fid
- Intercept reads from /session/{id}/agent/new and return stored result
- Added CreateAgentFromRoot public API
- Clearer error message when cwd is missing
2026-08-01 20:02:30 +02:00
Ollie Agent 8b795ad7e6 fix: allow access to agent/new on empty sessions
pathType in server.go blocked walks to agent/new when Core is nil.
Added early return for parts[2]=="new" before the nil-guard so the
agent creation file is always accessible.

Also made agent/new rdwr return the agent name instead of just "ok".
2026-08-01 19:56:14 +02:00
Ollie Agent 94d11f9121 feat: support empty sessions (Core=nil) for multi-agent prep
Create() now registers a Session with Core=nil instead of just reserving
a name. All code paths that accessed sess.Core are nil-guarded:

- root.go: buildIndex, InterruptAll, rootOpen (proc read), rootDel (proc dismiss)
- persist.go: Kill, Rename, Shutdown, waitIdle, persistSession
- files.go: handleCtl save command
- create.go: Create() creates and registers the empty session

Also fixed pathType in server.go which panicked on empty sessions
(calling sess.Core.Agent().Name() when Core is nil). The panic was
caught by the recovery handler and returned as 'internal error'.

AGENTS.md: document cmd/ollie-remote test/build quirk
2026-08-01 19:47:02 +02:00
Ollie Agent daf535308f server: handle session/new write+read in server (rdwr pattern), bypass FileConfig 2026-08-01 19:31:39 +02:00
Ollie Agent 9acc609db5 fix: server.go pathType validates agent by Name not ID 2026-08-01 18:02:57 +02:00
Ollie Agent 937e6aef3b update: agent loop, session persistence, 9P filesystem, server, and remote sandbox 2026-08-01 15:06:49 +02:00
Ollie Agent 83304e2896 split Session into Session + AgentState; move all agent-scoped state to AgentState
Session struct now only holds: id, uname, mu, Core, SessionCtx, cancel,
proc, modelsMu/Cache/At.

New AgentState struct holds: log, logVers, ChatOffset, chatCond, plan,
prevPrompt, remote — all per-agent state.

agentHelper now takes an *AgentState alongside *Session. All agent file
operations (plan, log, chat, prevPrompt, offset, remote, cfg) access
AgentState fields instead of Session fields.

Removed startEventLog from NewSession (was writing to session log).
replayMessagesToLog takes *AgentState. Rename no longer appends to log.
ReadPlanStep stubbed out (TODO: look up correct AgentState).
server.go makeStat stubbed out AgentState log size tracking (TODO).
2026-08-01 14:17:56 +02:00
Ollie Agent 3046afd8ed fix 9P session/agent boundary enforcement
Bug: reading agent files (log, chat, state, etc.) via 9P returned the
same data regardless of which session or agent ID was specified. The
server did not validate agent IDs and had out-of-bounds slice access
on  when the path had fewer components than expected.

Fixes:
- fs/session/root.go: Added  guard before all
   checks in rootReaddir, rootStat, rootOpen.
  Added  guard before  access in rootStat.
  Added agent ID validation — returns error if the requested agent ID
  doesn't match the session's actual agent.
- cmd/olliesrv/server.go: Restructured pathType() to nest agent path
  checks inside a single
  block, preventing proc-path checks from incorrectly matching agent
  paths. Added agent ID validation (returns "" = not found).
  Added agent-path support in makeStat() for Qid version tracking on
  log/chat. Skip blocking files (chat, statewait) in stat-size
  computation to prevent hangs.
2026-08-01 13:39:14 +02:00
Levi Neely c39149ca3e 9P streaming: add chat file, rename chat→log, rewrite acme frontend
- Rename 'chat' to 'log': returns last 64KB sliding window (static read).
  Tail still works via Qid.Vers. Full history persisted to disk per turn.

- Add 'chat' streaming file: blocking read that delivers new output as
  the agent produces it. Per-fid offset tracked via waitBase. Blocks
  indefinitely between turns (no EOF). EOF only on kill/session death.

- Add LongBlock interface to File: streaming files bypass the 5s read
  timeout. Server returns content directly (ignores Tread offset for
  streaming files since waitBase tracks position).

- Add 'kill'/'.'' ctl aliases with FIXME: currently kills entire session.
  When multi-agent-per-session lands, kill should kill agent only.

- Rewrite acme frontend (cmd/Ollie) to use 9P client directly instead
  of FUSE mount. streamChat() is a blocking read loop — no polling.
  All file operations (read, write, ls) go through plan9/client.

- Add 9P streaming prototype in experiments/9p-stream demonstrating
  the blocking-read pattern for token delivery.

The key insight: 9P's request-response model gives natural streaming.
Server holds the Tread until data arrives, client blocks on Read().
No polling, no signals, no offset tracking needed.
2026-07-31 19:52:27 +02:00
Ollie Agent 96dc6e442b main.go for olliesrv 2026-07-30 21:14:26 +02:00
Ollie Agent 9b6ddb7df1 flatten: absorb submodules into single Go module
- Remove submodules: core, 9p, acme, httpgw, remote, el
- Keep submodules: kde, webui (separate build systems)
- Collapse core/ packages into root: agent/, backend/, detach/, elevate/,
  env/, log/, paths/, sandbox/, session/, skills/, tools/, toolsrv/
- Collapse 9p/ packages: fs/session/, mount/, dbus/, cmd/olliesrv/
- Move binaries: acme/ → cmd/Ollie/, httpgw/ → cmd/ollie-httpgw/,
  remote/ → cmd/ollie-remote/, 9p/cmd/ollie-9p/ → cmd/ollie-9p/
- Move data: agents/, prompts/, tools/, scripts/, skills/ → contrib/
- Move services/ → contrib/services/, experiments/ → doc/experiments/
- Delete .beads/, .claude/, .kiro
- Single go.mod at root (module ollie)
- Fix all import paths, merge fs/ + session/ packages
- Extract dbus/ and elevate/notify.go from 9p server package
- Update justfile for new paths and single-module build
2026-07-30 18:57:51 +02:00