Restructure milestones from M1-M11 to M1-M15: - M3: Security Firewall (secret scanner, incognito mode) - M4: Router Foundation (arm registry, pools, task classifier) - M5: TUI with full 6 permission modes - M6: Full compaction (truncate + LLM summarization) - M9: Router Advanced (bandit learning, ensemble strategies) - M11: Task Learning (pattern detection, persistent tasks) Add ADR-007 through ADR-012 for security-as-core, router split, Thompson Sampling, MCP replaceability, task learning, incognito. Add risks R-010 through R-015 for router, security, feedback, task learning, ensemble quality, shell parser. Update architecture dependency graph with security, router, elf, hook, skill, mcp, plugin, tasklearn packages. Update domain model with Router, Arm, LimitPool, Firewall entities.
6.9 KiB
6.9 KiB
essential, status, last_updated, project, depends_on
| essential | status | last_updated | project | depends_on | |
|---|---|---|---|---|---|
| domain-model | complete | 2026-04-02 | gnoma |
|
Domain Model
Entity Relationships
classDiagram
class Session {
+id: string
+state: SessionState
+Send(input) error
+Events() chan Event
+Cancel()
}
class Engine {
+history: []Message
+usage: Usage
+Submit(input, callback) Turn
+SetProvider(provider)
+SetModel(model)
}
class Message {
+Role: Role
+Content: []Content
+HasToolCalls() bool
+ToolCalls() []ToolCall
+TextContent() string
}
class Content {
+Type: ContentType
+Text: string
+ToolCall: ToolCall
+ToolResult: ToolResult
+Thinking: Thinking
}
class Provider {
<<interface>>
+Stream(req) Stream
+Name() string
}
class Stream {
<<interface>>
+Next() bool
+Current() Event
+Err() error
+Close() error
}
class Tool {
<<interface>>
+Name() string
+Execute(args) Result
+IsReadOnly() bool
}
class Turn {
+Messages: []Message
+Usage: Usage
+Rounds: int
}
class Elf {
<<interface>>
+ID() string
+Send(msg) error
+Events() chan Event
+Wait() ElfResult
}
class Router {
+Select(task) RoutingDecision
+ClassifyTask(history) Task
}
class Arm {
+ID: ArmID
+Provider: Provider
+ModelName: string
+IsLocal: bool
+Pools: []LimitPool
}
class LimitPool {
+ID: string
+Kind: PoolKind
+TotalLimit: float64
+Used: float64
+Reserved: float64
+ScarcityMultiplier() float64
}
class Firewall {
+ScanOutgoing(req) req
+ScanToolResult(result) result
+Incognito: IncognitoMode
}
Session "1" --> "1" Engine : owns
Engine "1" --> "1" Router : routes through
Engine "1" --> "1" Firewall : scans through
Router "1" --> "*" Arm : selects from
Arm "1" --> "1" Provider : wraps
Arm "1" --> "*" LimitPool : draws from
Engine "1" --> "*" Tool : executes
Engine "1" --> "*" Message : history
Engine "1" --> "*" Turn : produces
Message "1" --> "*" Content : contains
Provider "1" --> "*" Stream : creates
Stream "1" --> "*" Event : yields
Session "1" --> "*" Elf : spawns
Elf "1" --> "1" Engine : owns
Glossary
| Term | Definition | Example |
|---|---|---|
| gnoma | The host application — single binary, agentic coding assistant | gnoma "list files" |
| Elf | A sub-agent (goroutine) with its own engine, history, and provider. Named after the elf owl. | Background elf exploring auth/ on Ollama |
| Session | A conversation boundary between UI and engine. Owns one engine, communicates via channels. | TUI session, CLI pipe session |
| Engine | The agentic loop orchestrator. Routes through firewall and router, executes tools, loops until done. | Engine running via router with 5 tools |
| Router | The smart routing layer. Classifies tasks, selects arms based on quality/cost/scarcity, learns from feedback. | Router picks local Qwen for boilerplate, Claude for security review |
| Arm | A provider+model pair registered in the router. Has capability metadata, pool memberships, and performance stats. | ollama/mistral-7b, anthropic/claude-opus-4 |
| LimitPool | A shared resource budget that arms draw from. Tracks usage with optimistic reservation and scarcity multipliers. | Daily cost cap of 5 EUR shared across API providers |
| Firewall | Security layer that scans outgoing requests and tool results for sensitive data. Manages incognito mode. | Redacts sk-ant-... from prompts before sending to API |
| Incognito | Mode where no data is persisted, logged, or fed back to the router. Optional local-only routing. | User toggles incognito for sensitive work |
| Provider | An LLM backend adapter. Translates gnoma types to/from SDK-specific types. | Anthropic provider, OpenAI-compat provider |
| Stream | Pull-based iterator over streaming events from a provider. Unified interface across all SDKs. | for s.Next() { e := s.Current() } |
| Event | A single streaming delta — text chunk, tool call fragment, thinking trace, or usage update. | EventTextDelta{Text: "hello"} |
| Message | A single turn in conversation history. Contains one or more Content blocks. | User text message, assistant message with tool calls |
| Content | A discriminated union within a Message — text, tool call, tool result, or thinking block. | Content{Type: ContentToolCall, ToolCall: &ToolCall{...}} |
| ToolCall | The model's request to invoke a tool, with ID, name, and JSON arguments. | {ID: "tc_1", Name: "bash", Args: {"command": "ls"}} |
| ToolResult | The output of executing a tool, correlated to a ToolCall by ID. | {ToolCallID: "tc_1", Content: "file1.go\nfile2.go"} |
| Turn | The result of a complete agentic loop — may span multiple API calls and tool executions. | Turn with 3 rounds: stream → tool → stream → tool → stream → done |
| Accumulator | Assembles a complete Response from a sequence of streaming Events. Shared across all providers. | Text fragments → complete assistant message |
| TaskType | Classification of a task for routing purposes. 10 types from boilerplate to security review. | TaskGeneration, TaskRefactor, TaskSecurityReview |
| Callback | Function the engine calls for each streaming event, enabling real-time UI updates. | func(evt stream.Event) { ch <- evt } |
| Round | A single API call within a Turn. A turn with 2 tool-use loops has 3 rounds. | Round 1: initial query. Round 2: after tool results. |
| Routing | Directing tasks to different providers based on capability, cost, or latency rules. | Complex reasoning → Claude, quick lookups → local Qwen |
| PersistentTask | A user-confirmed recurring task pattern saved for re-execution. | /task release v1.2.0 runs the saved release workflow |
Invariants
Rules that must always hold true in the domain:
- A Message always has at least one Content block
- A ToolResult always references a ToolCall.ID from the preceding assistant message
- A Session owns exactly one Engine; an Engine is owned by exactly one Session
- An Elf owns its own Engine — no shared mutable state between elfs
- The Accumulator produces exactly one Response per stream consumption
- Content.Type determines which payload field is set — exactly one is non-nil
- Thinking.Signature must round-trip unchanged through message history (Anthropic requirement)
- Tool execution only happens when StopReason == ToolUse
- Stream.Close() must be called after consumption, regardless of error state
- Provider.Stream() is the only network boundary — all tool execution is local
Changelog
- 2026-04-02: Initial version