# 02 — Agent Builder: композиция (целевое API) Как `AgentBuilder` собирает `BaseAgent` из компонентов. **Memory backend сам объявляет свои tools** — builder их авто-мержит. BackgroundScheduler подписан на события, не interval-poll. ![Agent Composition](./02-agent-composition.svg) 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 (авто-сборка из backends)" as Tools rectangle "background: BackgroundConfig (default EmptyBg)" as Bg rectangle "toolset: List (default empty)" as Ts } ' --- Backends with their tool side-effects --- rectangle "MemoryMd" as MdMem { interface "MemorySystem" as MemSys interface "List" as MdTools note right MemoryMd.exposesTools() → memory_save / memory_read / memory_list / memory_delete end note } rectangle "McpRegistry" as McpReg { interface "List" as McpTools note right McpRegistry.namedTools → server__tool1, server__tool2, ... end note } rectangle "BackgroundEvents" as Events { interface "MutableSharedFlow" 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).