468 lines
23 KiB
Markdown
468 lines
23 KiB
Markdown
# ollie Architecture
|
|
|
|
## Philosophy
|
|
|
|
ollie's design philosophy is Emacs: a small, extensible core. The Go runtime (`agent.Core`) defines what an agent *is* — an event loop, a backend connection, a message history, and a single built-in tool (`execute_code`). Everything else — orchestration, scheduling, workflows, UIs — is pushed out to the surrounding environment.
|
|
|
|
The primary integration philosophy is Plan 9's "everything is a file." `olliesrv` exposes agent state and behaviors as files in a 9P namespace. Any program that can read and write files can drive an agent: shell scripts, editors, web apps, cron, containers.
|
|
|
|
For desktop-native integration, `olliesrv` embeds a D-Bus adapter (`org.ollie.SessionManager`) alongside its 9P interface. The KDE GUI, plasmoid, and web UI connect this way. For D-Bus-only deployments, `-no9p` disables the 9P listener entirely.
|
|
|
|
Design principles:
|
|
- **Small extensible core.** The agent runtime is minimal; capabilities come from composing external scripts.
|
|
- **One built-in tool.** `execute_code` is the only tool compiled into the core. Everything else is a script.
|
|
- **Two integration paths.** 9P filesystem (canonical) and/or D-Bus (conventional). Same core, different surfaces, selectable via flags.
|
|
- **No framework lock-in.** Frontends are decoupled via whichever interface they prefer; the core doesn't know or care.
|
|
|
|
## Repository Structure
|
|
|
|
The project is a monorepo with Git submodules:
|
|
|
|
```
|
|
ollie/
|
|
├── core/ → ollie-core Go library: agent loop, backends, tools, config
|
|
├── 9p/ → ollie-9p 9P filesystem server (olliesrv)
|
|
├── kde/ → ollie-kde KDE GUI, plasmoid, Kate plugin, KRunner, tray
|
|
├── httpgw/ → ollie-httpgw HTTP-to-D-Bus gateway
|
|
├── webui/ → ollie-webui Preact SPA (browser frontend)
|
|
├── acme/ → ollie-acme Plan 9 acme editor frontend
|
|
├── el/ → ollie-el Emacs frontend (ellie.el)
|
|
├── skills/ → agent-skills Domain knowledge files
|
|
├── agents/ Agent config JSONs (default, coding, orchestrator, worker, ...)
|
|
├── prompts/ Prompt templates (markdown)
|
|
├── tools/ Tool scripts (file_read, lsp_*, memory_*, subagent_*, ...)
|
|
├── scripts/ User-facing scripts organized by namespace (s/, u/, x/)
|
|
├── sandbox/ Sandbox config YAML
|
|
└── doc/ Documentation
|
|
```
|
|
|
|
## System Overview
|
|
|
|
```
|
|
┌───────────────────────────────────────────────────────────────────────────┐
|
|
│ Frontends │
|
|
│ s/sh (terminal) │ acme │ ellie (Emacs) │ KDE GUI/plasmoid │ Web UI │
|
|
└───────┬──────────┴───┬──┴───────┬───────┴────────┬─────────┴──────┬──────┘
|
|
│ 9P │ 9P │ 9P │ D-Bus │ D-Bus
|
|
│ │ │ ▼ ▼
|
|
│ │ │ ┌──────────────────────────────────┐
|
|
│ │ │ │ org.ollie.SessionManager (D-Bus)│
|
|
│ │ │ └──────────────┬───────────────────┘
|
|
▼ ▼ ▼ │ embedded adapter
|
|
┌───────────────────────────────────────────────────────▼───────────────────┐
|
|
│ olliesrv (9P server + D-Bus adapter) │
|
|
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
|
│ │ Sessions │ │ backends │ │ models │ │
|
|
│ │ s/ │ │ (file) │ │ (file) │ │
|
|
│ └────┬─────┘ └──────────┘ └──────────┘ │
|
|
└───────┼───────────────────────────────────────────────────────────────────┘
|
|
│ per-session
|
|
▼
|
|
|
|
D-Bus-only mode: olliesrv -no9p (same interface, no filesystem listener)
|
|
┌────────────────────────────────────────────────────────────────┐
|
|
│ agent.Core (per session) │
|
|
│ ┌─────────────┐ ┌──────────────┐ ┌────────────────────────┐ │
|
|
│ │ Agent Loop │ │ Session │ │ tools.Dispatcher │ │
|
|
│ │ (loop.go) │ │ (messages, │ │ ┌──────────────────┐ │ │
|
|
│ │ │ │ task state, │ │ │ execute.Server │ │ │
|
|
│ │ stream ←────┼──┤ compaction) │ │ │ (execute_code, │ │ │
|
|
│ │ dispatch ───┼──┼───────────────┼──┤ │ call_tool, pipe)│ │ │
|
|
│ └─────────────┘ └──────────────┘ │ └────────┬─────────┘ │ │
|
|
│ └───────────┼────────────┘ │
|
|
└──────────────────────────────────────────────────┼──────────────┘
|
|
│
|
|
┌──────────────────────────────────────────┼──────┐
|
|
│ Tool Execution │ │
|
|
│ ┌─────────────┐ ┌───────────────────┐ │ │
|
|
│ │ landrun │ │ Tool Scripts │ │ │
|
|
│ │ (sandbox) │ │ (OLLIE_TOOLS_PATH)│ │ │
|
|
│ └─────────────┘ └───────────────────┘ │ │
|
|
└──────────────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────┐
|
|
│ LLM Backends │
|
|
│ Ollama│OpenAI│ │
|
|
│ Anthropic│... │
|
|
└─────────────────┘
|
|
```
|
|
|
|
## Core Library (`core/`)
|
|
|
|
The core is a Go library (`ollie/pkg/`) with no binary. Frontends import it as a dependency.
|
|
|
|
### Package Layout
|
|
|
|
| Package | Purpose |
|
|
|---|---|
|
|
| `pkg/agent/` | Agent loop, Core interface, Runtime, Session, TaskState, hooks, prompt resolution |
|
|
| `pkg/backend/` | Backend interface + implementations (Ollama, OpenAI, Anthropic) |
|
|
| `pkg/config/` | Agent config struct and JSON loader |
|
|
| `pkg/env/` | Environment variable loading (env file + shell) |
|
|
| `pkg/log/` | Structured logging |
|
|
| `pkg/paths/` | Config and data directory resolution |
|
|
| `pkg/tools/` | Server and Dispatcher interfaces |
|
|
| `pkg/tools/execute/` | The execute.Server (execute_code, call_tool, pipe) |
|
|
| `pkg/tools/execute/tool.go` | Named tool script discovery and execution |
|
|
| `internal/sandbox/` | Landlock sandbox config and command wrapper |
|
|
|
|
### Key Interfaces
|
|
|
|
**`agent.Core`** — the public API frontends drive:
|
|
```go
|
|
type Core interface {
|
|
Submit(ctx context.Context, input string)
|
|
Interrupt(cause error) bool
|
|
Queue(prompt string)
|
|
State() string // "idle" | "thinking" | "calling: <tool>"
|
|
WaitChange(ctx context.Context, field, current string) (string, bool)
|
|
Close()
|
|
// ... model/backend info, CWD, usage, cost, etc.
|
|
}
|
|
```
|
|
|
|
**`backend.Backend`** — LLM provider contract:
|
|
```go
|
|
type Backend interface {
|
|
ChatStream(ctx context.Context, messages []Message, tools []Tool, params GenerationParams) (<-chan StreamEvent, error)
|
|
Name() string
|
|
Model() string
|
|
SetModel(model string)
|
|
ContextLength(ctx context.Context) int
|
|
Models(ctx context.Context) []string
|
|
}
|
|
```
|
|
|
|
**`tools.Server`** — any tool provider:
|
|
```go
|
|
type Server interface {
|
|
ListTools() ([]ToolInfo, error)
|
|
CallTool(ctx context.Context, tool string, args json.RawMessage) (json.RawMessage, error)
|
|
}
|
|
```
|
|
|
|
**`tools.Dispatcher`** — routes tool calls to servers:
|
|
```go
|
|
type Dispatcher interface {
|
|
AddServer(name string, s Server)
|
|
ListTools() ([]ToolInfo, error)
|
|
Dispatch(ctx context.Context, server, tool string, args json.RawMessage) (json.RawMessage, error)
|
|
}
|
|
```
|
|
|
|
### Agent Loop (`pkg/agent/loop.go`)
|
|
|
|
The loop is the heart of the system. On each turn:
|
|
|
|
1. **Stream** — call `backend.ChatStream()` with messages + tool definitions
|
|
2. **Dispatch** — if the model returns tool calls, execute them via `tools.Dispatcher`
|
|
3. **Update** — append assistant message + tool results to session history
|
|
4. **Repeat** — loop until the model produces a final text response (no tool calls)
|
|
|
|
Safety mechanisms:
|
|
- **Consecutive error limits**: soft nudge at 5, hard abort at 10
|
|
- **Replan gate**: after 8 rounds without a PLAN: block, inject a replan nudge
|
|
- **Stall detection**: 5 rounds with unchanged LastAction triggers a nudge
|
|
- **Max steps**: configurable per-agent; soft exit when reached
|
|
- **Transient retry**: up to 3 retries with exponential backoff for rate limits and 5xx
|
|
- **Context overflow**: triggers automatic compaction and retry
|
|
|
|
### Session & Context Management (`pkg/agent/session.go`)
|
|
|
|
`Session` owns the message history as a flat `[]backend.Message` slice. Key behaviors:
|
|
|
|
- **TaskState**: structured JSON overlay (objective, plan_step, constraints, last_action, next_decision) injected at the top of every turn
|
|
- **Compaction**: three-zone strategy when context reaches 50% of model limit:
|
|
- **Cold zone**: structured task state summary
|
|
- **Warm zone**: one-line decision index (10 messages)
|
|
- **Hot zone**: last 8 messages verbatim
|
|
- **Result tiers**: tool results classified as Hot (verbatim), Warm (summarized on compaction), or Cold (immediately collapsed)
|
|
- **Persistence**: sessions saved as JSON to `{sessionsDir}/{id}.json`
|
|
|
|
### Hooks (`pkg/agent/hooks.go`)
|
|
|
|
Lifecycle callbacks executed as shell commands with JSON payload on stdin:
|
|
|
|
| Hook | When | Exit codes |
|
|
|---|---|---|
|
|
| `agentSpawn` | Session creation | 0=ok, 2=block |
|
|
| `preTurn` | Before each turn (deprecated) | 0=inject stdout, 2=block |
|
|
| `postTurn` | After each turn | 0=ok |
|
|
| `preTool` | Before each tool call | 0=ok, 2=block execution |
|
|
| `postTool` | After each tool call | 0=append, 2=replace result |
|
|
| `preCompact` | Before compaction | 0=ok |
|
|
| `postCompact` | After compaction | 0=ok |
|
|
| `turnError` | On backend error | 0=handled (skip retries) |
|
|
|
|
### Prompt Resolution (`pkg/agent/prompt_resolver.go`)
|
|
|
|
Agent configs declare prompts as a JSON array of shell commands. Each command is executed with `$OLLIE` and `$PWD` available; stdout is concatenated to form the system prompt. This enables dynamic, composable prompts assembled from template fragments.
|
|
|
|
## 9P Server (`9p/`)
|
|
|
|
`olliesrv` is the central daemon. It implements a 9P2000 file server that exposes the entire agent namespace:
|
|
|
|
### Namespace Layout
|
|
|
|
| Path | Contents |
|
|
|---|---|
|
|
| `s/` | Sessions — each session is a directory |
|
|
| `s/{id}/chat` | Full chat history (streamable) |
|
|
| `s/{id}/prompt` | Write to submit a prompt |
|
|
| `s/{id}/ctl` | Control commands (stop, kill, compact, model, ...) |
|
|
| `s/{id}/cfg` | Session config (key=value) |
|
|
| `s/{id}/state` | Current state (idle/thinking/calling) |
|
|
| `s/{id}/statewait` | Blocking read until state changes |
|
|
| `s/{id}/plan` | Session-scoped markdown checklist |
|
|
|
|
|
|
### Per-Agent 9P Principals
|
|
|
|
Each session gets its own FUSE mount at `/tmp/ollie-mount-{uid}` via `ollie-9p-mount -u {uid}`. The session's numeric UID is used as the 9P user principal, enabling permission enforcement:
|
|
- An agent cannot write to its own `prompt` file (mode `0466` — owner has no write)
|
|
- Cross-session access is controlled by standard Unix permission semantics
|
|
- The `$OLLIE` env var points to the session's private mount
|
|
|
|
### Permission Model (`9p/fs/perm.go`)
|
|
|
|
All permissions are declared in a single registry (`fs.Perms`). Key design:
|
|
- `prompt` is mode `0466` — group+other can write, owner (the agent) cannot
|
|
- `chat` is mode `0444` — read-only for everyone
|
|
- `ctl` is mode `0666` — anyone can send control commands
|
|
- `plan` is mode `0666` — readable by peer agents
|
|
|
|
### Session Manager (`9p/session/`)
|
|
|
|
The `Manager` handles session lifecycle:
|
|
- Creating sessions (write to `s/new`)
|
|
- Killing sessions (delete `s/{id}` or write "kill" to `s/{id}/ctl`)
|
|
- Renaming sessions
|
|
- Assigning unique numeric UIDs for 9P principals
|
|
- Wiring up `agent.Core` with backends and tool servers
|
|
- Persisting/restoring sessions across restarts
|
|
|
|
### Embedded D-Bus Adapter (`9p/dbus.go`)
|
|
|
|
`olliesrv` includes an embedded D-Bus adapter that claims `org.ollie.SessionManager` on the session bus. This provides:
|
|
- The same method/signal interface used by KDE frontends
|
|
- `SessionCreated`, `SessionKilled`, `StateChanged`, `ChatUpdated` signals
|
|
- Allows KDE GUI, plasmoid, and web UI (via `httpgw`) to interact with sessions without speaking 9P
|
|
- Lifecycle watchers that bridge session state changes to D-Bus signals
|
|
|
|
The adapter is best-effort: if no session bus is available (e.g., headless/container), `olliesrv` continues functioning as a pure 9P server.
|
|
|
|
## D-Bus Daemon (`dbus/`)
|
|
|
|
For D-Bus-only deployments (no 9P), run `olliesrv -no9p`. This disables the filesystem listener and per-session FUSE mounts while keeping the full D-Bus interface. Session persistence uses the same `~/.local/share/ollie/sessions/active/` directory.
|
|
|
|
## KDE Integration (`kde/`)
|
|
|
|
The KDE submodule provides several Qt/QML components that connect to `org.ollie.SessionManager` over D-Bus:
|
|
|
|
| Component | Path | Purpose |
|
|
|---|---|---|
|
|
| GUI | `kde/gui/` | Standalone Qt window for chat interaction |
|
|
| Plasmoid | `kde/plasmoid/` | Plasma widget with `OllieDBusClient` |
|
|
| Kate plugin | `kde/kate/` | In-editor chat pane and ghost completion |
|
|
| KRunner | `kde/krunner/` | Quick-launch sessions from KRunner |
|
|
| System tray | `kde/tray/` | Tray icon with session status |
|
|
|
|
All KDE components use `QDBusServiceWatcher` to handle daemon restarts gracefully — recreating the `QDBusInterface` when the service reappears and refreshing the session list.
|
|
|
|
## Tool System
|
|
|
|
### Architecture
|
|
|
|
The tool system has exactly one built-in server (`execute.Server`) that exposes three executors:
|
|
|
|
| Executor | Purpose |
|
|
|---|---|
|
|
| `execute_code` | Run inline code steps (bash, python3, perl, lua, ...) |
|
|
| `call_tool` | Run named scripts from `OLLIE_TOOLS_PATH` |
|
|
| `pipe` | Sequential pipeline chaining stdout→stdin across steps |
|
|
|
|
All other capabilities are **tool scripts** — executable files discovered from `OLLIE_TOOLS_PATH` (default: `~/.config/ollie/tools`).
|
|
|
|
### Tool Scripts
|
|
|
|
Tool scripts are plain executables with a structured header comment declaring their name, description, parameters, and parallelism annotation:
|
|
|
|
- `file_read`, `file_write`, `file_edit`, `file_glob`, `file_grep` — filesystem I/O
|
|
- `lsp_definition`, `lsp_references`, `lsp_rename`, `lsp_symbols`, `lsp_diagnostics`, `lsp_hover`, `lsp_completion` — LSP bridge (Python)
|
|
- `memory_remember`, `memory_recall` — persistent memory
|
|
- `reasoning_think` — scratchpad for externalized reasoning
|
|
- `subagent_spawn`, `subagent_generate` — sub-agent lifecycle
|
|
- `route` — orchestrator dispatch
|
|
|
|
Scripts annotated `ollie:parallel read` can be fanned out concurrently by the agent loop.
|
|
|
|
### Sandboxing (`internal/sandbox/`)
|
|
|
|
Every code execution step is wrapped with [landrun](https://github.com/landlock-lsm/landrun) (Landlock LSM). Configuration is YAML-based:
|
|
|
|
```yaml
|
|
general:
|
|
best_effort: true
|
|
filesystem:
|
|
ro: ["/usr", "/lib", "/etc", "{HOME}/.config/ollie"]
|
|
rox: ["/usr/bin", "/usr/local/bin"]
|
|
rw: ["{CWD}", "{TMPDIR}"]
|
|
network:
|
|
enabled: true
|
|
unrestricted: true
|
|
```
|
|
|
|
Template variables (`{CWD}`, `{HOME}`, `{XDG_*}`, `{OLLIE_*}`) are expanded at runtime. **landrun is mandatory** — there is no unsandboxed fallback. Steps marked `elevated: true` bypass the sandbox via the elevation backend (`x/elevate`).
|
|
|
|
## LLM Backends (`pkg/backend/`)
|
|
|
|
All backends implement the streaming-only `Backend` interface:
|
|
|
|
| Backend | Wire Format | Notes |
|
|
|---|---|---|
|
|
| `OllamaBackend` | `/api/chat` | Local models, context length from `/api/show` |
|
|
| `OpenAIBackend` | `/v1/chat/completions` | OpenAI, OpenRouter, any compatible API |
|
|
| `AnthropicBackend` | `/v1/messages` | Extended thinking, prompt caching |
|
|
|
|
Shared infrastructure:
|
|
- `streamRequest()` — common HTTP streaming with error classification
|
|
- `RateLimitError`, `TransientError`, `ContextOverflowError`, `ToolUnsupportedError` — typed errors for loop control
|
|
- `GenerationParams` — unified sampling parameters across all backends
|
|
|
|
## Multi-Agent System
|
|
|
|
Multi-agent coordination is built entirely on the filesystem primitive:
|
|
|
|
### Mechanism
|
|
|
|
1. **Spawn**: write session spec to `s/new` → new session directory appears
|
|
2. **Prompt**: write task to `s/{id}/prompt`
|
|
3. **Observe**: read `s/{id}/state` or block on `s/{id}/statewait`
|
|
4. **Result**: child writes to `s/{parent_id}/prompt` when done
|
|
|
|
### Delegation Tools
|
|
|
|
| Tool | Semantics |
|
|
|---|---|
|
|
| `route` | Orchestrator dispatch; selects backend+model for a task |
|
|
| `subagent_spawn` | Raw session creation with full control |
|
|
| `subagent_generate` | JIT agent identity generation |
|
|
|
|
### Callback Protocol
|
|
|
|
```
|
|
[from={session_id}]
|
|
STATUS: done|error
|
|
SUMMARY: <result>
|
|
ARTIFACTS: <paths>
|
|
```
|
|
|
|
The parent never actively waits — results arrive as queued prompts.
|
|
|
|
## HTTP Gateway (`httpgw/`)
|
|
|
|
`ollie-httpgw` bridges HTTP clients (like the web UI) to the D-Bus session manager. It translates REST-style requests into `org.ollie.SessionManager` method calls and streams `ChatUpdated` signals back as Server-Sent Events.
|
|
|
|
This is the secondary integration path for non-LLM consumers (web UIs, dashboards, external tools) that want a conventional HTTP API without speaking 9P or D-Bus directly.
|
|
|
|
## Frontends
|
|
|
|
Frontends connect via either 9P (filesystem) or D-Bus, depending on preference:
|
|
|
|
| Frontend | Technology | Connection |
|
|
|---|---|---|
|
|
| `s/sh` | Terminal (readline) | 9P |
|
|
| `ollie-acme` | Plan 9 acme editor | 9P |
|
|
| `ellie` | Emacs Lisp | 9P |
|
|
| KDE GUI | Qt/QML standalone app | D-Bus |
|
|
| Plasmoid | Plasma widget | D-Bus |
|
|
| Kate plugin | KTextEditor plugin | D-Bus |
|
|
| KRunner | Runner plugin | D-Bus |
|
|
| Web UI | Preact SPA | D-Bus (via `httpgw`) |
|
|
|
|
Frontends are fully decoupled — the core doesn't know which interface they use.
|
|
|
|
## Script Namespaces
|
|
|
|
User-facing scripts installed to `~/.config/ollie/scripts/{ns}/`:
|
|
|
|
| Namespace | Purpose | Key Scripts |
|
|
|---|---|---|
|
|
| `s/` | Session management | `sh` (interactive), `b` (one-shot), `bfg` (blocking), `bbg` (background) |
|
|
| `u/` | Utility compositions | `complete` (code completion), `optimize` (prompt optimization), `cascade`, `escalate` |
|
|
| `x/` | System plugins | `elevate` (privilege escalation), `prime` (prompt template renderer), `freeloader` |
|
|
|
|
## Agent Configuration
|
|
|
|
Agent configs are JSON files in `~/.config/ollie/agents/`:
|
|
|
|
```json
|
|
{
|
|
"prompt": [
|
|
"$OLLIE_CFG_PATH/scripts/x/prime SYSTEM_PROMPT",
|
|
"$OLLIE_CFG_PATH/scripts/x/prime session",
|
|
"$OLLIE_CFG_PATH/scripts/x/prime agent-coding",
|
|
"$OLLIE_CFG_PATH/scripts/x/prime tools-file",
|
|
"$OLLIE_CFG_PATH/scripts/x/prime tools-reasoning"
|
|
],
|
|
"hooks": {
|
|
"agentSpawn": [],
|
|
"turnError": ["$OLLIE_CFG_PATH/scripts/x/freeloader $OLLIE_SESSION_ID"]
|
|
},
|
|
"maxTokens": 8192,
|
|
"temperature": 0.7,
|
|
"maxSteps": 50
|
|
}
|
|
```
|
|
|
|
The `prompt` array is the key innovation: each element is a shell command whose stdout is concatenated to form the system prompt. This makes prompts composable, dynamic, and environment-aware.
|
|
|
|
## Data Flow
|
|
|
|
```
|
|
User input (via any frontend)
|
|
│
|
|
▼ write to s/{id}/prompt
|
|
olliesrv
|
|
│
|
|
▼ agent.Core.Submit()
|
|
agent loop (loop.go)
|
|
│
|
|
├── backend.ChatStream() → LLM API call (streaming)
|
|
│
|
|
├── tools.Dispatcher.Dispatch() → execute.Server
|
|
│ │
|
|
│ ├── {code} steps → landrun sandbox → subprocess
|
|
│ └── {tool} steps → landrun sandbox → tool script
|
|
│
|
|
├── session.update() → append to message history
|
|
│
|
|
└── loop (until no tool calls)
|
|
│
|
|
▼ event bus → session log
|
|
Frontend reads s/{id}/chat (streaming)
|
|
```
|
|
|
|
## Extension Points
|
|
|
|
| What | How |
|
|
|---|---|
|
|
| Add an LLM backend | Implement `backend.Backend` |
|
|
| Add a tool | Drop an executable into `OLLIE_TOOLS_PATH` |
|
|
| Add a tool server | Implement `tools.Server`, register in `NewDispatcherFunc` |
|
|
| Add a frontend | Connect via 9P (filesystem) or D-Bus (method calls) |
|
|
| Add a skill | Drop a markdown file into `skills/` |
|
|
| Add an agent persona | Create a JSON config in `agents/` |
|
|
| Customize sandbox | Create a YAML in `~/.config/ollie/sandbox/` |
|
|
|
|
## Key Design Decisions
|
|
|
|
1. **Two integration surfaces** — 9P filesystem (canonical, Unix philosophy) and D-Bus (conventional, for GUI/web consumers). Both are thin adapters over the same core.
|
|
2. **Single built-in tool** — `execute_code` is the only compiled-in capability. This keeps the core small and makes all other tools equal citizens.
|
|
3. **Mandatory sandboxing** — no unsandboxed fallback. Security is not optional.
|
|
4. **Streaming-only backends** — simplifies the interface; blocking APIs wrap as single-event streams.
|
|
5. **Fire-and-forget sub-agents** — no active waiting, no polling. Results arrive as filesystem writes.
|
|
6. **Prompt-as-code** — system prompts are assembled by running shell commands, not by concatenating static strings.
|
|
7. **Per-agent principals** — 9P permission enforcement prevents self-prompting loops and enforces access boundaries without application-level checks.
|