docs(diagrams): agent architecture overview with pre-rendered SVG
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:
2026-09-18 20:02:41 +03:00
parent 78cbe9b463
commit 25771a0c33
11 changed files with 773 additions and 0 deletions
+119
View File
@@ -0,0 +1,119 @@
# 04 — Sub-agents + A2A между агентами
Точки 2 и 3 из планов. Orchestrator-агент spawn'ит sub-агентов с изолированным контекстом. Независимые агенты общаются через A2A.
![Sub-agents + A2A](./04-sub-agents.svg)
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)