docs(diagrams): agent architecture overview with pre-rendered SVG
ci / JVM build + tests (push) Failing after 1m57s
ci / JVM build + tests (push) Failing after 1m57s
PlantUML diagrams for future agent architecture (Android, multi-user chat, sub-agents, A2A): - 01-module-layers.md — целевая модульная структура - 02-agent-composition.md — AgentBuilder DSL + MemoryBackend.exposesTools() - 03-multi-user-chat.md — mention-detection sequence - 04-sub-agents.md — spawnChild + Flow<SubAgentEvent> + A2A - 05-android-stack.md — что меняется на Android vs Standalone Каждый .md включает пред-рендеренный SVG (показывается во всех markdown viewers без PlantUML plugin) + PlantUML source в code block (для редактирования). SVG нужен потому что PlantUML требует Graphviz dot для рендеринга — без него IntelliJ/VSCode выдают ошибку. Регенерация SVG после правки PlantUML-source: docker run --rm -v "$PWD:/work" plantuml/plantuml -tsvg /work/docs/diagrams/*.md
This commit is contained in:
@@ -0,0 +1,158 @@
|
|||||||
|
# 01 — Слои модулей (целевое состояние)
|
||||||
|
|
||||||
|
Целевая модульная структура agentik. Снизу вверх:
|
||||||
|
**приложения → runtime → домен → абстракции → платформенные impl**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
PlantUML source (для редактирования; требует Graphviz `dot` для рендеринга):
|
||||||
|
|
||||||
|
```plantuml
|
||||||
|
@startuml agentik-module-layers
|
||||||
|
skinparam componentStyle rectangle
|
||||||
|
skinparam ranksep 60
|
||||||
|
skinparam nodesep 30
|
||||||
|
skinparam packageStyle rectangle
|
||||||
|
|
||||||
|
title agentik — слои модулей (целевое состояние)
|
||||||
|
|
||||||
|
' --- Applications: entry points (thin wrappers) ---
|
||||||
|
package "Applications\n(entry points, тонкие)" {
|
||||||
|
[Standalone\nHTTP+AG-UI+A2A] as Standalone
|
||||||
|
[AgentikCli\nREPL] as Cli
|
||||||
|
[AgentikAndroid\nCompose UI] as Android
|
||||||
|
}
|
||||||
|
|
||||||
|
' --- Agent runtime ---
|
||||||
|
package "Agent Runtime\n(композиция, lifecycle)" {
|
||||||
|
[AgentCore\nBaseAgent] as AgentCore
|
||||||
|
[AgentBuilder\nDSL] as Builder
|
||||||
|
}
|
||||||
|
|
||||||
|
' --- Background work ---
|
||||||
|
package "Background Work\n(event-driven triggers)" {
|
||||||
|
[BackgroundEvents\nbus + events] as Ev
|
||||||
|
[BackgroundScheduler\npolicy] as Sched
|
||||||
|
}
|
||||||
|
|
||||||
|
' --- Domain logic (generic, переиспользуется) ---
|
||||||
|
package "Domain Logic\n(generic tools)" {
|
||||||
|
[LlmTools\nReflector/Reviewer/Miner] as LlmT
|
||||||
|
[McpBridge\nMCP-SDK → LiteTool] as Mcp
|
||||||
|
[Skills\nparse + store] as Skills
|
||||||
|
}
|
||||||
|
|
||||||
|
' --- Storage abstractions + impls ---
|
||||||
|
package "Storage\n(abstractions)" as StoragePkg {
|
||||||
|
[StorageCore\ninterfaces] as StorageCore
|
||||||
|
}
|
||||||
|
|
||||||
|
package "Storage\n(JVM impls)" {
|
||||||
|
[StorageSqlite\nJDBC] as StorageSql
|
||||||
|
[StorageInmemory\ntests] as StorageInmem
|
||||||
|
}
|
||||||
|
|
||||||
|
package "Storage\n(Android impl)" {
|
||||||
|
[StorageSqliteAndroid\nRoom/sqlite] as StorageSqlA
|
||||||
|
}
|
||||||
|
|
||||||
|
' --- Memory backends ---
|
||||||
|
package "Memory\n(abstractions)" {
|
||||||
|
[MemoryApi\nMemorySystem/MemoryTools] as MemApi
|
||||||
|
}
|
||||||
|
|
||||||
|
package "Memory\n(impls)" {
|
||||||
|
[MemoryMd\nHermes §-files] as MemMd
|
||||||
|
[MemoryVector\nJVector+JVM] as MemVec
|
||||||
|
[MemoryVectorAndroid\nONNX+ANN] as MemVecA
|
||||||
|
}
|
||||||
|
|
||||||
|
' --- LLM backends ---
|
||||||
|
package "LLM\n(abstractions)" {
|
||||||
|
[LitertApi\nLiteLlm контракт] as Litert
|
||||||
|
}
|
||||||
|
|
||||||
|
package "LLM\n(impls)" {
|
||||||
|
[LitertOpenai\nHTTP] as LitertO
|
||||||
|
[LitertGoogle\nLiteRT JVM] as LitertG
|
||||||
|
[LitertAndroid\nLiteRT Android] as LitertA
|
||||||
|
}
|
||||||
|
|
||||||
|
' --- Inter-app protocol ---
|
||||||
|
package "Inter-app" {
|
||||||
|
[Proto\nAgent/Conversation] as Proto
|
||||||
|
[A2AServer] as A2A
|
||||||
|
}
|
||||||
|
|
||||||
|
' --- Зависимости (приложения → runtime → домен → абстракции → платформенные импл) ---
|
||||||
|
Standalone ..> Builder
|
||||||
|
Cli ..> Builder
|
||||||
|
Android ..> Builder
|
||||||
|
|
||||||
|
Builder ..> AgentCore
|
||||||
|
AgentCore ..> Proto
|
||||||
|
AgentCore ..> StorageCore
|
||||||
|
AgentCore ..> MemApi
|
||||||
|
AgentCore ..> Litert
|
||||||
|
AgentCore ..> Mcp
|
||||||
|
AgentCore ..> Skills
|
||||||
|
|
||||||
|
Sched ..> Ev
|
||||||
|
AgentCore ..> Sched
|
||||||
|
AgentCore ..> Ev
|
||||||
|
|
||||||
|
Mcp ..> Litert
|
||||||
|
LlmT ..> Litert
|
||||||
|
|
||||||
|
MemMd ..> MemApi
|
||||||
|
MemVec ..> MemApi
|
||||||
|
MemVecA ..> MemApi
|
||||||
|
|
||||||
|
StorageSql ..> StorageCore
|
||||||
|
StorageInmem ..> StorageCore
|
||||||
|
StorageSqlA ..> StorageCore
|
||||||
|
|
||||||
|
LitertO ..> Litert
|
||||||
|
LitertG ..> Litert
|
||||||
|
LitertA ..> Litert
|
||||||
|
|
||||||
|
Standalone ..> A2A
|
||||||
|
Standalone ..> LitertO
|
||||||
|
Standalone ..> LitertG
|
||||||
|
Standalone ..> StorageSql
|
||||||
|
Standalone ..> MemMd
|
||||||
|
Standalone ..> MemVec
|
||||||
|
Standalone ..> Mcp
|
||||||
|
|
||||||
|
Android ..> LitertA
|
||||||
|
Android ..> StorageSqlA
|
||||||
|
Android ..> MemMd
|
||||||
|
Android ..> MemVecA
|
||||||
|
|
||||||
|
@enduml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Что показывает
|
||||||
|
|
||||||
|
- **Applications** — три точки входа: web-сервер, CLI REPL, Android-приложение. Каждое тонкое, не содержит бизнес-логики.
|
||||||
|
- **Agent Runtime** — `BaseAgent` + `AgentBuilder` DSL. Вся композиция и lifecycle.
|
||||||
|
- **Background Work** — `BackgroundEvents` (event-bus) + `BackgroundScheduler` (policy подписки). Event-driven, не interval-polling.
|
||||||
|
- **Domain Logic** — generic переиспользуемые модули (`:llm-tools`, `:mcp-bridge`, `:skills`).
|
||||||
|
- **Storage / Memory / LLM** — каждая с абстракцией и одним или несколькими impl (JVM-only или Android-only).
|
||||||
|
- **Inter-app** — `:proto` контракты + `:a2a-server` для межагентного общения.
|
||||||
|
|
||||||
|
## Текущее состояние vs целевое
|
||||||
|
|
||||||
|
✅ Уже сделано (в этом цикле правок):
|
||||||
|
- `:llm-tools` extracted
|
||||||
|
- `:mcp-bridge` extracted
|
||||||
|
- `BackgroundScheduler` стал event-driven
|
||||||
|
- `ConversationLoop` стал отдельным компонентом (typealias `ChatConversation`)
|
||||||
|
|
||||||
|
⏳ Не сделано:
|
||||||
|
- `:agent-core` (выделить `BaseAgent` + builder в отдельный KMP-модуль)
|
||||||
|
- `:background-events` (выделить events + scheduler — пока в `:standalone`)
|
||||||
|
- `:storage-sqlite-android`
|
||||||
|
- `:memory-vector-android`
|
||||||
|
- `:litert-android`
|
||||||
|
- `:agentik-android` (само приложение)
|
||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 38 KiB |
@@ -0,0 +1,127 @@
|
|||||||
|
# 02 — Agent Builder: композиция (целевое API)
|
||||||
|
|
||||||
|
Как `AgentBuilder` собирает `BaseAgent` из компонентов. **Memory backend сам объявляет свои tools** — builder их авто-мержит. BackgroundScheduler подписан на события, не interval-poll.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
PlantUML source (для редактирования; требует Graphviz `dot` для рендеринга):
|
||||||
|
|
||||||
|
```plantuml
|
||||||
|
@startuml agent-composition
|
||||||
|
skinparam componentStyle rectangle
|
||||||
|
|
||||||
|
title Agent Builder — композиция (целевое API)
|
||||||
|
|
||||||
|
' --- Builder ---
|
||||||
|
rectangle "AgentBuilder" as Builder {
|
||||||
|
rectangle "llm: LiteLlm (обязательно)" as Llm
|
||||||
|
rectangle "storage: StorageBundle (обязательно)" as Storage
|
||||||
|
rectangle "memory: MemorySystem (обязательно)" as Mem
|
||||||
|
rectangle "soul: SoulProvider (default NoopSoul)" as Soul
|
||||||
|
rectangle "tools: List<NamedTool> (авто-сборка из backends)" as Tools
|
||||||
|
rectangle "background: BackgroundConfig (default EmptyBg)" as Bg
|
||||||
|
rectangle "toolset: List<ToolsetContribution> (default empty)" as Ts
|
||||||
|
}
|
||||||
|
|
||||||
|
' --- Backends with their tool side-effects ---
|
||||||
|
rectangle "MemoryMd" as MdMem {
|
||||||
|
interface "MemorySystem" as MemSys
|
||||||
|
interface "List<NamedTool>" as MdTools
|
||||||
|
note right
|
||||||
|
MemoryMd.exposesTools() →
|
||||||
|
memory_save / memory_read /
|
||||||
|
memory_list / memory_delete
|
||||||
|
end note
|
||||||
|
}
|
||||||
|
|
||||||
|
rectangle "McpRegistry" as McpReg {
|
||||||
|
interface "List<NamedTool>" as McpTools
|
||||||
|
note right
|
||||||
|
McpRegistry.namedTools →
|
||||||
|
server__tool1, server__tool2,
|
||||||
|
...
|
||||||
|
end note
|
||||||
|
}
|
||||||
|
|
||||||
|
rectangle "BackgroundEvents" as Events {
|
||||||
|
interface "MutableSharedFlow<CompactionEvent|ToolCallEvent|LifecycleEvent>" as Flow
|
||||||
|
note right
|
||||||
|
Эмитится из:
|
||||||
|
- CompactionCoordinator
|
||||||
|
- ToolDispatcher
|
||||||
|
- ConversationLoop.close()
|
||||||
|
end note
|
||||||
|
}
|
||||||
|
|
||||||
|
rectangle "BackgroundScheduler" as Sched {
|
||||||
|
interface "policy: trigger + debounce" as Policy
|
||||||
|
note right
|
||||||
|
Подписан на Events.
|
||||||
|
НИКАКОГО interval-polling.
|
||||||
|
end note
|
||||||
|
}
|
||||||
|
|
||||||
|
' --- Получаемый Agent ---
|
||||||
|
rectangle "BaseAgent\n(impl: ConversationLoop)" as Agent {
|
||||||
|
rectangle "send / interrupt / events" as API
|
||||||
|
rectangle "BackgroundScheduler\nподписка" as Sub
|
||||||
|
}
|
||||||
|
|
||||||
|
' --- Стрелки зависимостей ---
|
||||||
|
Builder --> Llm
|
||||||
|
Builder --> Storage
|
||||||
|
Builder --> Mem
|
||||||
|
Builder --> Soul
|
||||||
|
Builder --> Tools
|
||||||
|
Builder --> Bg
|
||||||
|
Builder --> Ts
|
||||||
|
|
||||||
|
MdMem --> Mem : implements
|
||||||
|
MdMem --> MdTools : exposes
|
||||||
|
|
||||||
|
McpReg --> McpTools : exposes
|
||||||
|
|
||||||
|
Bg --> Events : subscribes-to
|
||||||
|
Bg --> Sched : holds
|
||||||
|
|
||||||
|
Tools <-- MdTools : auto-merge
|
||||||
|
Tools <-- McpTools : auto-merge
|
||||||
|
|
||||||
|
Builder --> Agent : build()
|
||||||
|
Agent --> API
|
||||||
|
Agent --> Sub
|
||||||
|
|
||||||
|
@enduml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Целевой Kotlin DSL
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val agent = agentBuilder {
|
||||||
|
// Обязательные
|
||||||
|
llm(OpenAiLlm.fromEnv()) // или LitertAndroid.onDevice(context)
|
||||||
|
storage(SqliteStorage(path)) // или SqliteStorage.android(context)
|
||||||
|
memory(MemoryMd(root = "~/memory")) // или MemoryVector(embedding = HttpEmbedding(...))
|
||||||
|
|
||||||
|
// Опциональные
|
||||||
|
soul(FileSoul("~/SOUL.md")) // или HttpSoul(url), NoopSoul()
|
||||||
|
background {
|
||||||
|
// triggers: OnClosing (reflection+mining), OnCompaction(minTurns=10, mining=true)
|
||||||
|
// event-driven, не interval
|
||||||
|
}
|
||||||
|
tools {
|
||||||
|
// memoryMd.exposesTools() + mcpRegistry.namedTools авто-подцепляются
|
||||||
|
+FileReadTool(root = "/data")
|
||||||
|
}
|
||||||
|
toolset {
|
||||||
|
+MemoryToolsToolset(memoryMd)
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ключевые решения
|
||||||
|
|
||||||
|
- **`MemoryBackend.exposesTools()`** — backend декларирует свои tools. Не «подставить любой backend», а «backend сообщает что он умеет». Это убирает coupling «какие tools совместимы с какими backends».
|
||||||
|
- **Builder требует только `llm + storage + memory`** как обязательные. Всё остальное — опционально с разумными default'ами (`NoopSoul`, `EmptyBackground`, `empty toolset`).
|
||||||
|
- **`BaseAgent`** — реализация `ConversationLoop` через builder. Конструктор принимает все нужные компоненты. **Один и тот же `BaseAgent` в `:standalone`, `:agentik-cli`, `:agentik-android`** — отличается только wiring через builder.
|
||||||
|
- **BackgroundScheduler подписан на `BackgroundEvents`** — это даёт event-driven по умолчанию. `OnEvery(n)` interval-режим — опциональный fallback (не default).
|
||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 24 KiB |
@@ -0,0 +1,129 @@
|
|||||||
|
# 03 — Multi-user chat с mention-detection
|
||||||
|
|
||||||
|
Точка 1 из планов. Один `BaseAgent` обслуживает N пользователей. Отвечает только когда addressed (mention или admin-команда).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
PlantUML source (для редактирования; требует Graphviz `dot` для рендеринга):
|
||||||
|
|
||||||
|
```plantuml
|
||||||
|
@startuml multi-user-chat
|
||||||
|
skinparam componentStyle rectangle
|
||||||
|
skinparam participantPadding 15
|
||||||
|
skinparam boxPadding 10
|
||||||
|
|
||||||
|
title Multi-user chat с mention-detection (точка 1 из планов)
|
||||||
|
|
||||||
|
' --- Участники ---
|
||||||
|
actor "User A" as UA
|
||||||
|
actor "User B" as UB
|
||||||
|
actor "User C\n(админ)" as UC
|
||||||
|
participant "Telegram /\nSlack /\nMatrix" as Channel
|
||||||
|
participant "AgentRuntime\n(BaseAgent)" as Runtime
|
||||||
|
participant "MentionDetector" as Detector
|
||||||
|
participant "SoulProvider" as Soul
|
||||||
|
participant "MemorySystem\n(MdMemory)" as Memory
|
||||||
|
participant "LlmBackend\n(LiteLlm)" as Llm
|
||||||
|
|
||||||
|
' --- Сценарий ---
|
||||||
|
UA -> Channel : "@bot, что нового?"
|
||||||
|
UB -> Channel : "люблю котов"
|
||||||
|
UC -> Channel : "/bot status"
|
||||||
|
Channel -> Runtime : событие чата
|
||||||
|
|
||||||
|
' --- Внутри Runtime ---
|
||||||
|
Runtime -> Detector : isMentioned(message, botName)
|
||||||
|
|
||||||
|
note right of Detector
|
||||||
|
variants:
|
||||||
|
- SimpleMentionDetector (regex: @bot)
|
||||||
|
- LlmMentionDetector (mini-classifier)
|
||||||
|
- AdminCommandDetector (/command)
|
||||||
|
end note
|
||||||
|
|
||||||
|
Detector --> Runtime : MatchResult{isMentioned, isCommand}
|
||||||
|
|
||||||
|
alt isMentioned или isCommand
|
||||||
|
Runtime -> Soul : read()
|
||||||
|
Runtime -> Memory : prefetch(query, topK)
|
||||||
|
Runtime -> Llm : send(system + history + memory + user)
|
||||||
|
Llm --> Runtime : response + tool_calls
|
||||||
|
Runtime -> Memory : save(decision)
|
||||||
|
Runtime --> Channel : ответ в нужный канал/thread
|
||||||
|
else NOT mentioned и NOT command
|
||||||
|
Runtime -> Runtime : drop (если не админ)
|
||||||
|
note right
|
||||||
|
Не отвечаем, но возможно:
|
||||||
|
- запоминаем факт (memory-only update)
|
||||||
|
- summary на long conversation
|
||||||
|
end note
|
||||||
|
end
|
||||||
|
|
||||||
|
@enduml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ключевые модули (что нужно будет добавить)
|
||||||
|
|
||||||
|
### `MentionDetector` interface
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
interface MentionDetector {
|
||||||
|
data class Result(
|
||||||
|
val isMentioned: Boolean,
|
||||||
|
val isAdminCommand: Boolean,
|
||||||
|
val isPrivateMessage: Boolean, // DM — всегда отвечаем
|
||||||
|
)
|
||||||
|
|
||||||
|
fun detect(message: ChatMessage, botName: String): Result
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Имплементации:
|
||||||
|
- `SimpleMentionDetector` — regex `@bot`, `/command` (дешёво, latency ~0)
|
||||||
|
- `LlmMentionDetector` — маленькая классификация через тот же LLM (точнее, но +1 LLM-вызов на каждое сообщение)
|
||||||
|
- `HybridMentionDetector` — fast regex → fallback на LLM только если ambiguous
|
||||||
|
|
||||||
|
### `ChatAdapter` interface
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
interface ChatAdapter {
|
||||||
|
val channel: String // "telegram" / "slack" / "matrix"
|
||||||
|
suspend fun listen(onMessage: (ChatMessage) -> Unit): Job
|
||||||
|
suspend fun reply(messageId: String, text: String, threadId: String? = null)
|
||||||
|
suspend fun isAdmin(userId: String): Boolean
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Имплементации per platform. Каждая адаптирует формат platform → `ChatMessage`.
|
||||||
|
|
||||||
|
### Конфигурация builder'а
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
agentBuilder {
|
||||||
|
llm(...)
|
||||||
|
storage(...)
|
||||||
|
memory(...)
|
||||||
|
soul(...)
|
||||||
|
background { ... }
|
||||||
|
chat {
|
||||||
|
mentionDetector = HybridMentionDetector(regex = "@bot|@Agent", llmClassifier = false)
|
||||||
|
chatAdapter = TelegramChatAdapter(token = "...")
|
||||||
|
// На каждое сообщение:
|
||||||
|
// 1. mentionDetector.detect()
|
||||||
|
// 2. если isMentioned || isAdminCommand || isPrivate → process
|
||||||
|
// 3. иначе — опционально memory-only save (тихий режим)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Что это даёт
|
||||||
|
|
||||||
|
- Один `BaseAgent` обслуживает чат целиком (один LLM, одна память — общий контекст команды)
|
||||||
|
- `@bot` — explicit invocation, не «agent отвечает на всё подряд»
|
||||||
|
- `/bot status` / `/bot clear-memory` — admin-команды (отдельный канал, без LLM)
|
||||||
|
- DM — всегда отвечает (это личное обращение)
|
||||||
|
- В групповом чате без mention — agent может **молча учить** (memory update без ответа). Полезно для «запомнил что Вася любит котов».
|
||||||
|
|
||||||
|
## Текущее состояние vs целевое
|
||||||
|
|
||||||
|
⏳ Ничего из этого нет. Сейчас `:standalone` — это HTTP API, к которому подключаются clients. Для multi-user chat нужен новый `:chat-adapter-telegram` (или -slack / -matrix) модуль + `MentionDetector` interface.
|
||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,119 @@
|
|||||||
|
# 04 — Sub-agents + A2A между агентами
|
||||||
|
|
||||||
|
Точки 2 и 3 из планов. Orchestrator-агент spawn'ит sub-агентов с изолированным контекстом. Независимые агенты общаются через A2A.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
PlantUML source (для редактирования; требует Graphviz `dot` для рендеринга):
|
||||||
|
|
||||||
|
```plantuml
|
||||||
|
@startuml sub-agents-and-a2a
|
||||||
|
skinparam componentStyle rectangle
|
||||||
|
|
||||||
|
title Sub-agents + A2A между агентами (точка 2+3 из планов)
|
||||||
|
|
||||||
|
' --- Orchestrator ---
|
||||||
|
rectangle "OrchestratorAgent\n(BaseAgent + tools)" as Orch {
|
||||||
|
rectangle "ConversationLoop\n(main user)" as MainConv
|
||||||
|
}
|
||||||
|
|
||||||
|
' --- Sub-agent spawn ---
|
||||||
|
rectangle "subAgent(\n task: String,\n config: AgentConfig\n): Flow<SubAgentEvent>" as SpawnAPI
|
||||||
|
note right of SpawnAPI
|
||||||
|
Spawn API — НЕ отдельный модуль,
|
||||||
|
а convenience поверх BaseAgent:
|
||||||
|
val sub = agent.spawnChild(config) {
|
||||||
|
systemPrompt = "..."
|
||||||
|
tools = [ReadTool, WriteTool]
|
||||||
|
memory = EmptyMemory // изолированно
|
||||||
|
}
|
||||||
|
sub.events.collect { ... }
|
||||||
|
end note
|
||||||
|
|
||||||
|
' --- Дочерний агент (изолированный контекст) ---
|
||||||
|
rectangle "SubAgent\n(изолированный scope)" as Sub {
|
||||||
|
rectangle "ConversationLoop\n(child)" as SubConv
|
||||||
|
rectangle "backgroundScope\n(lifecycle scoped)" as SubBg
|
||||||
|
}
|
||||||
|
|
||||||
|
' --- A2A между независимыми агентами ---
|
||||||
|
rectangle "Agent A\n(BaseAgent)" as AgentA
|
||||||
|
rectangle "Agent B\n(BaseAgent)" as AgentB
|
||||||
|
rectangle "A2A Server\n(:a2a-server)" as A2ASrv
|
||||||
|
|
||||||
|
AgentA -> A2ASrv : POST /\n(application/json)
|
||||||
|
A2ASrv -> AgentB : dispatch(message)
|
||||||
|
AgentB --> A2ASrv : response
|
||||||
|
A2ASrv --> AgentA : SSE / JSON-RPC
|
||||||
|
|
||||||
|
' --- Стрелки ---
|
||||||
|
Orch -> SpawnAPI : calls
|
||||||
|
SpawnAPI -> Sub : creates with custom config
|
||||||
|
Sub -> SubBg : has its own
|
||||||
|
Orch -> Orch : main flow continues
|
||||||
|
Sub --> Orch : Flow<SubAgentEvent> emits\n(Started / ToolCalled / ToolResult /\nAssistantMessage / Done / Failed)
|
||||||
|
|
||||||
|
Orch -> A2ASrv : can also delegate to remote agent
|
||||||
|
|
||||||
|
@enduml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sub-agents API
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
sealed interface SubAgentEvent {
|
||||||
|
data class Started(val taskId: String) : SubAgentEvent
|
||||||
|
data class AssistantMessage(val text: String) : SubAgentEvent
|
||||||
|
data class ToolCalled(val toolName: String, val args: JsonObject) : SubAgentEvent
|
||||||
|
data class ToolResult(val toolName: String, val result: String) : SubAgentEvent
|
||||||
|
data class Done(val taskId: String, val finalResult: String) : SubAgentEvent
|
||||||
|
data class Failed(val taskId: String, val error: String) : SubAgentEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BaseAgent {
|
||||||
|
// ... existing methods ...
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spawn дочерний агент с изолированным контекстом (memory, system prompt,
|
||||||
|
* tools). Возвращает Flow событий жизненного цикла + результата.
|
||||||
|
* Cancellation родителя НЕ отменяет sub-agent — sub-agent живёт до Done/Failed.
|
||||||
|
*/
|
||||||
|
fun spawnChild(config: SubAgentConfig): Flow<SubAgentEvent>
|
||||||
|
}
|
||||||
|
|
||||||
|
data class SubAgentConfig(
|
||||||
|
val systemPrompt: String,
|
||||||
|
val tools: List<NamedTool> = emptyList(),
|
||||||
|
val memory: MemorySystem = EmptyMemory(),
|
||||||
|
val model: LiteLlm? = null, // если null — делит LLM родителя
|
||||||
|
val maxTurns: Int = 10,
|
||||||
|
val timeoutMs: Long = 60_000,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Зачем изолированный scope
|
||||||
|
|
||||||
|
Sub-agent получает **свою копию контекста**, не делит memory с родителем. Это критично:
|
||||||
|
- `research_subagent` — должен исследовать тему, не отвечать на основные сообщения пользователя
|
||||||
|
- `summarize_subagent` — суммаризировать документ, не трогать основной диалог
|
||||||
|
- `code_review_subagent` — ревьюить PR, не видеть разговор
|
||||||
|
|
||||||
|
Если нужно расшарить контекст — это explicit через `sharedMemory: SharedMemoryHandle` параметр, не default.
|
||||||
|
|
||||||
|
## A2A между независимыми агентами
|
||||||
|
|
||||||
|
Уже есть `:a2a-server` модуль (см. `standalone/build.gradle.kts` — `implementation(libs.a2a.server)`). Использовался для AG-UI/A2A протокола в `:standalone`. Можно переиспользовать для межагентного общения.
|
||||||
|
|
||||||
|
Сценарий: orchestrator-agent не может сам решить задачу → делегирует remote-агенту через A2A → получает response → продолжает. Это уже работающая инфраструктура.
|
||||||
|
|
||||||
|
## Текущее состояние vs целевое
|
||||||
|
|
||||||
|
✅ Уже есть:
|
||||||
|
- `:a2a-server` подключён
|
||||||
|
- `BaseAgent.spawnChild` — **не существует**, но `ConversationLoop` уже умеет создавать изолированный scope через свой `agentScope` — нужна только обёртка
|
||||||
|
|
||||||
|
⏳ Не сделано:
|
||||||
|
- `SubAgentConfig` + `Flow<SubAgentEvent>` API
|
||||||
|
- `EmptyMemory` (null-object для изолированного scope)
|
||||||
|
- Lifecycle management (parent dies → child должен complete or be cancelled?)
|
||||||
|
- Сериализация sub-agent state для отладки (event log)
|
||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,171 @@
|
|||||||
|
# 05 — Android Agent Stack
|
||||||
|
|
||||||
|
Что меняется vs `:standalone`. Цель: `BaseAgent` тот же самый, но platform-impl разные (Storage, LLM, Vector Memory, MCP).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
PlantUML source (для редактирования; требует Graphviz `dot` для рендеринга):
|
||||||
|
|
||||||
|
```plantuml
|
||||||
|
@startuml android-agent-stack
|
||||||
|
skinparam componentStyle rectangle
|
||||||
|
|
||||||
|
title Android Agent Stack — что меняется vs Standalone
|
||||||
|
|
||||||
|
' --- Android side ---
|
||||||
|
package "Android Application" {
|
||||||
|
[MainActivity\n(Compose)] as Activity
|
||||||
|
[AndroidAgentRunner\n(workmanager / service)] as Runner
|
||||||
|
[AndroidAgentBuilder] as AndroidBuilder
|
||||||
|
}
|
||||||
|
|
||||||
|
package "Android-specific impls" {
|
||||||
|
[StorageSqliteAndroid\n(Room/sqlite)] as StorageA
|
||||||
|
[MemoryVectorAndroid\n(ONNX runtime + ANN)] as MemVecA
|
||||||
|
[LitertAndroid\n(NNAPI delegate)] as LitertA
|
||||||
|
[SoulFileAndroid\n(context.filesDir)] as SoulA
|
||||||
|
[McpRegistry\nstdio: ProcessBuilder] as McpA
|
||||||
|
}
|
||||||
|
|
||||||
|
' --- Shared (KMP) ---
|
||||||
|
package "Agent Runtime (shared)" {
|
||||||
|
[AgentCore\nBaseAgent] as AgentCore
|
||||||
|
[AgentBuilder] as Builder
|
||||||
|
}
|
||||||
|
|
||||||
|
package "Domain (shared)" {
|
||||||
|
[LlmTools\ncommonMain] as LlmT
|
||||||
|
[BackgroundEvents\ncommonMain] as Ev
|
||||||
|
[McpBridge\njvmMain] as McpB
|
||||||
|
[Skills\ncommonMain] as Skills
|
||||||
|
}
|
||||||
|
|
||||||
|
package "Memory (shared impl)" {
|
||||||
|
[MemoryMd\n(commonMain)] as MemMd
|
||||||
|
[MemoryApi\ninterfaces] as MemApi
|
||||||
|
}
|
||||||
|
|
||||||
|
' --- Зависимости ---
|
||||||
|
Activity --> Runner
|
||||||
|
Runner --> AndroidBuilder
|
||||||
|
AndroidBuilder --> AgentCore
|
||||||
|
|
||||||
|
AndroidBuilder --> StorageA
|
||||||
|
AndroidBuilder --> MemVecA
|
||||||
|
AndroidBuilder --> LitertA
|
||||||
|
AndroidBuilder --> SoulA
|
||||||
|
AndroidBuilder --> McpA
|
||||||
|
|
||||||
|
AgentCore --> LlmT
|
||||||
|
AgentCore --> Ev
|
||||||
|
AgentCore --> McpB
|
||||||
|
AgentCore --> Skills
|
||||||
|
AgentCore --> MemMd
|
||||||
|
|
||||||
|
' --- Главные отличия от Standalone ---
|
||||||
|
note right of LitertA
|
||||||
|
On-device inference.
|
||||||
|
LiteRT с NNAPI delegate →
|
||||||
|
работает на CPU/GPU/NPU
|
||||||
|
прямо на устройстве, без сети.
|
||||||
|
|
||||||
|
vs Standalone: HTTP-only
|
||||||
|
(OpenAI-compatible).
|
||||||
|
end note
|
||||||
|
|
||||||
|
note right of StorageA
|
||||||
|
android.database.sqlite
|
||||||
|
через Room или сырой API.
|
||||||
|
|
||||||
|
vs Standalone: JDBC +
|
||||||
|
Sqlite-JDBC driver
|
||||||
|
(только JVM).
|
||||||
|
end note
|
||||||
|
|
||||||
|
note right of MemVecA
|
||||||
|
JVector JVM-only. На Android
|
||||||
|
нужна альтернатива —
|
||||||
|
ONNX Runtime + какой-нибудь
|
||||||
|
ANN (Annoy/HNSW).
|
||||||
|
|
||||||
|
Или пока без vector memory,
|
||||||
|
только MemoryMd.
|
||||||
|
end note
|
||||||
|
|
||||||
|
note right of McpA
|
||||||
|
MCP через ProcessBuilder
|
||||||
|
на Android работает, но
|
||||||
|
subprocess lifecycle
|
||||||
|
сложнее (foreground service
|
||||||
|
нужен для долгого subprocess).
|
||||||
|
end note
|
||||||
|
|
||||||
|
@enduml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Что общего с `:standalone`
|
||||||
|
|
||||||
|
**`BaseAgent`, `BackgroundScheduler`, `LlmTools`, `McpBridge`, `Skills`, `MemoryMd` — всё KMP (commonMain).** Android-agent = `:standalone` с другим wiring'ом. Не нужно переписывать agent logic.
|
||||||
|
|
||||||
|
## Что другое
|
||||||
|
|
||||||
|
| Компонент | `:standalone` (JVM) | `:agentik-android` (Android) | Сложность |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Storage | `:storage-sqlite` (JDBC + Sqlite-JDBC) | `:storage-sqlite-android` (Room или raw) | Низкая — тот же `StorageBundle` interface |
|
||||||
|
| LLM | `:litert-openai` (HTTP), `:litert-google` (LiteRT JVM) | `:litert-android` (LiteRT Android, NNAPI delegate) | Средняя — нужен новый модуль |
|
||||||
|
| Vector memory | `:memory-vector` (JVector) | `:memory-vector-android` (ONNX Runtime + HNSW/Annoy) | Высокая — JVector JVM-only, нужна альтернатива |
|
||||||
|
| SOUL provider | `FileSoulProvider` (path) | `SoulFileAndroid` (`context.filesDir`) | Низкая |
|
||||||
|
| MCP | `McpRegistry` (ProcessBuilder, stdio subprocess) | Тот же `McpRegistry`, но subprocess в foreground service | Средняя — нужен Android service |
|
||||||
|
| Embedding | `HttpEmbeddingClient` (HTTP) | Тот же ИЛИ on-device (ONNX) | Средняя |
|
||||||
|
|
||||||
|
## Минимальный Android agent (v1)
|
||||||
|
|
||||||
|
Если не нужны все фичи сразу — минимум:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val agent = androidAgentBuilder(context) {
|
||||||
|
llm(LitertAndroid.onDevice(context, modelPath = "/data/local/tmp/model.litertlm"))
|
||||||
|
storage(SqliteStorage.android(context, "agent.db"))
|
||||||
|
memory(MemoryMd.root(context.filesDir.resolve("memory")))
|
||||||
|
soul(FileSoul(context.filesDir.resolve("SOUL.md")))
|
||||||
|
background {
|
||||||
|
// OnClosing + OnCompaction работают так же как на JVM
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Без MCP, без vector memory (только MemoryMd на файлах), только on-device LLM. Достаточно для off-line агента.
|
||||||
|
|
||||||
|
## Foreground service для MCP
|
||||||
|
|
||||||
|
Если нужны MCP-серверы (например, локальный file-system MCP) — subprocess нужен foreground service чтобы Android не убил его при выключении экрана. Это добавляет сложности:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
class McpForegroundService : Service() {
|
||||||
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
startForeground(NOTIFICATION_ID, notification)
|
||||||
|
val proc = ProcessBuilder(command, args).start()
|
||||||
|
// ... route stdio to McpLiteToolAdapter ...
|
||||||
|
return START_STICKY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Пока можно без этого (только если MCP нужен на Android).
|
||||||
|
|
||||||
|
## Текущее состояние vs целевое
|
||||||
|
|
||||||
|
✅ KMP-ready:
|
||||||
|
- `:llm-tools` (commonMain, платформо-агностик)
|
||||||
|
- `:mcp-bridge` (jvmMain — Android-вариант через `:mcp-bridge-android`)
|
||||||
|
- `:skills` (commonMain)
|
||||||
|
- `:memory-md` (commonMain)
|
||||||
|
- `:proto` (commonMain)
|
||||||
|
|
||||||
|
⏳ Не существует:
|
||||||
|
- `:storage-sqlite-android`
|
||||||
|
- `:memory-vector-android`
|
||||||
|
- `:litert-android`
|
||||||
|
- `:agentik-android` (само приложение)
|
||||||
|
- `:agent-core` (выделить BaseAgent + builder)
|
||||||
|
- `:background-events` (выделить events + scheduler)
|
||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,64 @@
|
|||||||
|
# agentik — диаграммы архитектуры
|
||||||
|
|
||||||
|
PlantUML-схемы для обсуждения будущей структуры (Android agent, multi-user chat, sub-agents, A2A). Это **целевое состояние**, не текущее.
|
||||||
|
|
||||||
|
## Файлы
|
||||||
|
|
||||||
|
Каждый `.md` содержит:
|
||||||
|
- Краткое описание (что показывает)
|
||||||
|
- **Пред-рендеренный SVG** (``) — гарантированно показывается **везде**
|
||||||
|
- PlantUML source в ` ```plantuml ` блоке — для редактирования (требует Graphviz `dot` для рендеринга)
|
||||||
|
- Дополнительный markdown-текст (что нужно сделать, текущее vs целевое)
|
||||||
|
|
||||||
|
| Файл | Что показывает |
|
||||||
|
|---|---|
|
||||||
|
| [01-module-layers.md](./01-module-layers.md) | Целевая модульная структура (приложения → runtime → домен → абстракции → платформенные impl). Что в каком слое и кто от кого зависит. |
|
||||||
|
| [02-agent-composition.md](./02-agent-composition.md) | Как `AgentBuilder` собирает `BaseAgent` из компонентов. Memory backend сам объявляет свои tools. BackgroundScheduler подписан на события (НЕ interval-poll). |
|
||||||
|
| [03-multi-user-chat.md](./03-multi-user-chat.md) | Сценарий: чат с N пользователями, mention-detection, админ-команды, agent отвечает только когда addressed. |
|
||||||
|
| [04-sub-agents.md](./04-sub-agents.md) | Orchestrator spawn'ит sub-agent с изолированным контекстом, получает `Flow<SubAgentEvent>`. A2A между независимыми агентами через `:a2a-server`. |
|
||||||
|
| [05-android-stack.md](./05-android-stack.md) | Что меняется на Android: on-device LLM (NNAPI), Room/sqlite, ONNX-based vector memory, foreground-service для MCP subprocess. |
|
||||||
|
|
||||||
|
## Почему SVG + PlantUML source
|
||||||
|
|
||||||
|
PlantUML требует Java + (для component/class/deployment диаграмм) Graphviz `dot`. Если `dot` не установлен — рендерер падает с ошибкой "Executable dot does not exist".
|
||||||
|
|
||||||
|
Решение: **пре-рендерим в SVG один раз** и вставляем как `<img>`. Диаграмма гарантированно показывается в любом markdown-viewer (GitHub, IntelliJ, VSCode, GitLab) без зависимостей. PlantUML source в code block остаётся для редактирования.
|
||||||
|
|
||||||
|
## Как редактировать диаграмму
|
||||||
|
|
||||||
|
1. Меняешь PlantUML-source в ` ```plantuml ` блоке `.md` файла.
|
||||||
|
2. Ре-рендеришь SVG:
|
||||||
|
```bash
|
||||||
|
mkdir -p /tmp/plantuml-work && chmod 777 /tmp/plantuml-work
|
||||||
|
cp docs/diagrams/*.md /tmp/plantuml-work/
|
||||||
|
docker run --rm -v /tmp/plantuml-work:/work plantuml/plantuml -tsvg /work/*.md
|
||||||
|
cp /tmp/plantuml-work/*.svg docs/diagrams/
|
||||||
|
```
|
||||||
|
3. Проверяешь что SVG обновился:
|
||||||
|
```bash
|
||||||
|
ls -la docs/diagrams/*.svg
|
||||||
|
```
|
||||||
|
4. Коммитишь оба файла: `.md` (source) и `.svg` (rendered).
|
||||||
|
|
||||||
|
Требует Docker (или локального PlantUML+Graphviz). `apt install graphviz` для Arch/Manjaro.
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
|
||||||
|
Текущий код движется в эту сторону:
|
||||||
|
- `:llm-tools` extracted ✅
|
||||||
|
- `:mcp-bridge` extracted ✅
|
||||||
|
- `BackgroundScheduler` стал event-driven ✅
|
||||||
|
- `ConversationLoop` стал отдельным компонентом ✅
|
||||||
|
|
||||||
|
Не сделано (см. детали в каждом .md):
|
||||||
|
- `:agent-core` (выделить `BaseAgent` + builder)
|
||||||
|
- `:background-events` (выделить events + scheduler)
|
||||||
|
- `:storage-sqlite-android`, `:memory-vector-android`, `:litert-android`
|
||||||
|
- `:agentik-android` (само приложение)
|
||||||
|
- `MentionDetector` interface + adapters для multi-user chat
|
||||||
|
- `BaseAgent.spawnChild` + `Flow<SubAgentEvent>`
|
||||||
|
|
||||||
|
Подробнее:
|
||||||
|
- `STANDALONE-REVIEW.md` — что плохо в текущем коде
|
||||||
|
- `MEMORY-DESIGN.md` — детали memory архитектуры
|
||||||
|
- `STANDALONE.md` — текущий standalone
|
||||||
Reference in New Issue
Block a user