efcb5a2901
Vision, domain model, architecture, patterns, process flows, UML diagrams, API contracts, tech stack, constraints, milestones (M1-M11), decision log (6 ADRs), and risk register. Key decisions: single binary, pull-based streaming, Mistral as M1 reference provider, discriminated unions, multi-provider collaboration as core identity.
133 lines
5.0 KiB
Markdown
133 lines
5.0 KiB
Markdown
---
|
|
essential: domain-model
|
|
status: complete
|
|
last_updated: 2026-04-02
|
|
project: gnoma
|
|
depends_on: [vision]
|
|
---
|
|
|
|
# Domain Model
|
|
|
|
## Entity Relationships
|
|
|
|
```mermaid
|
|
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
|
|
}
|
|
|
|
Session "1" --> "1" Engine : owns
|
|
Engine "1" --> "1" Provider : uses
|
|
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 (future)
|
|
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. Manages history, streams from provider, executes tools, loops until done. | Engine running on Mistral with 5 tools |
|
|
| 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 |
|
|
| 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 |
|
|
|
|
## 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
|