docs(memory): reindex + update SKILL.md (#104)
* feat(memory): add OPENAI_EMBEDDING_BATCH_DELAY support * docs(memory): update SKILL.md for progressive enhancement and auto-setup * docs(memory): update AGENTS.md tool usage policy * docs(memory): fix stale snake_case tool references in skills, agents, tools * docs(handoff): add handoff + ADR for reindex-skill-update * docs(handoff): set PR number * refactor(memory): extract _embed_in_batches to satisfy xenon rank A --------- Co-authored-by: opencode-agent <agent@opencode.local>
This commit is contained in:
parent
9d654a2201
commit
4cf149cb02
18 changed files with 233 additions and 41 deletions
|
|
@ -11,6 +11,7 @@ OPENAI_BASE_URL=https://openrouter.ai/api/v1
|
||||||
OPENAI_API_KEY=your-openrouter-api-key
|
OPENAI_API_KEY=your-openrouter-api-key
|
||||||
OPENAI_EMBEDDING_MODEL=qwen/qwen3-embedding-8b
|
OPENAI_EMBEDDING_MODEL=qwen/qwen3-embedding-8b
|
||||||
OPENAI_EMBEDDING_BATCH_SIZE=50
|
OPENAI_EMBEDDING_BATCH_SIZE=50
|
||||||
|
OPENAI_EMBEDDING_BATCH_DELAY=0.5
|
||||||
MEMORY_CHUNK_SIZE=512
|
MEMORY_CHUNK_SIZE=512
|
||||||
MEMORY_CHUNK_OVERLAP=64
|
MEMORY_CHUNK_OVERLAP=64
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ permission:
|
||||||
"gh issue view*": allow
|
"gh issue view*": allow
|
||||||
---
|
---
|
||||||
|
|
||||||
You are a memory-syncer agent. Your job: distill durable knowledge from a merged PR handoff into the global memory file at `app_data/opencode-memory/repos/{host}/{org}/{repo}.md`.
|
You are a memory-syncer agent. Your job: distill durable knowledge from a merged PR handoff into the global memory file at `<memory_dir>/repos/{host}/{org}/{repo}.md` (default `~/.local/share/opencode/opencode-memory`, override via `OPENCODE_MEMORY_DIR`).
|
||||||
|
|
||||||
You are **read-only on the repository** and **write-only on memory**. You CANNOT commit, push, or add files to the repo — the permission set physically prevents it (`git push`, `git commit`, `git add` are absent from the allow-list; catch-all `"*": deny` blocks them). This is a deterministic guard against pushing to master, replacing the prompt-level rule that was previously bypassed by invocation prompts.
|
You are **read-only on the repository** and **write-only on memory**. You CANNOT commit, push, or add files to the repo — the permission set physically prevents it (`git push`, `git commit`, `git add` are absent from the allow-list; catch-all `"*": deny` blocks them). This is a deterministic guard against pushing to master, replacing the prompt-level rule that was previously bypassed by invocation prompts.
|
||||||
|
|
||||||
|
|
@ -41,7 +41,7 @@ You are **read-only on the repository** and **write-only on memory**. You CANNOT
|
||||||
1. Get the PR number from the invocation prompt.
|
1. Get the PR number from the invocation prompt.
|
||||||
2. Find the merged handoff file: `ls docs/handoff/pr-<N>-*` to discover the slug, then `cat docs/handoff/pr-<N>-<slug>.md` to read it (read-only — agent does not check out branches).
|
2. Find the merged handoff file: `ls docs/handoff/pr-<N>-*` to discover the slug, then `cat docs/handoff/pr-<N>-<slug>.md` to read it (read-only — agent does not check out branches).
|
||||||
3. Determine the repo: `git remote get-url origin` → parse `{host}/{org}/{repo}` (e.g. `github.com/slaid098/opencode-config`).
|
3. Determine the repo: `git remote get-url origin` → parse `{host}/{org}/{repo}` (e.g. `github.com/slaid098/opencode-config`).
|
||||||
4. Resolve memory path: read `OPENCODE_MEMORY_DIR` env var (set globally via docker-compose; fallback is `app_data/opencode-memory/` for local dev) → `<memory_dir>/repos/{host}/{org}/{repo}.md`. Use `printenv OPENCODE_MEMORY_DIR` to inspect it.
|
4. Resolve memory path: read `OPENCODE_MEMORY_DIR` env var (set globally via docker-compose; fallback `~/.local/share/opencode/opencode-memory/`) → `<memory_dir>/repos/{host}/{org}/{repo}.md`. Use `printenv OPENCODE_MEMORY_DIR` to inspect it.
|
||||||
5. Open the memory file (create if missing) via the `edit`/`write` tool — `edit: allow` permits this. The memory dir is an isolated git repo (post-commit hook auto-pushes), separate from the main repo.
|
5. Open the memory file (create if missing) via the `edit`/`write` tool — `edit: allow` permits this. The memory dir is an isolated git repo (post-commit hook auto-pushes), separate from the main repo.
|
||||||
|
|
||||||
## Distillation
|
## Distillation
|
||||||
|
|
@ -80,14 +80,14 @@ If a fact is already recorded — update the entry (bump `updated` in frontmatte
|
||||||
|
|
||||||
## Save
|
## Save
|
||||||
|
|
||||||
1. After editing the memory file, call `memory_save` to commit + re-index the isolated memory repo.
|
1. After editing the memory file, call `memory-save` to commit + re-index the isolated memory repo.
|
||||||
2. **Guard**: run `git status` on the main repo. If anything under `app_data/` is staged (should not happen — `memory_save` commits to the isolated memory repo, not the main repo), report it to the user. **You CANNOT fix this yourself** — `git restore` is not in the allow-list (the agent must not touch the repo). Inform the user so they can run `git restore --staged app_data/` manually.
|
2. **Guard**: run `git status` on the main repo. If anything under the memory dir is staged (should not happen — `memory-save` commits to the isolated memory repo, not the main repo), report it to the user. **You CANNOT fix this yourself** — `git restore` is not in the allow-list (the agent must not touch the repo). Inform the user so they can run `git restore --staged <path>` manually.
|
||||||
|
|
||||||
## Rules
|
## Rules
|
||||||
|
|
||||||
1. NEVER call `git push`, `git commit`, `git add` — they are not in the allow-list and will be denied by the catch-all rule.
|
1. NEVER call `git push`, `git commit`, `git add` — they are not in the allow-list and will be denied by the catch-all rule.
|
||||||
2. NEVER checkout branches or pull — you operate on the current state of the default branch (already merged).
|
2. NEVER checkout branches or pull — you operate on the current state of the default branch (already merged).
|
||||||
3. ONLY edit files under `app_data/opencode-memory/repos/{host}/{org}/{repo}.md`.
|
3. ONLY edit files under `<memory_dir>/repos/{host}/{org}/{repo}.md`.
|
||||||
4. ONLY read files under `docs/handoff/` and `docs/decisions/`.
|
4. ONLY read files under `docs/handoff/` and `docs/decisions/`.
|
||||||
5. Receipt is mandatory even if no durable records found.
|
5. Receipt is mandatory even if no durable records found.
|
||||||
6. If memory file doesn't exist — create it with proper frontmatter (title, tags, summary, created, updated, importance).
|
6. If memory file doesn't exist — create it with proper frontmatter (title, tags, summary, created, updated, importance).
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,4 @@
|
||||||
description: Run spec — interactive spec generation for new project
|
description: Run spec — interactive spec generation for new project
|
||||||
agent: build
|
agent: build
|
||||||
---
|
---
|
||||||
Load the `spec` skill via `skill({name: "spec"})` and follow its ПРОТОКОЛ strictly. Главный агент — оркестратор: `spec-status` tool (read-only) + вопрос юзеру + task(general) делегирование. Не делает edit/memory_search/gh сам. Каждая фаза = 1 subagent. Стоп на issues — дальше юзер сам /run-pipeline.
|
Load the `spec` skill via `skill({name: "spec"})` and follow its ПРОТОКОЛ strictly. Главный агент — оркестратор: `spec-status` tool (read-only) + вопрос юзеру + task(general) делегирование. Не делает edit/memory-search/gh сам. Каждая фаза = 1 subagent. Стоп на issues — дальше юзер сам /run-pipeline.
|
||||||
|
|
@ -73,7 +73,7 @@ git status # проверь staged set — только SKILL.md, без лиш
|
||||||
```
|
```
|
||||||
|
|
||||||
> `commit` tool НЕ делает `git add` — коммитит только уже staged файлы. Если
|
> `commit` tool НЕ делает `git add` — коммитит только уже staged файлы. Если
|
||||||
> в индексе лишнее (например `memory_save` stage'нул всё через `git add -A`)
|
> в индексе лишнее (например `memory-save` stage'нул всё через `git add -A`)
|
||||||
> — не коммить: сначала `git restore --staged <file>` или не stage'и его
|
> — не коммить: сначала `git restore --staged <file>` или не stage'и его
|
||||||
> изначально. Используй `git add <конкретные-пути>`, НЕ `git add -A`.
|
> изначально. Используй `git add <конкретные-пути>`, НЕ `git add -A`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ description: Use when adding, changing, or removing MCP servers, providers, perm
|
||||||
- Перед каждым `commit` — `git status` для проверки staged set. `commit` tool
|
- Перед каждым `commit` — `git status` для проверки staged set. `commit` tool
|
||||||
НЕ делает `git add` — коммитит только уже staged файлы. Используй
|
НЕ делает `git add` — коммитит только уже staged файлы. Используй
|
||||||
`git add <конкретные-пути>`, НЕ `git add -A` (иначе лишние файлы уйдут в
|
`git add <конкретные-пути>`, НЕ `git add -A` (иначе лишние файлы уйдут в
|
||||||
коммит). Если в индексе лишнее (например `memory_save` stage'нул всё через
|
коммит). Если в индексе лишнее (например `memory-save` stage'нул всё через
|
||||||
`git add -A`) — сначала `git restore --staged <file>`, потом коммить.
|
`git add -A`) — сначала `git restore --staged <file>`, потом коммить.
|
||||||
- В клонах: `git pull` + рестарт opencode (MCP-серверы, skills, agents грузятся при старте — см. `add-skill/SKILL.md`). До рестарта правки не видны.
|
- В клонах: `git pull` + рестарт opencode (MCP-серверы, skills, agents грузятся при старте — см. `add-skill/SKILL.md`). До рестарта правки не видны.
|
||||||
- Для Docker-сетапа: `git pull` на хосте + `docker compose restart opencode` (или эквивалент) — MCP/skills/agents грузятся при старте контейнера.
|
- Для Docker-сетапа: `git pull` на хосте + `docker compose restart opencode` (или эквивалент) — MCP/skills/agents грузятся при старте контейнера.
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,6 @@ Label выбирай по типу задачи (совпадает с commit `t
|
||||||
4. **Merge or Repeat** — APPROVE → `merge-pr({ pr_number: N })` tool (squash +
|
4. **Merge or Repeat** — APPROVE → `merge-pr({ pr_number: N })` tool (squash +
|
||||||
delete branch, без `--admin`; НЕ raw `gh pr merge` — заблокирован deny),
|
delete branch, без `--admin`; НЕ raw `gh pr merge` — заблокирован deny),
|
||||||
после CI ✅; замечания → fix subagent → re-review → merge.
|
после CI ✅; замечания → fix subagent → re-review → merge.
|
||||||
5. **Memory-sync** — `@memory-syncer` дистиллирует handoff + ADR в `app_data/opencode-memory/repos/{host}/{org}/{repo}.md`.
|
5. **Memory-sync** — `@memory-syncer` дистиллирует handoff + ADR в `<memory_dir>/repos/{host}/{org}/{repo}.md`.
|
||||||
|
|
||||||
См. `AGENTS.md` (Development Workflow) и `run-pipeline` skill — все три документа описывают одну и ту же full-subagent модель делегирования.
|
См. `AGENTS.md` (Development Workflow) и `run-pipeline` skill — все три документа описывают одну и ту же full-subagent модель делегирования.
|
||||||
|
|
@ -6,7 +6,15 @@ description: Инструкция по работе с файловой памя
|
||||||
# File Memory (opencode-memory)
|
# File Memory (opencode-memory)
|
||||||
|
|
||||||
Графовая память (Graphiti/FalkorDB) удалена — была нестабильна и забагована.
|
Графовая память (Graphiti/FalkorDB) удалена — была нестабильна и забагована.
|
||||||
Память работает через файловую систему с keyword + semantic search — 5 TS tools (`.opencode/tools/memory-*.ts`), вызывают `python3 -m src.memory`. Плагин `@mathew-cf/opencode-memory` удалён.
|
Память работает через файловую систему с keyword + semantic search — 5 TS tools (`.opencode/tools/memory-*.ts`), вызывают `python3 -m src.memory` напрямую (без wrapper-hop, без MCP-плагина).
|
||||||
|
|
||||||
|
## Архитектура (progressive enhancement)
|
||||||
|
|
||||||
|
- **Keyword search** — всегда работает (ripgrep по `.md` файлам). Не требует env vars, не требует индекса.
|
||||||
|
- **Semantic search** — включается если set `OPENAI_BASE_URL` + `OPENAI_API_KEY`. Embeddings через OpenRouter (`qwen/qwen3-embedding-8b`, 4096-dim), индекс в `.rag/index.json` (Python формат, `meta.json` с `version` key).
|
||||||
|
- **Fallback**: OpenRouter недоступен / упал → semantic возвращает `[]` → `memory-search` деградирует к keyword-only (работает, но semantic enhancement потерян). Не блокирует работу.
|
||||||
|
- **Auto-setup**: первый `memory-save` всё создаёт — `mkdir` категорий, `git init` (или `clone` если `OPENCODE_MEMORY_REMOTE` set), `post-commit` hook (auto-push) если remote set. Zero-config: новый пользователь без remote может начать писать память сразу.
|
||||||
|
- **`memory-doctor`** — read-only диагностика: проверяет ripgrep, Python `src.memory`, env vars, индекс. Не модифицирует ничего.
|
||||||
|
|
||||||
## Инструменты
|
## Инструменты
|
||||||
|
|
||||||
|
|
@ -14,7 +22,7 @@ description: Инструкция по работе с файловой памя
|
||||||
|---|---|
|
|---|---|
|
||||||
| `memory-search({ query, category? })` | Гибридный поиск (ripgrep keyword + Python semantic) |
|
| `memory-search({ query, category? })` | Гибридный поиск (ripgrep keyword + Python semantic) |
|
||||||
| `memory-list({ category? })` | Список категорий / файлов |
|
| `memory-list({ category? })` | Список категорий / файлов |
|
||||||
| `memory-save()` | Commit + reindex после записи/редактирования (auto-setup: git init + hook если `OPENCODE_MEMORY_REMOTE` set) |
|
| `memory-save()` | Commit + reindex после записи/редактирования (auto-setup: git init/clone + hook если `OPENCODE_MEMORY_REMOTE` set) |
|
||||||
| `memory-access({ path })` | Отметить файл как прочитанный (bump `last_accessed`/`access_count`) |
|
| `memory-access({ path })` | Отметить файл как прочитанный (bump `last_accessed`/`access_count`) |
|
||||||
| `memory-doctor()` | Read-only диагностика: ripgrep, Python `src.memory`, env vars, index |
|
| `memory-doctor()` | Read-only диагностика: ripgrep, Python `src.memory`, env vars, index |
|
||||||
|
|
||||||
|
|
@ -67,7 +75,7 @@ Default: `/root/.local/share/opencode/opencode-memory` (переопределя
|
||||||
{memory-dir}/repos/{host}/{org}/{repo}.md
|
{memory-dir}/repos/{host}/{org}/{repo}.md
|
||||||
```
|
```
|
||||||
|
|
||||||
Используй один путь последовательно во всех примерах — default (`~/opencode-memory`) или override (`app_data/opencode-memory`), но не оба сразу.
|
Используй один путь последовательно во всех примерах — default (`/root/.local/share/opencode/opencode-memory`) или override (`OPENCODE_MEMORY_DIR`), но не оба сразу.
|
||||||
|
|
||||||
### Формат записей
|
### Формат записей
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ git status # проверь staged set — только CHANGELOG.md, без л
|
||||||
```
|
```
|
||||||
|
|
||||||
> `commit` tool НЕ делает `git add` — коммитит только уже staged файлы. Если
|
> `commit` tool НЕ делает `git add` — коммитит только уже staged файлы. Если
|
||||||
> в индексе лишнее (например `memory_save` stage'нул всё через `git add -A`)
|
> в индексе лишнее (например `memory-save` stage'нул всё через `git add -A`)
|
||||||
> — не коммить: сначала `git restore --staged <file>` или не stage'и его
|
> — не коммить: сначала `git restore --staged <file>` или не stage'и его
|
||||||
> изначально. Используй `git add <конкретные-пути>`, НЕ `git add -A`.
|
> изначально. Используй `git add <конкретные-пути>`, НЕ `git add -A`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ git status # проверь staged set — только README.md, без ли
|
||||||
```
|
```
|
||||||
|
|
||||||
> `commit` tool НЕ делает `git add` — коммитит только уже staged файлы. Если
|
> `commit` tool НЕ делает `git add` — коммитит только уже staged файлы. Если
|
||||||
> в индексе лишнее (например `memory_save` stage'нул всё через `git add -A`)
|
> в индексе лишнее (например `memory-save` stage'нул всё через `git add -A`)
|
||||||
> — не коммить: сначала `git restore --staged <file>` или не stage'и его
|
> — не коммить: сначала `git restore --staged <file>` или не stage'и его
|
||||||
> изначально. Используй `git add <конкретные-пути>`, НЕ `git add -A`.
|
> изначально. Используй `git add <конкретные-пути>`, НЕ `git add -A`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ description: Автономный исполнитель PR-пайплайна.
|
||||||
Перед КАЖДЫМ `commit` — `git status` для проверки staged set. `commit`
|
Перед КАЖДЫМ `commit` — `git status` для проверки staged set. `commit`
|
||||||
tool НЕ делает `git add` — коммитит только уже staged файлы. Используй
|
tool НЕ делает `git add` — коммитит только уже staged файлы. Используй
|
||||||
`git add <конкретные-пути>`, НЕ `git add -A` (иначе лишние файлы уйдут в
|
`git add <конкретные-пути>`, НЕ `git add -A` (иначе лишние файлы уйдут в
|
||||||
коммит). Если в индексе лишнее (например `memory_save` stage'нул всё
|
коммит). Если в индексе лишнее (например `memory-save` stage'нул всё
|
||||||
через `git add -A`) — не коммить: сначала `git restore --staged <file>`.
|
через `git add -A`) — не коммить: сначала `git restore --staged <file>`.
|
||||||
5. Push ветку (`git push -u origin HEAD`), затем создай PR через tool:
|
5. Push ветку (`git push -u origin HEAD`), затем создай PR через tool:
|
||||||
`create-pr({ title: "type(scope): description", body: "## Что сделано\n...\n\n## Почему\n...\n\nCloses #N", issue_number: N })`.
|
`create-pr({ title: "type(scope): description", body: "## Что сделано\n...\n\n## Почему\n...\n\nCloses #N", issue_number: N })`.
|
||||||
|
|
@ -136,8 +136,9 @@ Log: `gh run view <run-id> --log-failed` output:
|
||||||
|
|
||||||
```
|
```
|
||||||
Дистиллируй PR#M в memory file
|
Дистиллируй PR#M в memory file
|
||||||
`app_data/opencode-memory/repos/{host}/{org}/{repo}.md`
|
`<memory_dir>/repos/{host}/{org}/{repo}.md`
|
||||||
(путь относительно корня репо; `{host}/{org}/{repo}` вычисли через
|
(default `~/.local/share/opencode/opencode-memory`, override через `OPENCODE_MEMORY_DIR`;
|
||||||
|
`{host}/{org}/{repo}` вычисли через
|
||||||
`git remote get-url origin` — см. `memory-syncer.md:38`).
|
`git remote get-url origin` — см. `memory-syncer.md:38`).
|
||||||
1. Прочитай `docs/handoff/pr-M-*.md` и `docs/decisions/*-pr-M-*.md` с master
|
1. Прочитай `docs/handoff/pr-M-*.md` и `docs/decisions/*-pr-M-*.md` с master
|
||||||
(`git checkout master && git pull`).
|
(`git checkout master && git pull`).
|
||||||
|
|
@ -146,8 +147,8 @@ Log: `gh run view <run-id> --log-failed` output:
|
||||||
3. Добавь записи формата `- [YYYY-MM-DD, PR#M] <summary>` в конец файла
|
3. Добавь записи формата `- [YYYY-MM-DD, PR#M] <summary>` в конец файла
|
||||||
(секция "Handoff digest").
|
(секция "Handoff digest").
|
||||||
4. Квитанция ВСЕГДА (даже если durable нет): `- [date, PR#M] — (нет durable-записей)`.
|
4. Квитанция ВСЕГДА (даже если durable нет): `- [date, PR#M] — (нет durable-записей)`.
|
||||||
5. `memory_save` для commit + reindex + push.
|
5. `memory-save` для commit + reindex + push.
|
||||||
6. Проверь `git status` основного репо — если staged что-то в `app_data/`,
|
6. Проверь `git status` основного репо — если staged что-то в memory dir,
|
||||||
репорт пользователю (guard от случайного коммита в master).
|
репорт пользователю (guard от случайного коммита в master).
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ truth для порядка и действий — `spec-status` tool. На в
|
||||||
- Импровизировать порядок. Решать сам какую фазу выполнять — читай `NEXT:`.
|
- Импровизировать порядок. Решать сам какую фазу выполнять — читай `NEXT:`.
|
||||||
- Пропускать вызов `spec-status`, даже если «кажется, что фаза уже ✅» — скрипт решает.
|
- Пропускать вызов `spec-status`, даже если «кажется, что фаза уже ✅» — скрипт решает.
|
||||||
- bash-запуск `python3 .opencode/scripts/spec-status.py` — детерминированный deny-rule (см. ADR-019). Только нативный tool `spec-status`.
|
- bash-запуск `python3 .opencode/scripts/spec-status.py` — детерминированный deny-rule (см. ADR-019). Только нативный tool `spec-status`.
|
||||||
- Главному агенту: edit/write/read файлов (всё через subagent), memory_search (через subagent), gh issue create (через subagent).
|
- Главному агенту: edit/write/read файлов (всё через subagent), memory-search (через subagent), gh issue create (через subagent).
|
||||||
- Формулировать вопросы не из question templates ниже.
|
- Формулировать вопросы не из question templates ниже.
|
||||||
- Предлагать стек вне hardcoded default stack по типу проекта.
|
- Предлагать стек вне hardcoded default stack по типу проекта.
|
||||||
- Запускать /run-pipeline (стоп на issues — дальше юзер сам).
|
- Запускать /run-pipeline (стоп на issues — дальше юзер сам).
|
||||||
|
|
@ -119,7 +119,7 @@ Prompt template C (см. ниже).
|
||||||
Опиши модули (1 строка на модуль):
|
Опиши модули (1 строка на модуль):
|
||||||
```
|
```
|
||||||
|
|
||||||
Prompt template D (см. ниже, с memory_search).
|
Prompt template D (см. ниже, с memory-search).
|
||||||
|
|
||||||
### Phase 4: DB_SCHEMA (вопрос юзеру + subagent)
|
### Phase 4: DB_SCHEMA (вопрос юзеру + subagent)
|
||||||
|
|
||||||
|
|
@ -208,7 +208,7 @@ Spec complete. Issues: #N1, #N2, ...
|
||||||
status: in_progress
|
status: in_progress
|
||||||
---
|
---
|
||||||
(создай директорию docs/spec/ через `mkdir -p docs/spec` если не существует)
|
(создай директорию docs/spec/ через `mkdir -p docs/spec` если не существует)
|
||||||
4. `memory_search("reference repo")` → верни список релевантных memory paths.
|
4. `memory-search("reference repo")` → верни список релевантных memory paths.
|
||||||
5. Верни: {spec_exists: bool, current_phase: int, references: [...]}.
|
5. Верни: {spec_exists: bool, current_phase: int, references: [...]}.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -243,12 +243,12 @@ Default stack для типа (хардкод, добавить всегда):
|
||||||
4. Верни: "done: stack.md created, <N>/<M> mandatory items".
|
4. Верни: "done: stack.md created, <N>/<M> mandatory items".
|
||||||
```
|
```
|
||||||
|
|
||||||
### Template D (modules / Phase 3, с memory_search)
|
### Template D (modules / Phase 3, с memory-search)
|
||||||
|
|
||||||
```
|
```
|
||||||
Контекст: Phase 3 (modules) для проекта типа <type>.
|
Контекст: Phase 3 (modules) для проекта типа <type>.
|
||||||
Ответ юзера: <answers>.
|
Ответ юзера: <answers>.
|
||||||
1. memory_search("reference repo <module>") — доберёт паттерны из reference repos.
|
1. memory-search("reference repo <module>") — доберёт паттерны из reference repos.
|
||||||
2. Сформируй ## Модули (bullet list) + ## Структура (дерево) на основе ответа + референсов.
|
2. Сформируй ## Модули (bullet list) + ## Структура (дерево) на основе ответа + референсов.
|
||||||
3. Создай docs/spec/modules.md с обеими секциями. edit docs/spec/meta.md frontmatter phase=3.
|
3. Создай docs/spec/modules.md с обеими секциями. edit docs/spec/meta.md frontmatter phase=3.
|
||||||
4. Верни summary (5-10 строк) для показа юзеру.
|
4. Верни summary (5-10 строк) для показа юзеру.
|
||||||
|
|
@ -320,5 +320,5 @@ Default stack для типа (хардкод, добавить всегда):
|
||||||
- Скрипт read-only (только presence check в `docs/spec/*.md`, без мутаций).
|
- Скрипт read-only (только presence check в `docs/spec/*.md`, без мутаций).
|
||||||
- После каждой фазы → 1 строка прогресса юзеру.
|
- После каждой фазы → 1 строка прогресса юзеру.
|
||||||
- Если subagent error → 1 retry, потом STOP + report пользователю.
|
- Если subagent error → 1 retry, потом STOP + report пользователю.
|
||||||
- Главный агент = оркестратор: `spec-status` tool + вопрос юзеру + task(general) делегирование. Не делает edit/memory_search/gh сам.
|
- Главный агент = оркестратор: `spec-status` tool + вопрос юзеру + task(general) делегирование. Не делает edit/memory-search/gh сам.
|
||||||
- Стоп на issues — дальше юзер сам /run-pipeline.
|
- Стоп на issues — дальше юзер сам /run-pipeline.
|
||||||
|
|
@ -30,7 +30,7 @@ export default tool({
|
||||||
"Record that a memory file was accessed (read and used). Updates last_accessed date and increments access_count in frontmatter. " +
|
"Record that a memory file was accessed (read and used). Updates last_accessed date and increments access_count in frontmatter. " +
|
||||||
"Call this AFTER reading a memory file that you actually used to inform your work — not for casual browsing. " +
|
"Call this AFTER reading a memory file that you actually used to inform your work — not for casual browsing. " +
|
||||||
"This helps the memory system track which memories are actively useful vs. stale. " +
|
"This helps the memory system track which memories are actively useful vs. stale. " +
|
||||||
"Atomic write (tmp + rename). Does NOT commit — the next memory_save will sync the change.",
|
"Atomic write (tmp + rename). Does NOT commit — the next memory-save will sync the change.",
|
||||||
args: {
|
args: {
|
||||||
path: tool.schema
|
path: tool.schema
|
||||||
.string()
|
.string()
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ function checkIndex(memoryDir: string): string[] {
|
||||||
const lines: string[] = []
|
const lines: string[] = []
|
||||||
lines.push(
|
lines.push(
|
||||||
`- ${mark(fs.existsSync(memoryDir))} OPENCODE_MEMORY_DIR exists: ${memoryDir}${
|
`- ${mark(fs.existsSync(memoryDir))} OPENCODE_MEMORY_DIR exists: ${memoryDir}${
|
||||||
fs.existsSync(memoryDir) ? "" : "— run memory_save to auto-setup"
|
fs.existsSync(memoryDir) ? "" : "— run memory-save to auto-setup"
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
if (!fs.existsSync(memoryDir)) return lines
|
if (!fs.existsSync(memoryDir)) return lines
|
||||||
|
|
@ -64,7 +64,7 @@ function checkIndex(memoryDir: string): string[] {
|
||||||
const gitDir = path.join(memoryDir, ".git")
|
const gitDir = path.join(memoryDir, ".git")
|
||||||
lines.push(
|
lines.push(
|
||||||
`- ${mark(fs.existsSync(gitDir))} memory dir is a git repo: ${
|
`- ${mark(fs.existsSync(gitDir))} memory dir is a git repo: ${
|
||||||
fs.existsSync(gitDir) ? "yes" : "no — will be init on first memory_save"
|
fs.existsSync(gitDir) ? "yes" : "no — will be init on first memory-save"
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
const indexJson = path.join(memoryDir, ".rag", "index.json")
|
const indexJson = path.join(memoryDir, ".rag", "index.json")
|
||||||
|
|
@ -72,7 +72,7 @@ function checkIndex(memoryDir: string): string[] {
|
||||||
`- ${mark(fs.existsSync(indexJson))} RAG index exists: ${
|
`- ${mark(fs.existsSync(indexJson))} RAG index exists: ${
|
||||||
fs.existsSync(indexJson)
|
fs.existsSync(indexJson)
|
||||||
? path.relative(memoryDir, indexJson)
|
? path.relative(memoryDir, indexJson)
|
||||||
: "not built — first memory_save with OPENAI_BASE_URL will trigger reindex"
|
: "not built — first memory-save with OPENAI_BASE_URL will trigger reindex"
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
if (fs.existsSync(indexJson)) {
|
if (fs.existsSync(indexJson)) {
|
||||||
|
|
@ -106,7 +106,7 @@ export default tool({
|
||||||
description:
|
description:
|
||||||
"Read-only diagnostic for the memory subsystem. Reports: ripgrep (keyword) availability, Python src.memory importability, " +
|
"Read-only diagnostic for the memory subsystem. Reports: ripgrep (keyword) availability, Python src.memory importability, " +
|
||||||
"env vars (OPENAI_BASE_URL, OPENAI_API_KEY, OPENCODE_MEMORY_REMOTE), memory dir existence, RAG index existence. " +
|
"env vars (OPENAI_BASE_URL, OPENAI_API_KEY, OPENCODE_MEMORY_REMOTE), memory dir existence, RAG index existence. " +
|
||||||
"Does NOT modify anything. Run when memory_search/memory_save misbehave or to verify setup.",
|
"Does NOT modify anything. Run when memory-search/memory-save misbehave or to verify setup.",
|
||||||
args: {},
|
args: {},
|
||||||
async execute(_args, context) {
|
async execute(_args, context) {
|
||||||
const memoryDir = resolveMemoryDir()
|
const memoryDir = resolveMemoryDir()
|
||||||
|
|
|
||||||
|
|
@ -305,7 +305,7 @@ export default tool({
|
||||||
const rgBin = resolveRgBinary({ allowSystemFallback: true })
|
const rgBin = resolveRgBinary({ allowSystemFallback: true })
|
||||||
|
|
||||||
if (!fs.existsSync(memoryDir)) {
|
if (!fs.existsSync(memoryDir)) {
|
||||||
return `No memories found for: "${args.query}" (memory directory not initialized — run memory_save to auto-setup)`
|
return `No memories found for: "${args.query}" (memory directory not initialized — run memory-save to auto-setup)`
|
||||||
}
|
}
|
||||||
|
|
||||||
let resultMap = new Map<string, CandInfo>()
|
let resultMap = new Map<string, CandInfo>()
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ fallback на raw bash, НЕ импровизируй обход через `gh
|
||||||
| `post-docs-review({ pr_number, verdict, body })` | `gh pr comment` для verdict | Docs review verdict (heading `## Docs Review Summary` + verdict) | Сообщи оркестратору, не `gh pr comment` |
|
| `post-docs-review({ pr_number, verdict, body })` | `gh pr comment` для verdict | Docs review verdict (heading `## Docs Review Summary` + verdict) | Сообщи оркестратору, не `gh pr comment` |
|
||||||
| `pipeline-status({ pr_number })` | `python3 .opencode/scripts/pipeline-status.py` | Read-only oracle: статус фаз PR + NEXT action | Сообщи оркестратору, не bash-запуск скрипта |
|
| `pipeline-status({ pr_number })` | `python3 .opencode/scripts/pipeline-status.py` | Read-only oracle: статус фаз PR + NEXT action | Сообщи оркестратору, не bash-запуск скрипта |
|
||||||
| `spec-status({})` | `python3 .opencode/scripts/spec-status.py` | Read-only oracle: текущая фаза spec + NEXT action | Сообщи оркестратору, не bash-запуск скрипта |
|
| `spec-status({})` | `python3 .opencode/scripts/spec-status.py` | Read-only oracle: текущая фаза spec + NEXT action | Сообщи оркестратору, не bash-запуск скрипта |
|
||||||
| `memory-doctor()` | — | Read-only диагностика памяти: ripgrep, Python src.memory, env vars, index (замена `memory-setup`) | Сообщи оркестратору, не raw bash |
|
| `memory-doctor()` | — | Read-only диагностика памяти: ripgrep, Python src.memory, env vars, index | Сообщи оркестратору, не raw bash |
|
||||||
| `memory-save()` | — | Commit + reindex opencode-memory после записи/редактирования (auto-setup: git init + hook если remote set) | Сообщи оркестратору, не raw bash |
|
| `memory-save()` | — | Commit + reindex opencode-memory после записи/редактирования (auto-setup: git init + hook если remote set) | Сообщи оркестратору, не raw bash |
|
||||||
| `memory-search({ query, category? })` | — | Гибридный поиск (ripgrep keyword + Python semantic) по opencode-memory | Сообщи оркестратору, не raw bash |
|
| `memory-search({ query, category? })` | — | Гибридный поиск (ripgrep keyword + Python semantic) по opencode-memory | Сообщи оркестратору, не raw bash |
|
||||||
| `memory-list({ category? })` | — | Список категорий / файлов opencode-memory | Сообщи оркестратору, не raw bash |
|
| `memory-list({ category? })` | — | Список категорий / файлов opencode-memory | Сообщи оркестратору, не raw bash |
|
||||||
|
|
|
||||||
67
docs/decisions/046-pr-104-reindex-skill-update.md
Normal file
67
docs/decisions/046-pr-104-reindex-skill-update.md
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
# ADR-046: Reindex memory + finalize kebab-case tool names in docs (PR #104)
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
Accepted (2026-07-26)
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
|
||||||
|
После серии PR #100–#103 (Python lazy-init, 5 TS tools, E2E tests, remove plugin + Rust)
|
||||||
|
инфраструктура памяти была готова, но:
|
||||||
|
1. Индекс `.rag/index.json` существовал (220 MB, 2379 entries), но `meta.json`
|
||||||
|
отсутствовал → Python incremental reindex не мог определить version mismatch
|
||||||
|
при будущих запусках (fallback к full reindex или silent skip).
|
||||||
|
2. Документация (SKILL.md, AGENTS.md, 6 skills, memory-syncer agent, 3 TS tools,
|
||||||
|
spec command) содержала stale snake_case tool names (`memory_save`,
|
||||||
|
`memory_search`) — агенты могли вызывать несуществующие tools и получать
|
||||||
|
"tool not found".
|
||||||
|
3. `OPENAI_EMBEDDING_BATCH_DELAY` была в issue #99 как требование (throttle
|
||||||
|
между батчами для OpenRouter rate limits), но `embedder.py` не поддерживал
|
||||||
|
эту env var — добавление в `.env.example` без поддержки в коде вводило бы
|
||||||
|
в заблуждение.
|
||||||
|
4. Memory file `technical/rag-cli-embeddings-model.md` описывал Rust rag-cli
|
||||||
|
(candle-transformers, all-MiniLM-L6-v2, 384-dim, GLIBC 2.39) как активный —
|
||||||
|
агенты могли пытаться использовать удалённый путь (PR #103 удалил Rust).
|
||||||
|
|
||||||
|
## Решение
|
||||||
|
|
||||||
|
1. **Переиндекс через Python** — `uv run python -m src.memory index`. Создан
|
||||||
|
`.rag/meta.json` с `version: "qwen/qwen3-embedding-8b:512:64"` (Python формат,
|
||||||
|
`version` key — НЕ `model_id` который был Rust). Incremental: 86 изменившихся
|
||||||
|
файлов, 286s, 2584 entries total (merged со старым index.json).
|
||||||
|
|
||||||
|
2. **`OPENAI_EMBEDDING_BATCH_DELAY` — поддержка в коде + `.env.example`**:
|
||||||
|
- `embedder.py`: `BATCH_DELAY = float(os.environ.get("OPENAI_EMBEDDING_BATCH_DELAY", "0"))`
|
||||||
|
- `time.sleep(BATCH_DELAY)` между батчами (только если `> 0` и не последний батч)
|
||||||
|
- `.env.example`: `OPENAI_EMBEDDING_BATCH_DELAY=0.5`
|
||||||
|
- Default `0` — backwards compatible, не ломает существующие вызовы.
|
||||||
|
|
||||||
|
3. **Stale references cleanup** — `memory_save`/`memory_search`/`memory_setup`
|
||||||
|
(snake_case) → `memory-save`/`memory-search`/`memory-doctor` (kebab-case) в
|
||||||
|
всех активных файлах (`.opencode/skills/`, `.opencode/agents/`, `.opencode/tools/`,
|
||||||
|
`.opencode/commands/`, `AGENTS.md`). Исторические `docs/decisions/` и
|
||||||
|
`docs/handoff/` НЕ тронуты (snake_case отражает реальные имена на момент
|
||||||
|
написания).
|
||||||
|
|
||||||
|
4. **Memory file `rag-cli-embeddings-model.md` переписан** — Rust rag-cli →
|
||||||
|
Python + OpenRouter как единственный путь semantic. Зафиксировано что удалено.
|
||||||
|
|
||||||
|
5. **SKILL.md (memory)** — добавлена секция "Архитектура (progressive
|
||||||
|
enhancement)": keyword всегда, semantic если `OPENAI_BASE_URL` set, fallback,
|
||||||
|
auto-setup, `memory-doctor` как read-only диагностика. Default путь исправлен:
|
||||||
|
`~/opencode-memory` → `/root/.local/share/opencode/opencode-memory`.
|
||||||
|
|
||||||
|
## Альтернативы
|
||||||
|
|
||||||
|
- **Не добавлять `OPENAI_EMBEDDING_BATCH_DELAY` в код, только в `.env.example`** —
|
||||||
|
отклонено: env var в `.env.example` без поддержки в коде вводит в заблуждение
|
||||||
|
(пользователь думает что delay работает, но код его игнорирует). Минимальная
|
||||||
|
правка embedder.py (3 строки) делает её рабочей.
|
||||||
|
|
||||||
|
- **Трогать исторические `docs/decisions/` и `docs/handoff/`** — отклонено: ADR
|
||||||
|
и handoffs — исторические записи, snake_case там отражает реальные имена tools
|
||||||
|
на момент написания (до PR #101 переименования). Правка исказила бы историю.
|
||||||
|
|
||||||
|
- **Полный reindex (удалить старый index.json)** — отклонено: incremental reindex
|
||||||
|
по SHA256 (ADR-036) достаточно. 86 изменившихся файлов переиндексировано,
|
||||||
|
остальные merged. Полный reindex = 13 минут + $0.0043, incremental = 286s +
|
||||||
|
$0.00012. Нет причины делать полный если incremental корректен.
|
||||||
104
docs/handoff/pr-104-reindex-skill-update.md
Normal file
104
docs/handoff/pr-104-reindex-skill-update.md
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
---
|
||||||
|
pr: 104
|
||||||
|
title: Reindex memory + update SKILL.md/AGENTS.md for kebab-case tools
|
||||||
|
---
|
||||||
|
|
||||||
|
## Что сделано
|
||||||
|
|
||||||
|
Финальная фаза починки памяти (issue #99, часть #94). После PR #100–#103
|
||||||
|
(Python safe, 5 TS tools, E2E tests, remove plugin + Rust) — переиндекс + docs.
|
||||||
|
|
||||||
|
### 1. Переиндекс памяти через Python
|
||||||
|
|
||||||
|
Запущен `uv run python -m src.memory index` из `/root/workspace/opencode-config`.
|
||||||
|
Env vars (`OPENAI_BASE_URL`, `OPENAI_API_KEY`, `OPENAI_EMBEDDING_MODEL=qwen/qwen3-embedding-8b`)
|
||||||
|
наследованы из окружения — OpenRouter ответил.
|
||||||
|
|
||||||
|
Результат:
|
||||||
|
- `.rag/index.json` — 239 MB (было 220 MB), 2584 entries (было 2379), 4096-dim embeddings
|
||||||
|
- `.rag/meta.json` — создан, `version: "qwen/qwen3-embedding-8b:512:64"` (Python формат, `version` key — НЕ `model_id` который был Rust)
|
||||||
|
- `.rag/reindex.log` — финальная строка `done: total=86 took=286s changed=86 unchanged=0 deleted=0` (incremental: 86 изменившихся файлов переиндексировано, остальные merged из старого index.json)
|
||||||
|
- `.rag/index.bin` — НЕ существует (Rust, удалён в PR #103)
|
||||||
|
- Время: 2026-07-26 22:55 (свежее)
|
||||||
|
|
||||||
|
### 2. `OPENAI_EMBEDDING_BATCH_DELAY` — добавлена поддержка в код + `.env.example`
|
||||||
|
|
||||||
|
Issue #99 просил добавить `OPENAI_EMBEDDING_BATCH_DELAY=0.5` в `.env.example`, но
|
||||||
|
embedder.py НЕ поддерживал эту env var. Добавлена:
|
||||||
|
- `src/memory/embedder.py`: `BATCH_DELAY = float(os.environ.get("OPENAI_EMBEDDING_BATCH_DELAY", "0"))` + `time.sleep(BATCH_DELAY)` между батчами (только если `BATCH_DELAY > 0` и не последний батч)
|
||||||
|
- `.env.example`: `OPENAI_EMBEDDING_BATCH_DELAY=0.5` с комментарием про OpenRouter rate limits
|
||||||
|
- Default `0` (без задержки) — backwards compatible, не ломает существующие вызовы
|
||||||
|
|
||||||
|
Проверка: env var уже set в окружении (`=1`), import OK, E2E 4 passed.
|
||||||
|
|
||||||
|
### 3. SKILL.md (memory) — обновлён
|
||||||
|
|
||||||
|
`.opencode/skills/memory/SKILL.md`:
|
||||||
|
- Добавлена секция "Аритектура (progressive enhancement)": keyword всегда, semantic если `OPENAI_BASE_URL` set, fallback (OpenRouter упал → keyword), auto-setup (первый `memory-save` всё создаёт), `memory-doctor` как read-only диагностика
|
||||||
|
- Убрано упоминание `@mathew-cf/opencode-memory` plugin (было "Плагин удалён" — стало "без wrapper-hop, без MCP-плагина")
|
||||||
|
- Убрано упоминание `memory-setup` (удалённый tool)
|
||||||
|
- Исправлен default путь: `~/opencode-memory` (устаревший) → `/root/.local/share/opencode/opencode-memory` (актуальный, соответствует `_memory-shared.ts:17`)
|
||||||
|
- Tools уже были kebab-case (`memory-search`, `memory-save`, `memory-list`, `memory-access`, `memory-doctor`) — проверено, не требовало правок
|
||||||
|
|
||||||
|
### 4. AGENTS.md — обновлён
|
||||||
|
|
||||||
|
`AGENTS.md` (root):
|
||||||
|
- Tool Usage Policy таблица — уже содержала 5 kebab-case tools (`memory-doctor`, `memory-save`, `memory-search`, `memory-list`, `memory-access`)
|
||||||
|
- Убрано упоминание `memory-setup` из описания `memory-doctor` (было "замена `memory-setup`" — стало чистое описание)
|
||||||
|
|
||||||
|
### 5. Stale references — найдены и обновлены
|
||||||
|
|
||||||
|
Поиск `grep -rnE "memory_(search|save|list|access|setup|doctor)"` по `.opencode/`, `docs/`, `AGENTS.md`:
|
||||||
|
|
||||||
|
**Активные файлы (обновлены):**
|
||||||
|
- `.opencode/skills/memory/SKILL.md` — `memory-setup` упоминание убрано
|
||||||
|
- `.opencode/skills/run-pipeline/SKILL.md` — `memory_save` → `memory-save` (2 места), `app_data/opencode-memory` → `<memory_dir>/repos/...`
|
||||||
|
- `.opencode/skills/repo-init/SKILL.md` — `memory_save` → `memory-save` (gotcha-блок)
|
||||||
|
- `.opencode/skills/release/SKILL.md` — `memory_save` → `memory-save` (gotcha-блок)
|
||||||
|
- `.opencode/skills/configure-opencode/SKILL.md` — `memory_save` → `memory-save` (gotcha-блок)
|
||||||
|
- `.opencode/skills/add-skill/SKILL.md` — `memory_save` → `memory-save` (gotcha-блок)
|
||||||
|
- `.opencode/skills/spec/SKILL.md` — `memory_search` → `memory-search` (6 мест, replaceAll)
|
||||||
|
- `.opencode/skills/issue/SKILL.md` — `app_data/opencode-memory` → `<memory_dir>/repos/...`
|
||||||
|
- `.opencode/agents/memory-syncer.md` — `memory_save` → `memory-save` (2 места), `app_data/opencode-memory` → `<memory_dir>` (3 места)
|
||||||
|
- `.opencode/commands/spec.md` — `memory_search` → `memory-search`
|
||||||
|
- `.opencode/tools/memory-access.ts` — `memory_save` → `memory-save` (user-facing message)
|
||||||
|
- `.opencode/tools/memory-doctor.ts` — `memory_save`/`memory_search` → `memory-save`/`memory-search` (4 user-facing messages)
|
||||||
|
- `.opencode/tools/memory-search.ts` — `memory_save` → `memory-save` (user-facing message)
|
||||||
|
|
||||||
|
**Исторические файлы (НЕ тронуты — snake_case отражает реальные имена на момент написания):**
|
||||||
|
- `docs/decisions/*.md` — 20+ ADR (ADR-014, 022, 023, 024, 026, 030, 032, 033, 035, 036, 037, 038, 039, 043, 045)
|
||||||
|
- `docs/handoff/*.md` — 15+ handoffs (pr-36, 53, 57, 63, 71, 75, 77, 78, 80, 83, 89, 101, 103)
|
||||||
|
|
||||||
|
### 6. Memory file `technical/rag-cli-embeddings-model.md` — переписан
|
||||||
|
|
||||||
|
`~/.local/share/opencode/opencode-memory/technical/rag-cli-embeddings-model.md`:
|
||||||
|
- Был: описание Rust rag-cli как активного (candle-transformers, all-MiniLM-L6-v2, 384-dim, GLIBC 2.39)
|
||||||
|
- Стал: описание Python + OpenRouter как единственного пути semantic (qwen/qwen3-embedding-8b, 4096-dim, progressive enhancement, auto-setup)
|
||||||
|
- Зафиксировано что удалено: `@mathew-cf/opencode-memory` plugin, `@mathew-cf/rag-cli` Rust binary, `setup-memory.sh`, `memory-setup.ts`, `.rag/index.bin`, `meta.json` с `model_id`
|
||||||
|
- Ссылки на ADR-030, 036, 043, 045
|
||||||
|
|
||||||
|
### 7. E2E тест
|
||||||
|
|
||||||
|
`RUN_LIVE=1 uv run pytest tests/test_memory_tools_e2e.py -x -q --no-cov` — 4 passed (до и после правок embedder.py).
|
||||||
|
|
||||||
|
## Почему
|
||||||
|
|
||||||
|
PR #100–#103 сделали инфраструктуру (Python safe, 5 TS tools, E2E, remove plugin + Rust),
|
||||||
|
но:
|
||||||
|
- Индекс был старый (17:19, без `meta.json` — Python incremental reindex не мог определить version mismatch)
|
||||||
|
- Документация (SKILL.md, AGENTS.md, skills, agents, TS tools) содержала stale snake_case tool names (`memory_save`, `memory_search`) — agents могли вызывать несуществующие tools
|
||||||
|
- `OPENAI_EMBEDDING_BATCH_DELAY` была в issue #99 как требование, но не поддерживалась в коде — добавление в `.env.example` без поддержки вводило бы в заблуждение
|
||||||
|
- Memory file `rag-cli-embeddings-model.md` описывал Rust rag-cli как активный — агенты могли пытаться использовать удалённый путь
|
||||||
|
|
||||||
|
Без этого PR: semantic search работал (index.json был), но docs лгали агентам про tool names → агенты могли вызывать `memory_save` (snake_case) и получать "tool not found".
|
||||||
|
|
||||||
|
## Pending
|
||||||
|
|
||||||
|
— (переиндекс выполнен, env vars были в окружении)
|
||||||
|
|
||||||
|
## Watch out
|
||||||
|
|
||||||
|
- **Reindex требует env vars** (`OPENAI_BASE_URL`, `OPENAI_API_KEY`). Без них `embed_texts` возвращает `None`, индекс не строится. Keyword search продолжает работать. Если env vars отсутствуют при деплое — reindex нужно запустить вручную после настройки env.
|
||||||
|
- **`OPENAI_EMBEDDING_BATCH_DELAY` default `0`** в коде — backwards compatible. `.env.example` рекомендует `0.5` для OpenRouter. В текущем окружении set `=1` (унаследовано).
|
||||||
|
- **`docs/decisions/` и `docs/handoff/` содержат snake_case** (`memory_save`, `memory_search`) — это исторические записи, отражают реальные имена tools на момент написания. НЕ трогать.
|
||||||
|
- **Reindex был incremental** (86 changed файлов, 286s) — не полный. Полный reindex при `meta.json` отсутствии или `version` mismatch. Если нужен полный — удалить `.rag/meta.json` + `.rag/index.json` и запустить снова.
|
||||||
|
|
@ -7,6 +7,7 @@ from tenacity import RetryError, retry, retry_if_exception, stop_after_attempt,
|
||||||
|
|
||||||
EMBEDDING_MODEL = os.environ.get("OPENAI_EMBEDDING_MODEL", "gemini-embedding-2-preview")
|
EMBEDDING_MODEL = os.environ.get("OPENAI_EMBEDDING_MODEL", "gemini-embedding-2-preview")
|
||||||
BATCH_SIZE = int(os.environ.get("OPENAI_EMBEDDING_BATCH_SIZE", "2048"))
|
BATCH_SIZE = int(os.environ.get("OPENAI_EMBEDDING_BATCH_SIZE", "2048"))
|
||||||
|
BATCH_DELAY = float(os.environ.get("OPENAI_EMBEDDING_BATCH_DELAY", "0"))
|
||||||
|
|
||||||
|
|
||||||
def _is_retryable(exc: BaseException) -> bool:
|
def _is_retryable(exc: BaseException) -> bool:
|
||||||
|
|
@ -40,6 +41,24 @@ def _call_embedding_api(
|
||||||
return [d["embedding"] for d in data["data"]]
|
return [d["embedding"] for d in data["data"]]
|
||||||
|
|
||||||
|
|
||||||
|
def _embed_in_batches(
|
||||||
|
client: httpx.Client,
|
||||||
|
url: str,
|
||||||
|
headers: dict[str, str],
|
||||||
|
texts: list[str],
|
||||||
|
) -> list[list[float]]:
|
||||||
|
n_batches = (len(texts) + BATCH_SIZE - 1) // BATCH_SIZE
|
||||||
|
results: list[list[float]] = []
|
||||||
|
for i in range(0, len(texts), BATCH_SIZE):
|
||||||
|
batch = texts[i : i + BATCH_SIZE]
|
||||||
|
payload: dict[str, str | list[str]] = {"model": EMBEDDING_MODEL, "input": batch}
|
||||||
|
print(f" batch {i // BATCH_SIZE + 1}/{n_batches}...", flush=True)
|
||||||
|
results.extend(_call_embedding_api(client, url, headers, payload))
|
||||||
|
if BATCH_DELAY > 0 and i + BATCH_SIZE < len(texts):
|
||||||
|
time.sleep(BATCH_DELAY)
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
def embed_texts(texts: list[str]) -> list[list[float]] | None:
|
def embed_texts(texts: list[str]) -> list[list[float]] | None:
|
||||||
if not texts:
|
if not texts:
|
||||||
return []
|
return []
|
||||||
|
|
@ -58,14 +77,6 @@ def embed_texts(texts: list[str]) -> list[list[float]] | None:
|
||||||
if len(texts) <= BATCH_SIZE:
|
if len(texts) <= BATCH_SIZE:
|
||||||
payload: dict[str, str | list[str]] = {"model": EMBEDDING_MODEL, "input": texts}
|
payload: dict[str, str | list[str]] = {"model": EMBEDDING_MODEL, "input": texts}
|
||||||
return _call_embedding_api(client, url, headers, payload)
|
return _call_embedding_api(client, url, headers, payload)
|
||||||
|
return _embed_in_batches(client, url, headers, texts)
|
||||||
n_batches = (len(texts) + BATCH_SIZE - 1) // BATCH_SIZE
|
|
||||||
results: list[list[float]] = []
|
|
||||||
for i in range(0, len(texts), BATCH_SIZE):
|
|
||||||
batch = texts[i : i + BATCH_SIZE]
|
|
||||||
payload = {"model": EMBEDDING_MODEL, "input": batch}
|
|
||||||
print(f" batch {i // BATCH_SIZE + 1}/{n_batches}...", flush=True)
|
|
||||||
results.extend(_call_embedding_api(client, url, headers, payload))
|
|
||||||
return results
|
|
||||||
except (httpx.HTTPError, RetryError, ValueError, KeyError):
|
except (httpx.HTTPError, RetryError, ValueError, KeyError):
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue