Files
agentik/docs/diagrams/04-sub-agents.md
T
subochev 25771a0c33
ci / JVM build + tests (push) Failing after 1m57s
docs(diagrams): agent architecture overview with pre-rendered SVG
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
2026-09-18 20:02:41 +03:00

5.3 KiB

04 — Sub-agents + A2A между агентами

Точки 2 и 3 из планов. Orchestrator-агент spawn'ит sub-агентов с изолированным контекстом. Независимые агенты общаются через A2A.

Sub-agents + A2A

PlantUML source (для редактирования; требует Graphviz dot для рендеринга):

@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

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)