25771a0c33
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
120 lines
5.3 KiB
Markdown
120 lines
5.3 KiB
Markdown
# 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)
|