ollie/fs/spec.go

382 lines
11 KiB
Go

package fs
// Filesystem specification — the single source of truth for the 9P namespace.
// Every file and directory is declared here with Doc() descriptions.
// treeSpecUID/GID are set at NewRoot() time from the current system user.
import (
"ollie/agent"
"ollie/elevate"
"ollie/fsedsl"
olog "ollie/log"
"ollie/skills"
"ollie/toolsrv"
)
// HandlerCtx carries runtime dependencies for handler functions.
// The wire step populates the correct subset per node scope.
// Handlers assert their scope by checking which pointer is non-nil.
type HandlerCtx struct {
// Root-level (always available).
Root *Tree
Log *olog.Logger
ToolReg *toolsrv.Registry
Skills *skills.Registry
Models *ModelCache
Elevate *elevate.Broker
Invalidate func() // called by root ctl "invalidate"
Shutdown func() // called by root ctl "kill"
// Session scope.
Session *SessionNode
Remove func()
Rename func(string) error
SaveFn func([]byte) error
// Agent scope.
Agent *agent.Agent
AgentLog *AgentLog
// Generic binding data — type depends on context.
Data any
}
// Type aliases from fsedsl, specialized to HandlerCtx.
type (
FsNodeDecl = fsedsl.FsNodeDecl[HandlerCtx]
NodeOption = fsedsl.NodeOption[HandlerCtx]
Binding = fsedsl.Binding[HandlerCtx]
Tree = fsedsl.Tree
File = fsedsl.File
FileTree = fsedsl.FileTree
FileConfig = fsedsl.FileConfig
SyntheticFileInfo = fsedsl.SyntheticFileInfo
SyntheticEntry = fsedsl.SyntheticEntry
TreeOption = fsedsl.TreeOption
BindTarget = fsedsl.BindTarget
)
// Structural permissions — used by Tree for mounted children and synthetic files.
const (
PermChildDir = fsedsl.PermChildDir
PermIdx = fsedsl.PermIdx
PermMkdir = fsedsl.PermMkdir
PermMkdirPrivate = fsedsl.PermMkdirPrivate
BindData = fsedsl.BindData
)
var (
DirNode = fsedsl.DirNode[HandlerCtx]
FileNode = fsedsl.FileNode[HandlerCtx]
Each = fsedsl.Each[HandlerCtx]
Doc = fsedsl.Doc[HandlerCtx]
Read = fsedsl.Read[HandlerCtx]
Write = fsedsl.Write[HandlerCtx]
Stream = fsedsl.Stream[HandlerCtx]
BlockOnce = fsedsl.BlockOnce[HandlerCtx]
Request = fsedsl.Request[HandlerCtx]
Remove = fsedsl.RemoveNode[HandlerCtx]
Stat = fsedsl.StatOverride[HandlerCtx]
UID = fsedsl.UID[HandlerCtx]
GID = fsedsl.GID[HandlerCtx]
Bind = fsedsl.Bind[HandlerCtx]
)
var treeSpecUID, treeSpecGID string
var treeSpec = DirNode("/",
FileNode("backends", 0444,
Doc("Available backend names, one per line (ollama, openai, anthropic, etc.)"),
Read(readBackends),
GID("agent"),
),
FileNode("help", 0444,
Doc("Filesystem reference (this output). Generated from spec."),
Read(readHelp),
GID("agent"),
),
FileNode("models", 0444,
Doc("Available models across all backends. Format: backend<tab>model per line."),
Read(readModels),
GID("agent"),
),
FileNode("agents", 0444,
Doc("Available agent profile names (from agents/*.json), one per line"),
Read(readAgents),
GID("agent"),
),
FileNode("tools", 0444,
Doc("Available tools and their descriptions. Format: name<spaces>description per line."),
Read(readTools),
GID("agent"),
),
FileNode("ctl", 0666,
Doc("Server control. Write: 'invalidate' (refresh caches), 'kill' (shutdown server)"),
Write(writeRootCtl),
GID("agent"),
),
FileNode("eventwait", 0444,
Doc("Blocking read for server events. Returns next event as 'topic payload'."),
Read(readEventwait),
BlockOnce(blockEventwait),
GID("agent"),
),
FileNode("complete", 0666,
Doc("Code completion. Write JSON {file,prefix,suffix,context} or plain prefix. Read returns completion."),
Request(requestComplete),
GID("agent"),
),
FileNode("generate", 0666,
Doc("One-shot LLM generation. Write JSON {prompt,...} or plain text. Read returns result."),
Request(requestGenerate),
GID("agent"),
),
FileNode("route", 0666,
Doc("Model routing. Write task description. Read returns 'backend=X model=Y' recommendation."),
Request(requestRoute),
GID("agent"),
),
FileNode("aliases", 0444,
Doc("Alias table for ID-based lookups. Format: id<tab>path per line."),
Read(readAliases),
GID("agent"),
),
DirNode("elevate",
Doc("Elevation broker for sandboxed command approval"),
GID("agent"),
FileNode("policy", 0666,
Doc("Global elevation policy. Read: current policy. Write: key=value to update."),
Read(readElevatePolicy),
Write(writeElevatePolicy),
),
Each("{reqid}", elevateBindings,
Doc("Pending elevation request. Read: request summary. Write: 'approve', 'deny', or 'persist'."),
Bind(BindData),
Read(readElevateRequest),
Write(writeElevateRequest),
),
),
DirNode("session",
Doc("Session management. Contains per-session subdirectories."),
GID("agent"),
FileNode("new", 0666,
Doc("Create session. Write key=value args (name=, cwd=, agent=, backend=, model=). Read returns session name."),
Request(requestSessionNew),
),
FileNode("idx", 0444,
Doc("Session index. Format: name<tab>state<tab>cwd<tab>backend<tab>model<tab>agentName<tab>id per line."),
Read(readSessionIdx),
),
Each("{sname}", sessionBindings,
Doc("Session directory. Addressable by name or immutable ID."),
FileNode("env", 0444,
Doc("Session environment. OLLIE_SESSION_ID plus inherited OLLIE_* variables."),
Read(readSessionEnv),
),
FileNode("paused", 0444,
Doc("Session pause status. Returns 'true' or 'false'."),
Read(readSessionPaused),
),
FileNode("connected", 0444,
Doc("RPC connection status to tool server. Returns 'true' or 'false'."),
Read(readSessionConnected),
),
FileNode("ctl", 0666,
Doc("Session control. Write: 'kill' (destroy), 'save', 'invalidate', 'pause', 'resume'."),
Read(nopRead),
Write(writeSessionCtl),
),
FileNode("name", 0666,
Doc("Session display name. Read: current name. Write: rename session."),
Read(readSessionName),
Write(writeSessionName),
),
FileNode("id", 0444,
Doc("Immutable session ID (UUID). Does not change when session is renamed."),
Read(readSessionID),
),
FileNode("elevate", 0666,
Doc("Session-scoped elevation policy. Overrides global policy for this session."),
Read(readSessionElevate),
Write(writeSessionElevate),
),
DirNode("agent",
Doc("Agent management within this session."),
FileNode("new", 0666,
Doc("Create agent. Write key=value args (name=, profile=). Read returns agent ID."),
Request(requestAgentNew),
),
Each("{aname}", agentBindings,
Doc("Agent directory. Addressable by name or immutable ID."),
FileNode("prompt", 0666,
Doc("Submit prompt to agent. Write: prompt text (triggers async execution). Special: '/invalidate' refreshes model cache."),
Write(writeAgentPrompt),
),
FileNode("prompt.prev", 0444,
Doc("Previous prompt text. Returns the last prompt submitted to this agent."),
Read(readAgentPromptPrev),
),
FileNode("fifo.in", 0222,
Doc("Queue a prompt for later execution (FIFO input). Write-only."),
Write(writeAgentFifoIn),
),
FileNode("fifo.out", 0444,
Doc("Pop queued prompt (FIFO output). Returns empty if queue is empty."),
Read(readAgentFifoOut),
),
FileNode("chat", 0444,
Doc("Full conversation history. Supports streaming reads for real-time updates."),
Stat(statAgentChat),
Read(readAgentChat),
Stream(streamAgentChat),
),
FileNode("connection", 0444,
Doc("Tool server connection status. Returns 'connected' or error."),
Read(readAgentConnection),
),
FileNode("state", 0444,
Doc("Agent state: 'idle', 'thinking', or 'calling: <tool>'."),
Read(readAgentState),
),
FileNode("statewait", 0444,
Doc("Blocking read that returns when state changes. Returns new state value."),
Read(readAgentState),
BlockOnce(blockAgentStateWait),
),
FileNode("log", 0444,
Doc("Agent activity log (last 64KB). Debug output and internal events."),
Read(readAgentLog),
),
FileNode("offset", 0444,
Doc("Byte offset after last user prompt in chat. For extracting latest response."),
Read(readAgentOffset),
),
FileNode("plan", 0666,
Doc("Session-scoped markdown checklist. Survives context compaction. Read/write."),
Read(readAgentPlan),
Write(writeAgentPlan),
),
FileNode("cfg", 0666,
Doc("Agent configuration. Read: all settings. Write: key=value to update (name=, backend=, model=)."),
Read(readAgentCfg),
Write(writeAgentCfg),
),
FileNode("ctl", 0666,
Doc("Agent control. Write: 'kill', 'stop', 'compact', 'clear', 'model <name>', 'cwd <path>'."),
Write(writeAgentCtl),
),
FileNode("cwd", 0666,
Doc("Agent working directory. Read: current path. Write: change directory."),
Read(readAgentCwd),
Write(writeAgentCwd),
),
FileNode("usage", 0444,
Doc("Token usage statistics for this agent."),
Read(readAgentUsage),
),
FileNode("cost", 0444,
Doc("Estimated API cost for this agent session."),
Read(readAgentCost),
),
FileNode("ctxsz", 0444,
Doc("Current context window size (tokens used / max)."),
Read(readAgentCtxsz),
),
FileNode("models", 0444,
Doc("Available models for this agent's backend."),
Read(readAgentModels),
),
FileNode("systemprompt", 0444,
Doc("Fully rendered system prompt for this agent."),
Read(readAgentSystemprompt),
),
FileNode("context", 0444,
Doc("Full context window as JSON lines (one message per line)."),
Read(readAgentContext),
),
FileNode("tail", 0555,
Doc("Executable shell script: tail -f chat. For convenience."),
Read(readAgentTail),
),
FileNode("tools", 0666,
Doc("Tool management. Read: loaded tools. Write: tool name to load."),
Read(readAgentTools),
Write(writeAgentTools),
),
FileNode("name", 0666,
Doc("Agent display name. Read: current name. Write: rename agent."),
Read(readAgentName),
Write(writeAgentName),
),
FileNode("id", 0444,
Doc("Immutable agent ID. Does not change when agent is renamed."),
Read(readAgentID),
),
DirNode("proc",
Doc("Detached background processes started by this agent."),
Each("{pid}", processBindings,
Doc("Process output buffer. Read: output. Remove: dismiss after completion."),
Bind(BindData),
Read(readProcess),
Remove(removeProcess),
),
),
),
),
),
),
)