- Replaced `EmbeddingProvider` with cross-platform `TextEmbeddingExecutor` for native target compatibility.
- Introduced `:memory-md-vector` module combining vector-cache and `.md` file-based memory systems (`hybrid` backend).
- Updated `SiglipEmbeddingProvider` to use KMP `TextEmbeddingExtractor` and streamlined compatibility via `asExecutor`.
- Added hybrid memory backend to `standalone`, supporting `.md` reconciliation with vector-cache for semantic
- Removed `:message-store-api` module and associated classes (ConversationStore, ReflectionStore, Ids, etc.).
- Migrated reusable components to `:journal-api` (conversation-related) and `:reflection-api` (reflection-related).
- Updated imports and module dependencies across all projects to reflect new structure.
- Adjusted build scripts and tests for compatibility with the new APIs.
- Replaced usages of `:message-store-api` and `:working-memory-api` with `:journal-api`, `:outbox-api`, and `:context-api`.
- Deprecated legacy `EventStore` and `MessageStore` interfaces, added `typealias` for backward compatibility.
- Updated imports across all modules with references to `:journal-api` and `:outbox-api`.
- Introduced `journalRoutes` and `outboxRoutes` in `:server` for audit log and live event stream endpoints.
- Adjusted `Agent` to expose read-only `journal` and `outbox` stores for improved modularity and clarity.
- Removed legacy Event and AgentEvent definitions from `:proto`, migrated to `:outbox-api`.
- Storage-related modules have been updated to support the new APIs consistently.
- Remove legacy pw.binom.agentik.messageStore.events.EventStore (EventRecord,
EventType) and all three impls (in-memory, sqlite, ksqlite) + tests + .sq
- Drop :storage-bundle module entirely; ChatAgent / ChatConversation /
ConversationLoop / DebugRoutes now take stores individually
(conversationStore, messageStore, workingMemoryStore, reflectionStore,
eventStore) instead of StorageBundle
- Delete server endpoints /events/replay and /conversations/{id}/events/replay;
Route.agentikAgent no longer takes eventStore param
- Add :client/HttpEventStore implementing :event-store/EventStore over HTTP:
events() -> GET /events/all, agentEvents() -> GET /events,
conversationEvents(convId) -> GET /conversations/{id}/events;
exposed via AgentClient.eventStore
- :event-store: add macosX64/macosArm64/linuxArm64 targets to match :client KMP
- :working-memory-api: drop api dep on :message-store-api (no longer needed)
- :storage-{inmemory,sqlite,ksqlite}: drop deps on :storage-bundle
Выделяет append-only message log в отдельный KMP-модуль.
Цель — разделить ДВЕ сущности по своей природе:
:message-log-api — append-only audit log (User/Assistant/ToolCall/
ToolResult/Error). Никаких update, только insert + read.
Это иммутабельная история диалога.
:working-memory-api — mutable runtime context (compact, summary, WM order).
Live state. Compaction-логика.
Раньше оба жили в :message-store-api, что:
- смешивало контракты: append-only audit vs mutable runtime;
- делало невозможным лёгкого клиента который читает только audit log
без WM-runtime зависимости;
- затрудняло compaction-логике жить в одном модуле с audit-записью.
Миграция:
- В :message-log-api переехали: Content, MessageRecord, MessageStore,
MessageContext (с MessageOrigin), MessageEvent, TokenStats, TurnTokens,
helpers (encode/decodeBodyPayload, MessageBodyPayload, BodyDecoded).
Пакет pw.binom.agentik.messageLog.
- В :message-store-api остались: ConversationStore, ConversationRecord,
ReflectionStore, Ids, legacy events.EventStore (paginated replay).
Пакет pw.binom.agentik.messageStore.
- :working-memory-api: обновил deps (api → :message-log-api для Content/MessageContext).
- 23 consumer-файла обновлены (FQN renames).
- storage-sqlite/ksqlite: убраны недостижимые ветки Summary/System
(эти synthetic records живут ТОЛЬКО в :working-memory-api, не попадают
в audit log :message-log-api).
Файлы:
+ :message-log-api (5 файлов, ~280 строк)
- :message-store-api (5 файлов, ~430 строк)
~ 23 файла обновлены
Совместимость схем не меняется. Все 5 storage impl'ов (3 backend × 5 store)
работают на тех же таблицах.
Разделяет монолитный :storage-core на 3 модуля с чёткими границами:
:message-store-api — MessageStore, ReflectionStore, EventStore, ConversationStore +
Content, Payload, MessageContext, Ids, MessageEvent
(audit log + event stream)
:working-memory-api — WorkingMemoryStore + WorkingMemoryEntry
(runtime context с compaction)
:storage-bundle — StorageBundle агрегатор, зависит от обоих
(только для server-side runtime)
Пакеты:
pw.binom.agentik.storage.* → УДАЛЕНО
pw.binom.agentik.messageStore.* — append-only API
pw.binom.agentik.messageStore.events.* — EventStore + EventRecord
pw.binom.agentik.workingMemory.* — WM API
pw.binom.agentik.storageBundle.* — aggregator
Зачем:
- Тонкий клиент может подтянуть ТОЛЬКО :message-store-api (~15KB, нет
compaction-логики, нет MessageStore+WorkingMemoryStore cross-deps).
- Android-agent в будущем подключит :message-store-api для audit log,
серверный runtime — :storage-bundle со всем.
- Компиляционные границы защищают от случайной зависимости от WM
в read-only клиентах (раньше один :storage-core не давал такой
гарантии).
Миграция:
- Имплементации (:storage-inmemory, :storage-sqlite, :storage-ksqlite)
обновили package + добавили deps на оба API модуля + :storage-bundle.
- Тесты из :storage-core (PersistenceTest, SqliteStoresMigrationTest,
TokenStatsTest) переехали в :standalone, получили testImplementation
на оба API модуля и импорты новых типов.
- 52 файла в :standalone, :agent-toolsets, :llm-tools, :server, :client,
:agentik-cli обновили FQN.
- :storage-core удалён.
Совместимость схем не меняется — все 5 impl'ов (3 backend × 5 store) хранят
данные в тех же таблицах, миграция между Sqlite и Ksqlite возможна через SQL dump.
Тесты:
standalone 178 ✅
agent-toolsets 36 ✅
storage-inmemory 47 ✅
storage-sqlite 17 ✅ (включая переехавшие persistence/* + tokenStats)
storage-ksqlite 36 ✅
---
Total: 314 tests, 0 failures
Standalone refactor — modularity + correctness improvements after
STANDALONE-REVIEW findings. Touches ~30 files. Build green, 178 tests pass.
(1) Module extractions — generic components out of :standalone:
• :llm-tools (new KMP module, package pw.binom.agentik.llm.tools)
- LlmReflector, SkillMiner, LlmMemoryReviewer, LiteLlmContextCompactor
- Parsers: ReflectionParser, SkillMiningParser, ReviewDecisionParser
- Prompts: ReflectionPrompts, SkillMiningPrompts, ReviewPrompts
• :mcp-bridge (new JVM module, package pw.binom.agentik.mcp.bridge)
- McpConfig, McpRegistry, McpLiteToolAdapter
• NamedTool moved from :standalone to :agent-toolsets/commonMain
- Generic (name + LiteTool) wrapper, used by both :mcp-bridge
and :standalone's tool dispatcher
:standalone loses ~1400 lines, depends on the two new modules.
(2) Background work → event-driven (no more interval-polling):
• New :standalone/agent/BackgroundEvents.kt — internal event bus:
- ToolCallEvent.Succeeded/Failed (emitted by ToolDispatcher after invoke)
- CompactionEvent.Triggered (emitted by CompactionCoordinator pre-delete)
- ConversationLifecycleEvent.Closing (emitted by ConversationLoop.close)
• BackgroundScheduler rewritten as event subscriber:
- On Closing: final reflection + skill mining (last-chance extraction)
- On Compaction (turnsToDelete > 10): skill mining (debounced 60s)
- On ToolFailure x2 in 60s window: reflection (debounced 5min)
- Dropped: maybeScheduleReview/Reflection/SkillMining (interval-based)
- Dropped config: memoryReviewInterval, reflectionInterval, skillMiningInterval
• ToolDispatcher emits ToolCallEvent after each invoke.
• CompactionCoordinator emits CompactionEvent before workingMemory.compact().
• ConversationLoop.close() emits Closing BEFORE agentScope.cancel() so the
subscription gets to run final reflection/mining.
Net effect: typical 30-turn conversation runs ~38 LLM calls (was: 30 main +
3 review + 3 reflection + 2 mining). With event-driven, review/mining only fire
when their triggers actually make sense (compaction about to delete, or
conversation closing).
(3) AppConfig single source of truth:
• Replaces AgentikConfig + LlmConfig.fromEnv + McpConfig.fromEnv with one
AppConfig.fromEnv() that reads all ~25 env vars in a single pass.
• Sections: AgentSection, LlmSection, McpSection, MemorySection,
EmbeddingSection, ReflectionSection, SkillMiningSection, DebugSection.
• OPENAI_CONTEXT_WINDOW / AGENTIK_GOOGLE_CONTEXT_WINDOW no longer
read twice (was a bug per STANDALONE-REVIEW E3).
(4) Other fixes inherited from earlier waves:
• Hardening — size caps on user-input boundaries:
MAX_MEMORY_CONTENT_LEN=32KB, MAX_SKILL_BODY_LEN=64KB,
MAX_MCP_CONFIG_BYTES=1MB, MAX_A2A_REPLY_LEN=10MB, MAX_PORT=65535,
blank-rejection in LlmConfig.requireEnv, URL/command validation.
• Single scope — :standalone/agent/ConversationLoop has one
agentScope (was: scope + backgroundScope).
• liteConvRef race fix — capture-then-use pattern replaces !!-after-read;
close() + runTurn.finally race on LiteConv JNI handled via
AtomicReference.getAndSet.
• SkillMiner.maxTurns / LlmReflector.maxTurns exposed as public (needed
by BackgroundScheduler for prompt sizing).
• Tests: MemoryWiringTest updated for new compaction-triggered review
behavior; all parser/test imports updated for new packages.
Test results: 178/178 in :standalone, 36/36 in :agent-toolsets — all green.
Every subproject now has README.md:
- 3 runnable modules (:standalone, :agentik-cli, :agentik-tui):
quickstart, env table, parameters, known limits
- 11 library modules: what it is, which problem solves, how to
wire it in, where versions live
Root README.md is the navigation hub (Quickstart, Modules table,
publish + CI/CD notes).
Also: ci.yml prunes the :memory-vector -x excludes now that
text-embedding-kmp artifacts are published to caffeine.
518 tests green.
Verified publish pipeline: :proto:publish to caffeine produces
pom.module + per-target klibs + sources for all 9 KMP targets.
🤖 Generated with [opencode]
Добавлен SystemPromptToolsetSection — рендер markdown-секции для system prompt.
Контракт:
- toolsets пустой → null (секция не добавляется, агент не знает о механике)
- иначе → краткое описание концепции + список 'name — description' для
активных и неактивных (одинаковый формат per design contract)
- auto-activation НЕ упоминается в промпте (только в dispatch)
Интеграция в ChatAgent:
- Добавлен параметр toolsets: List<ToolsetContribution> = emptyList()
- При пустом списке — enable_toolset/disable_toolset НЕ регистрируются,
секция в system prompt НЕ появляется (полная невидимость per A1-α)
- При непустом — тулы регистрируются, секция добавляется
- ToolsetRegistry + ToolsetDispatchPolicy создаются per-agent (один реестр
на все диалоги — состояние 'активные тулсеты' общее)
Интеграция в ChatConversation:
- Новый параметр toolsetDispatch: ToolsetDispatchPolicy? = null
- runToolAndPersist: если задан — вызов идёт через policy (auto-activate
неактивных тулсетов, fallback в base dispatcher для плоских тулов)
- Иначе — старое поведение через toolsByName
Тесты:
- 7 новых в :agent-toolsets (SystemPromptToolsetSection): пустые списки,
только активные, только неактивные, оба, проверка отсутствия auto-activation
упоминания, registry-based рендер, пустой реестр
- 5 новых в :standalone (ChatAgentToolsetsTest): default (пустой) — нет
тулов и секции; non-empty — тулы и секция есть; enable_toolset активирует;
вызов тула из неактивного тулсета — auto-activate; disable_toolset
снимает из active set (но auto-activate на следующем вызове — by design)
Tests: 340/340 green (335 ранее + 5 новых ChatAgent integration)
Перенесён SQLDelight (4 .sq файла, конфигурация databases { AgentikDatabase })
и 5 SQLite-импл классов (SqliteStores, SqliteConversationStore, SqliteMessageStore,
SqliteWorkingMemoryStore, SqliteReflectionStore) из :standalone в новый JVM-only
модуль :storage-sqlite под пакетом pw.binom.agentik.storage.sqlite.
Изменения:
- Новый :storage-sqlite модуль с sqldelight-плагином + sqlite JDBC driver
- Все .sq файлы и Kotlin-классы переехали с переименованием пакета
- ReflectionStore.kt в :standalone (только SQLite-импл) удалён — функционал
живёт в :storage-sqlite/SqliteReflectionStore.kt
- :standalone/build.gradle.kts: убран sqldelight-плагин и конфигурация,
добавлена зависимость :storage-sqlite
- Все импорты в :standalone (8 main + 7 test) перенаправлены на новый пакет
- ReflectionStoreTest.kt переехал в :storage-sqlite/jvmTest (тестирует
internal fun encode/decodeStringArray в :storage-sqlite)
Совместимость:
- SqliteStores доступен по новому пути pw.binom.agentik.storage.sqlite.SqliteStores
- Старые импорты в тестах обновлены (минимум diff — 1 строка на файл)
- В commit 6 ChatAgent переключится на StorageBundle API; SqliteStores
станет деталью реализации :standalone
Тесты: 299/299 green. Fatjar standalone-all.jar 240 MB.
Преимущества:
- :standalone больше не зависит от SQLDelight плагина (легче поддерживать)
- :storage-sqlite может быть заменён/расширен (например, :storage-android)
- Тесты storage-слоя сгруппированы по модулю реализации
Выносим интерфейсы и data-классы истории диалога (MessageStore / WorkingMemoryStore /
ConversationStore / ReflectionStore + соответствующие sealed-иерархии MessageRecord /
WorkingMemoryEntry / Content / ConversationRecord / Reflection + payload-утилиты) из
:standalone в отдельный KMP-модуль :storage-core (pw.binom.agentik.storage).
Цель — подготовка к Android-портированию и подключению альтернативных реализаций
хранилища без затягивания всей :standalone. Дальше (commit 2/3) — :storage-inmemory
и :storage-sqlite как самостоятельные модули, плюс :storage-android (deferred).
Изменения:
- Новый :storage-core (KMP, commonMain only, jvm + native таргеты) — 12 файлов
- StorageBundle агрегатор (conversationStore + messageStore + workingMemoryStore +
reflectionStore; SkillStore живёт в :skills и подключается отдельно)
- 11 файлов импортов в :standalone переключены на новый пакет
- SqliteReflectionStore оставлен в :standalone до commit 3 (зависит от
SQLDelight AgentikDatabase, которую ещё не отвязали от :standalone)
- 4 теста перенесены в :standalone/.../storage/ с обновлённым пакетом
- PayloadTest переехал в :storage-core/commonTest (тестирует чистые типы)
Tests: 264/264 green (179 :standalone + 6 :storage-core + прочие JVM-модули)
Заменил все System.err.println / println на структурное логирование
(kotlin-logging, пакет mu) — теперь логи идут с timestamp/level/thread/logger.
* gradle/libs.versions.toml: kotlin-logging = "3.0.5" (в прокси доступна
только эта версия; новые 7.x пока не подтянуты), logback-classic = "1.5.18".
* standalone/build.gradle.kts: implementation(libs.kotlin.logging) +
implementation(libs.logback.classic) в jvmMain.
* standalone/src/jvmMain/resources/logback.xml: консольный appender,
pattern с timestamp/level/thread/logger, level управляется через
${AGENTIK_LOG_LEVEL:-INFO} (env override на старте JVM), уровни
io.netty/ai.onnxruntime уведены в WARN чтобы не забивать канал.
* Заменены все System.err.println в: ChatConversation (14 callsites),
Curator (2), McpRegistry (5), McpConfig (2), Main (2). Startup banner
в Main оставлен на println — это user-facing output, не log.
* Tests: 288 зелёных (только замена log-вызовов, без изменения семантики).
Smoke: `04:53:49.313 INFO [DefaultDispatcher-worker-4] p.b.a.s.agent.memory.Curator - started (interval=1d, maxAge=90d, maxUseCount=0)`
подтверждает структурный лог вместо println. AGENTIK_LOG_LEVEL=DEBUG работает.
Пользовательские инструкции («навыки») живут в указанной папке
(AGENTIK_SKILLS_DIR), рекурсивно читаются при старте и попадают в
системный промпт в сжатом виде: только имя + краткое описание.
Полный текст модель подгружает по требованию, вызывая встроенный
инструмент read_skill(name).
*:skills
- SkillCatalog + SkillPrompt (commonMain): рендер секции системного
промпта; тело навыка в промпт не течёт.
- SkillParser.parseAuto(): теперь читает и opencode-стиль SKILL.md
(YAML frontmatter + markdown тело), и голый *.yaml/*.yml
(поля name, description, опц. body). parseOrThrow для strict-путей.
- SkillParseError.render(): человекочитаемое описание ошибки для
логов и диагностики.
- SkillLoader (jvmMain): рекурсивный обход папки, детерминированный
порядок (по пути), ошибки отдельных файлов не валят загрузку;
дубликаты имён → ошибка, выигрывает первый по пути.
* :standalone
- AgentikConfig.skillsDir + env AGENTIK_SKILLS_DIR.
- ChatAgent: параметр skills (SkillCatalog); системный промпт
автоматически дополняется секцией «## Навыки» и в working memory
сидится вместе с базовым промптом.
- При непустом каталоге в tools автоматически добавляется
SkillReadTool (имя read_skill) — модель может загрузить полный
текст навыка, как обычный LiteTool.
- Main.kt: загружает навыки и шумно логирует ошибки загрузки в stderr.
* docs
- STANDALONE.md: секция «Навыки (skills)», env-переменная в таблице.
- Формат SKILL.md (opencode frontmatter) + голый *.yaml/*.yml.
Тесты: :skills jvmTest 34, :standalone jvmTest 69 (новые — состав
системного промпта, регистрация read_skill, навыки не утекают в
промпт телом).
Убрана зависимость pw.binom.agui:server из проекта — AGUI больше
не нужен (см. Memory #3675; agentik переходит на свой протокол :proto
и HTTP-фасад :server).
Чистка:
- standalone/build.gradle.kts: implementation(libs.agui.server) → удалено
- gradle/libs.versions.toml: [versions] agui + agui-api/agui-client/
agui-server entries → удалены
- settings.gradle.kts: убран 'AG-UI' из комментария к Nexus-репо
- proto/build.gradle.kts: комментарий 'Зеркалит набор AG-UI api' →
'Полный набор KMP-целей'
- proto/src/.../Agent.kt: KDoc '(замена AG-UI)' → удалено
- settings.gradle.kts: то же
- docs/ARCHITECTURE.md: переписан (описывал старую AGUI-centric
архитектуру с AbstractAgent/SessionStore/
AgentEngine — ничего этого в коде уже
нет; теперь отражает текущее состояние:
:proto + :server + :standalone + LiteLlm +
MCP + SqliteStores)
- NATIVE-COMPATIBILITY.md: пункт 10 'agui-server KMP-готовность' →
отменён (см. -); убран из 'Жёсткие блокеры'
В коде не осталось ни одного обращения к AGUI. После чистки в репо
больше нет ни одной зависимости от pw.binom.agui.*.
Проверка: ./gradlew :server:assemble (9/9 KMP-целей) +
:standalone:jvmTest 44/44 (ChatAgent 15, LlmConfig 6, McpConfig 8,
McpRegistry 4, Persistence 11) — зелёные.
Punkt 7 of NATIVE-COMPATIBILITY.md. Proto sources were already on
kotlin.time.Instant (migrated earlier); the kotlinx-datetime api dep
in :proto/build.gradle.kts and the implementation dep in
:standalone/build.gradle.kts were dead weight.
Dropped:
- api(libs.kotlinx.datetime) from proto/build.gradle.kts
- implementation(libs.kotlinx.datetime) from standalone/build.gradle.kts
- [versions] kotlinx-datetime and [libraries] kotlinx-datetime from
gradle/libs.versions.toml
Verification: ./gradlew :server:assemble (all 9 native targets) +
./gradlew :standalone:jvmTest (44/44 green, 0 failures) — both pass.
The kotlinx-datetime typealias was deprecated in 0.8.0 (memory #3708);
now nothing in agentik pulls the library.
1. UUID: java.util.UUID.randomUUID() → commonMain helper Ids.new(prefix)
wrapping kotlin.uuid.Uuid.random(). kotlin.uuid is stdlib (KMP: jvm +
all native targets), so the call sites (ChatAgent, ChatConversation,
SqliteWorkingMemoryStore) are now ready for native builds. Format kept:
'<prefix>-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'.
2. HTTP engine: ktor-server-netty → ktor-server-cio. Netty is JVM-only;
CIO is KMP (jvm, linuxX64, ios, etc.) and feature-complete for our
needs (POST + SSE). standalone/build.gradle.kts drops netty and adds
cio; Main.kt imports io.ktor.server.cio.CIO and calls
embeddedServer(CIO, port = port).
Netty is left in libs.versions.toml (catalog entry kept) in case anyone
needs to fall back; only :standalone stopped depending on it.
E2E verified end-to-end against litellm local/codding: standalone-1.0.0
tarball starts on AGENTIK_PORT, GET /health -> 'ok', POST
/agentik/conversations -> 201 with new UUID-format id, POST .../messages
returns real LLM answer ('Ассистент'). 44/44 unit tests green.
Major additions:
* :proto (KMP submodule) — in-house stateful protocol replacing AG-UI.
Agent owns conversation transcript; Conversation.events(after) is a live,
replay-free stream; backfill via Conversation.getMessages(after, offset, limit).
Each Event carries an Instant date for client-side resume tracking.
Sealed hierarchies (Content/Message/Event/AgentEvent) annotated @Serializable
with snake_case @SerialName JSON discriminators so the wire format is
decoupled from Kotlin class names.
* :server (JVM, Ktor 3.1.3) — REST+SSE facade for Agent.
Public entry: Route.agentikAgent(agent, path = "/agentik").
Endpoints: create/list/get/patch/delete conversations, POST messages (202),
POST interrupt, GET messages, GET conversation events (SSE),
GET agent events (SSE), GET /health. Custom Instant serializer for
kotlin.time.Instant registered contextually on agentikJson (ISO-8601,
ignoreUnknownKeys=true, explicitNulls=false).
* :client (JVM, Ktor HTTP Client + CIO) — mirror of :server returning
a pw.binom.agentik.proto.Agent backed by HTTP calls. Custom SSE parser
since ktor-client-sse is not on the 3.1.3 client classpath.
* standalone — EchoProtoAgent (in-memory Agent for :proto), EchoAgent
(existing AG-UI echo), both mounted on the same Netty embedded server
on port 8080 (/agui and /agentik); A2A stays on its own CIO engine on
8081. EchoProtoAgent smoke-tested end-to-end against :server: all 11
endpoints, including live SSE delivery of StartResponse/AppendText/End
event triplets and Agent-level Created/Deleted events.
Design notes pinned in:
* agentik/IRC-QUESTIONS.md — closed 13-item checklist for the upcoming
:irc-server transport (channel = conversation, CTCP for structural
events, draft/chathistory for backfill, ImageStore side-channel, etc).
* docs/ARCHITECTURE.md — overall layout snapshot.