Конфигурация и БД

Архитектура конфигурации Creo: структура config.json, model resolution, XDG paths, миграции и валидация. Архитектура SQLite-хранилища: схема, таблицы, FTS5, миграции БД.

1type Config struct {
2    Schema    string    // JSON Schema reference
3    Agent     Agent     // Модельная конфигурация (обязательно)
4    Locale    *string   // Язык интерфейса: "ru", "en"
5    Scheduler Scheduler // dreaming, scheduled prompts
6    Skillhub  Skillhub  // Реестр навыков
7    Tui       Tui       // Настройки TUI
8    Version   string    // Версия схемы (управляется мигратором)
9}
1type Agent struct {
2    Current       string         // Ключ модели для основных сессий
3    Mini          string         // Ключ модели для фоновых задач
4    ModelBindings []ModelBinding // CWD-based переопределения
5    Models        AgentModels    // Map: имя → Model
6}
1type Scheduler struct {
2    DreamModel    *string // Явная модель для dreaming (пусто = mini)
3    Dreaming      bool    // Включить dreaming (default: true)
4    RetentionDays int     // Дни хранения soft-deleted (default: 30, 0 = ∞)
5    ScheduleModel *string // Явная модель для scheduled prompts (пусто = current)
6}
1type Tui struct {
2    BottomPanelVisible *bool     // Видимость нижней панели (Ctrl+Y)
3    Mini               *bool     // Compact mode
4    Shell              TuiShell  // Windows: "powershell" | "cmd"
5    SidebarVisible     *bool     // Видимость sidebar (Ctrl+B)
6    Theme              string    // Цветовая схема (default: "onedark")
7}

Creo разрешает модель для конкретной задачи через иерархию fallback'ов:

ResolveModel(cwd)   → model_bindings (first match) → agent.current
DreamModelName()    → scheduler.dream_model → agent.mini
ScheduleModelName() → scheduler.schedule_model → agent.current
MiniModel()         → agent.Models[agent.mini]
CurrentModel()      → agent.Models[agent.current]
 1func (c *Config) ResolveModel(cwd string) string {
 2    for _, b := range c.Agent.ModelBindings {
 3        if matchCwdPattern(b.Cwd, cwd) {
 4            if b.Model != nil && *b.Model != "" {
 5                if _, ok := c.Agent.Models[*b.Model]; ok {
 6                    return *b.Model
 7                }
 8            }
 9            break // даже если model невалидна — не пытаться дальше
10        }
11    }
12    return c.Agent.Current
13}

