Изменение выносит типы контента в :content-api и разделяет потоки событий.
This commit is contained in:
+19
-12
@@ -47,28 +47,35 @@ agentik
|
||||
## 3. `:proto` — интерфейсы
|
||||
|
||||
`Agent` (см. `proto/src/commonMain/.../Agent.kt`):
|
||||
- `id: String`
|
||||
- `id: String`, `info: AgentInfo`
|
||||
- read-only сторы: `journal: JournalStore`, `outbox: OutboxStore`,
|
||||
`onlineOutbox: OnlineOutbox`, `conversationStore: ConversationStore`
|
||||
- `createConversation(temp: Boolean): Conversation`
|
||||
- `suspend getConversation(id): Conversation?`
|
||||
- `suspend getConversations(offset, limit): List<Conversation>`
|
||||
- `getConversations(offset = 0): Flow<Conversation>` — cold-flow paging через suspend-версию, `PAGE_SIZE = 100`.
|
||||
- `events(after: Instant): Flow<AgentEvent>` — replay-free, бэкфилл через snapshot.
|
||||
- `deleteConversation(id): Boolean`
|
||||
- `suspend deleteConversation(id): Boolean`
|
||||
- `suspend renameConversation(id, title): Instant?`
|
||||
|
||||
`Conversation`:
|
||||
- `isSupportImageInput / Output / isTemporal: Boolean`
|
||||
- `isSupportImageInput / Output / isTemporal: Boolean`, `title: String?`
|
||||
- `updatedAt: Instant`
|
||||
- `send(content: List<Content>)` — write-only, ничего не возвращает.
|
||||
- `send(content: List<Content>, context: MessageContext? = null)` — write-only, ничего не возвращает.
|
||||
- `interrupt()` — отмена активного хода.
|
||||
- `events(after): Flow<Event>` — live, replay-free.
|
||||
- `getMessages(after, offset, limit)` + `getMessages(after): Flow<Message>` — paging.
|
||||
- `getMessages(after, offset, limit): List<Message>` — paging.
|
||||
- `rename(title)` — мутация, бампит `updatedAt`.
|
||||
- `AutoCloseable` — `close()` идемпотентен.
|
||||
|
||||
`Content = Text(body) | Image(data, mime)`.
|
||||
`Content = Text(body) | Image(data, mime)` — из `:content-api` (там же `MessageContext`/`MessageOrigin`/`TurnTokens`).
|
||||
`Message = UserMessage | AssistantMessage | ToolCall | ToolResult | Error`.
|
||||
`Event = StartReasoning | StartResponse | End | AppendText | AppendImage | ToolCall | ToolResult | Error`.
|
||||
`AgentEvent = Created(conversationId) | Deleted(id) | Renamed(id, title)`.
|
||||
|
||||
События разделены на два потока (оба в `:outbox-api`):
|
||||
- **durable** `Event` (`outbox.conversationEvents(after, id)`): `UserMessage |
|
||||
AssistantMessage | ToolCall | ToolResult | ToolFailed | Interrupted | Error |
|
||||
ConversationClosing | CompactionTriggered` — перезапрашиваются по курсору;
|
||||
- **online** `OnlineEvent` (`onlineOutbox.onlineEvents(id)`): `Working | End |
|
||||
StartReasoning | StartResponse | AppendText | AppendImage` — live-only,
|
||||
никогда не сохраняются.
|
||||
|
||||
`AgentEvent = Created(conversationId) | Deleted(id) | Renamed(id, title) | Touched(...)`.
|
||||
|
||||
Принцип: **агент — источник истины** для транскрипта и сессий. Клиент
|
||||
лишь рендерит Event-stream и кэширует историю.
|
||||
|
||||
+6
-5
@@ -63,9 +63,9 @@
|
||||
│ 3. ensureLiteConversation: │
|
||||
│ first turn → create from WM; │
|
||||
│ next turns → reuse (KV-cache) │
|
||||
│ 4. sendStreamContents → emit │
|
||||
│ StartResponse / AppendText / │
|
||||
│ End │
|
||||
│ 4. sendStreamContents → online: │
|
||||
│ StartResponse/AppendText/End; │
|
||||
│ durable: AssistantMessage │
|
||||
│ 5. audit + WM: append AssistantMessage│
|
||||
└──────────────────────────────────────┘
|
||||
│ │
|
||||
@@ -151,7 +151,8 @@ fun main() {
|
||||
| `updatedAt: Instant` | последний `send`/`rename` |
|
||||
| `send(content: List<Content>)` | **fire-and-forget**: добавить user-сообщение, запустить ход, выйти |
|
||||
| `interrupt()` | остановить текущий ход (best-effort) |
|
||||
| `events(after): Flow<Event>` | live-события хода (StartReasoning, StartResponse, AppendText, End, Interrupted, Error) |
|
||||
| `outbox.conversationEvents(after, id): Flow<Event>` | durable-события (UserMessage, AssistantMessage, ToolCall/Result, Interrupted, Error) — скурсором |
|
||||
| `onlineOutbox.onlineEvents(id): Flow<OnlineEvent>` | live-only стриминг (Working, End, StartReasoning, StartResponse, AppendText, AppendImage) |
|
||||
| `getMessages(after, offset, limit)` | страница истории |
|
||||
| `rename(title)` | переименовать |
|
||||
| `close()` | освободить ресурсы |
|
||||
@@ -173,7 +174,7 @@ fun main() {
|
||||
|
||||
Подробный контракт — в комментариях к `MessageRecord.kt` и `WorkingMemoryEntry.kt`.
|
||||
|
||||
**Ошибки хода персистятся.** Если ход провалился (LLM/движок недоступны — например, HTTP 400 от endpoint'а), `ChatConversation.failTurn` пишет терминальную запись `MessageRecord.Error` в audit и эмитит `Event.Error` + `Event.End`. Благодаря audit-записи ошибка видна не только подписчику live-SSE, но и клиенту, который делает backfill через `getMessages` (polling/переподключение): в истории будет `Message.Error(id, message, code?)`, а для этого user-сообщения не будет `AssistantMessage`. При ошибке стрима живой `LiteConversation` сбрасывается — следующий `send` пересоберёт его из `working_memory`. В working_memory `Error` не пишется (модель не должна видеть ошибки прошлых ходов).
|
||||
**Ошибки хода персистятся.** Если ход провалился (LLM/движок недоступны — например, HTTP 400 от endpoint'а), `ChatConversation.failTurn` пишет терминальную запись `MessageRecord.Error` в audit и эмитит durable `Event.Error` + онлайн `OnlineEvent.End`. Благодаря audit-записи ошибка видна не только подписчику live-SSE, но и клиенту, который делает backfill через `getMessages` (polling/переподключение): в истории будет `Message.Error(id, message, code?)`, а для этого user-сообщения не будет `AssistantMessage`. При ошибке стрима живой `LiteConversation` сбрасывается — следующий `send` пересоберёт его из `working_memory`. В working_memory `Error` не пишется (модель не должна видеть ошибки прошлых ходов).
|
||||
|
||||
### `JournalStore`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user