Паттерны model_bindings поддерживают glob (*, ?), суффикс /* для поддиректорий. Первый матч выигрывает.

1func (c *Config) DreamModelName() string {
2    if c.Scheduler.DreamModel != nil && *c.Scheduler.DreamModel != "" {
3        if _, ok := c.Agent.Models[*c.Scheduler.DreamModel]; ok {
4            return *c.Scheduler.DreamModel
5        }
6    }
7    return c.Agent.Mini  // fallback
8}

Все пути следуют XDG Base Directory Specification:

Функция Переменная Fallback Описание
ConfigDir() $XDG_CONFIG_HOME/creo ~/.config/creo Конфигурация
ConfigPath() $ConfigDir/config.json
DataDir() $XDG_DATA_HOME/creo ~/.local/share/creo БД сессий
StateDir() $XDG_STATE_HOME/creo ~/.local/state/creo Логи, state
DatabasePath() $DataDir/sessions.db
CommandsDir() $ConfigDir/commands
MemoryProfilePath() $ConfigDir/memory/profile
MemoryEnvironmentPath(cwd) $cwd/.creo/memory/environment
SkillsGlobalDir() $ConfigDir/skills
SkillsProjectDir(cwd) $cwd/.creo/skills
AutoskillsGlobalDir() $ConfigDir/autoskills
AutoskillsProjectDir(cwd) $cwd/.creo/autoskills

ConfigDir использует os.UserConfigDir() (→ ~/.config на Linux, ~/Library/Application Support на macOS). DataDir и StateDir~/.local. Разделение соответствует XDG spec.

Два пакета: migrator (фреймворк) и migrations (миграции).

1type Migrator struct {
2    migrations map[Version]Migration
3    versions   []Version  // сортированный список
4}
5
6type Version string  // "1.0.0", "2.0.0"
7type Migration func(cfg map[string]any) error

Алгоритм Migrate:

  1. Парсить JSON → map[string]any
  2. Прочитать cfg["version"] (default: "0.0.0")
  3. Найти все версии > currentVersion (сортированный порядок)
  4. Применить каждую миграцию последовательно
  5. Записать cfg["version"] = latest
  6. Сериализовать обратно в JSON

Семантическое сравнение версий: major → minor → patch.

Единственная миграция: из старого плоского формата в секционированный.

Было (v0.0.0):

1{
2    "current": "my-model",
3    "mini": "my-mini",
4    "models": { ... },
5    "model_bindings": [ ... ]
6}

Стало (v1.0.0):

 1{
 2    "agent": {
 3        "current": "my-model",
 4        "mini": "my-mini",
 5        "models": { ... },
 6        "model_bindings": [ ... ]
 7    },
 8    "scheduler": {
 9        "dreaming": true,
10        "dream_model": "my-mini",
11        "schedule_model": "my-model"
12    }
13}

Конфиги без поля "version" считаются 0.0.0 и всегда проходят миграцию.

Read config.json
    ↓
migrations.Migrator.Migrate(data)  ← raw JSON transformation
    ↓ (если миграция была)
Save(cfg)                          ← persist обновлённый конфиг
    ↓
json.Unmarshal → *Config
    ↓
cfg.validate()
    ↓
return cfg, nil
  1. json.MarshalIndent с отступами
  2. Создание temp-файла (.creo-config-*.tmp)
  3. Write + chmod 0644
  4. os.Rename — POSIX-атомарный rename поверх temp-файла

Даже если процесс упадёт посреди записи, config.json останется нетронутым.

Двухуровневая:

Модель: для openai-провайдера требуется api_key. Для ollama — допустимо без ключа.

Конфиг:

  1. Каждая модель проходит Model.validate()
  2. current и mini должны существовать в Agent.Models
  3. dream_model и schedule_model (если заданы явно) должны существовать
  4. model_bindings: cwd обязателен, model/mini (если заданы) должны существовать

Params (через generated UnmarshalJSON):

  • frequency_penalty: -2 ≤ x ≤ 2
  • presence_penalty: -2 ≤ x ≤ 2
  • repeat_penalty: x ≥ 0
  • temperature: 0 ≤ x ≤ 2
  • top_k: x ≥ 0
  • top_p: 0 ≤ x ≤ 1
  • context_length: x ≥ 1
  • locale: паттерн ^[a-zA-Z]{2,3}(-[a-zA-Z0-9]+)*$

Единый файл: $XDG_DATA_HOME/creo/sessions.db.

1dsn := "file:" + dbPath + "?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)"
2conn.SetMaxOpenConns(128)
  • WAL mode: reader не блокирует writer, несколько concurrent readers
  • busy_timeout=5000: writer-конкуренция ждёт до 5 секунд
  • SetMaxOpenConns(128): пул соединений для параллельных reads при стриминге. Исторически SetMaxOpenConns(1) вызывал deadlock — pending-сообщение блокировалось в database/sql pool.
Таблица Назначение Ключевые поля
sessions Метаданные сессий id, cwd, parent_session, created_at, leaf_id, deleted_at, last_active_at, session_type, name_source
entries Сообщения и стримы id, session_id, parent_id, type, timestamp, data
session_meta Key-value пары сессии session_id, key, value (PK: оба)
entries_fts FTS5-индекс для полнотекстового поиска text (virtual table)
todos Todo-листы (JSON-массив) session_id, data, updated_at
pending_messages Очередь сообщений id, session_id, message_id, data, created_at
scheduler_tasks Задачи планировщика id, type, session_id, prompt, scheduled_for, repeat_spec, status, locked_at, locked_by, created_at, last_error
kv_store Глобальное key-value key, value, updated_at
multiagent_children Дочерние агенты child_id, task_id, parent_id, role, model, status, trace_id, created_at, completed_at
 1CREATE TABLE sessions (
 2    id             TEXT PRIMARY KEY,
 3    version        INTEGER NOT NULL,
 4    cwd            TEXT NOT NULL,
 5    parent_session TEXT,
 6    created_at     TEXT NOT NULL,
 7    leaf_id        TEXT,
 8    name           TEXT DEFAULT '',
 9    deleted_at     TEXT,        -- миграция: soft-delete
10    locked_at      TEXT,        -- миграция: PID lock
11    locked_pid     INTEGER,     -- миграция: PID locker
12    running        INTEGER DEFAULT 0,  -- миграция: activity flag
13    session_type   TEXT NOT NULL DEFAULT 'interactive',  -- миграция
14    name_source    TEXT DEFAULT '',    -- миграция
15    last_active_at TEXT          -- миграция: сортировка "most recent"
16);
1CREATE INDEX idx_entries_session   ON entries(session_id);
2CREATE INDEX idx_entries_parent     ON entries(parent_id);
3CREATE INDEX idx_pending_session    ON pending_messages(session_id);
4CREATE INDEX idx_scheduler_due      ON scheduler_tasks(scheduled_for);
5CREATE INDEX idx_scheduler_status   ON scheduler_tasks(status);
6CREATE INDEX idx_multiagent_parent  ON multiagent_children(parent_id);
7CREATE INDEX idx_multiagent_task    ON multiagent_children(task_id);

Шесть inline-миграций в Open(), применяются при открытии. Все идемпотентны: проверяют PRAGMA table_info перед ALTER TABLE.

Миграция Добавляет Назначение
migrateDeletedAt deleted_at TEXT Soft-delete сессий
migrateLockColumns locked_at, locked_pid, running Process locking
migrateSessionType session_type TEXT Batch vs interactive
migrateNameSource name_source TEXT User-set vs auto name
migrateLastActiveAt last_active_at TEXT + backfill Сортировка по свежести
migrateGoalIDToTaskID переименование goal_idtask_id Ясность семантики

entries_fts — virtual table FTS5 для поиска по содержимому entries.data.

Поддержка:

  • Булевы операторы: AND (неявный), OR, NOT
  • Prefix matching: config* → «configuration», «configure»
  • Фразовый поиск: "git reset" — точное совпадение
  • Комбинирование: bug OR fix AND "user input"

Результаты включают:

  • Bookends — первые 2 и последние 2 сообщения сессии (цель → результат)
  • Контекст — ±3 сообщения вокруг совпадения с маркером >>>

kv_store — глобальное key-value хранилище для operational state между сессиями: курсоры, last-processed IDs, чекпоинты обработки.

1CREATE TABLE kv_store (
2    key        TEXT PRIMARY KEY,
3    value      TEXT,
4    updated_at TEXT
5);

Ключи глобальные, namespaced: email:last_id, sync:cursor, task:cleanup:last_run.

Файл Назначение
internal/config/config.go Load, Save, CurrentModel, MiniModel, DreamModelName, ScheduleModelName, ResolveModel, validate
internal/config/config.gen.go Generated from JSON Schema (go-jsonschema)
internal/config/paths.go XDG path resolution
internal/config/migrator/migrator.go Version, Migrator, Register, Migrate
internal/config/migrations/migrations.go init()register("1.0.0", migrateToV1_0_0)
schemes/config.schema.json JSON Schema для автодополнения в редакторе
Файл Назначение
internal/storage/db.go Open, schema, inline migrations, DSN
internal/storage/session_repo.go SessionRepo — CRUD сессий
internal/storage/session_entries.go Entries — сообщения внутри сессий
internal/storage/fts.go FTS5 index management
internal/storage/search.go Полнотекстовый поиск (search_sessions)
internal/storage/search_extras.go Bookends, context around match
internal/storage/scheduler_tasks.go Scheduler tasks CRUD
internal/storage/pending_messages.go Pending messages queue
internal/storage/kv_store.go KV store CRUD
internal/storage/session_stats.go Статистика сессий
internal/storage/session_lock.go Session locking (lock/unlock/heartbeat)
internal/storage/session_lock_unix.go Unix-specific lock implementation
internal/storage/session_lock_windows.go Windows-specific lock implementation