From d7a7a79cbabf888fe2cef8d6d9132355feb5cebf Mon Sep 17 00:00:00 2001 From: vitya Date: Tue, 5 May 2026 16:16:26 +0300 Subject: [PATCH] Capture interns design spec in .brainstorm Domain spec for delegated cheap-LLM workers via local MCP server. Three layers (config / runtime / skills), MVP catalog of two interns (bulk_text_read + transcript_distill) on DeepSeek Flash via ollama_cloud. Permission model mirrors project-discipline Rule 4 (per-session grant); always-ask paths enforced server-side. Lives at .common/lib/interns-mcp/. Target promote: claude-skills wiki + tasks via meeting-room-promote-brainstorm. Co-Authored-By: Claude Opus 4.7 (1M context) --- .brainstorm/interns.md | 301 +++++++++++++++++++++++++++++++++++++++++ .wiki/log.md | 1 + 2 files changed, 302 insertions(+) create mode 100644 .brainstorm/interns.md diff --git a/.brainstorm/interns.md b/.brainstorm/interns.md new file mode 100644 index 0000000..c0ec4a7 --- /dev/null +++ b/.brainstorm/interns.md @@ -0,0 +1,301 @@ +--- +date: 2026-05-05 +topic: interns +status: design-approved +type: domain +target_promote: claude-skills +sources: + - .wiki/raw/research/tokens-economy/2026-05-05-i-gave-claude-code-a-$0.02call-coworker-and-stopped-hitting-pro-limits---here's-the-full-setup.md + - .wiki/raw/research/tokens-economy/i-was-burning-through-claude-codes-weekly-limit-in-3-days-here-s-how-i-fixed-it.html + - .brainstorm/modulair-rag.md (selected line 134 — Marker/Repomix/Firecrawl specialization hint) + - claude-skills/.wiki/concepts/project-discipline-design.md (Rule 4 — permission grant prototype) + - claude-skills/.wiki/concepts/skill-vs-plugin.md (bare skill vs plugin decision) + - claude-skills/.wiki/concepts/repo-layout.md (skill repo conventions) +--- + +# Interns — Design Spec + +Каталог специализированных «интернов» — дешёвых LLM/тулзов, которым Claude Code делегирует bulk I/O и предсказуемую генерацию, чтобы экономить твою Anthropic квоту. Доступ — через локальный MCP-сервер. Использование защищено per-session permission grant (зеркало `project-discipline` Rule 4). Каталог расширяется без переписывания скила. + +## Context + +Triggered by Reddit thread (May 2026) и Medium-статьёй того же автора, обе в `.wiki/raw/research/tokens-economy/`. Pattern: `expensive manager (Claude) + cheap intern (DeepSeek/Kimi/Ollama)`. ~23× cheaper end-to-end на summarization задачах, ~125× per-call на bulk read. Без этого OP упирался в weekly Pro limit к среде. + +Локальный сигнал из selected line `.brainstorm/modulair-rag.md:134` — `Marker лучше PDF, Repomix лучше код, Firecrawl лучше JS-сайты` — определил выбор архитектуры (b) специализированных интернов вместо одного universal cheap-LLM. + +## Architecture (three layers) + +``` +┌──────────────────────────────────────────────────────────────┐ +│ Layer 3 — Policy (skills) │ +│ using-interns — runtime policy + permission grant │ +│ setup-interns — one-time install/build/register │ +│ CLAUDE.md trigger: "delegate to interns when allowed" │ +└──────────────────────────────────────────────────────────────┘ + ↑ читает / соблюдает Claude +┌──────────────────────────────────────────────────────────────┐ +│ Layer 2 — Runtime (MCP server) │ +│ .common/lib/interns-mcp/ (Python + FastMCP, stdio) │ +│ ├── interns_mcp/server.py — MCP entry point │ +│ ├── interns_mcp/registry.py — load catalog from config │ +│ ├── interns_mcp/client.py — OpenAI-compatible HTTP │ +│ ├── interns_mcp/safety.py — always-ask path matcher │ +│ └── interns_mcp/interns/ — one file per intern impl │ +│ ├── base.py │ +│ ├── bulk_text_read.py │ +│ └── transcript_distill.py │ +│ Tools exposed: │ +│ mcp__interns__bulk_text_read │ +│ mcp__interns__transcript_distill │ +└──────────────────────────────────────────────────────────────┘ + ↑ читает on startup +┌──────────────────────────────────────────────────────────────┐ +│ Layer 1 — Config (data, no code) │ +│ .common/config/interns/config.yaml — endpoints + tools │ +│ .common/secrets/interns.env — API ключи (gitignored) │ +└──────────────────────────────────────────────────────────────┘ +``` + +Слои ортогональны. Добавить интерна = одна запись в config + один файл в `interns/`. Skills и setup-flow не трогаются. + +## MVP catalog + +| ID | Описание | Endpoint | Модель | Когда вызывать | +|---|---|---|---|---| +| `bulk_text_read` | Прочитать N файлов и ответить на вопрос | `ollama_cloud` | `deepseek-v4-flash` | Когда Claude собирался прочесть 3+ файлов или один >400 строк ради контекста | +| `transcript_distill` | Сжать session-transcript / лог в action-list | `ollama_cloud` | `deepseek-v4-flash` | Перед обновлением `.wiki/log.md` или summary документации по сессии | + +Оба на одном endpoint и модели — демонстрируют разделение **по классу задачи**, не по провайдеру. Дальнейшие интерны (PDF/web/code) — следующий релиз. + +## Layer 1 — Config + +`.common/config/interns/config.yaml`: + +```yaml +endpoints: + ollama_cloud: + base_url: https://ollama.com/v1 + api_key_env: OLLAMA_CLOUD_API_KEY + request_defaults: + extra_body: + reasoning: { enabled: false } # обязательно для DeepSeek V4 — иначе max_tokens уходит в silent thinking + +interns: + bulk_text_read: + description: "Read N files and answer a focused question. Returns concise summary." + endpoint: ollama_cloud + model: deepseek-v4-flash + max_tokens: 4096 + temperature: 0.2 + system_prompt: | + You are a careful reader. Answer ONLY what the user asks, citing + file:line refs. Do not hallucinate file contents. If unsure, say so. + + transcript_distill: + description: "Compress a session transcript/log into a structured action-list." + endpoint: ollama_cloud + model: deepseek-v4-flash + max_tokens: 2048 + temperature: 0.1 + system_prompt: | + Extract action items, decisions, and unresolved questions from the + transcript. Output structured markdown sections. Be terse. +``` + +`.common/secrets/interns.env` (gitignored): + +```dotenv +OLLAMA_CLOUD_API_KEY=... +``` + +Сервер на startup: загружает `.env` через `python-dotenv` → читает `config.yaml` → берёт ключ из `os.environ[api_key_env]`. Если ключа нет — `setup-interns` интерактивно спрашивает и пишет в `.env` (с preview-confirmation gate перед записью). + +## Layer 2 — MCP server + +`.common/lib/interns-mcp/`: + +``` +interns-mcp/ +├── pyproject.toml +├── README.md +├── interns_mcp/ +│ ├── __init__.py +│ ├── server.py — FastMCP app, tool registration +│ ├── registry.py — load config.yaml → list of Intern objects +│ ├── client.py — OpenAI-compatible HTTP client (per-endpoint, persistent) +│ ├── safety.py — always-ask path matcher +│ └── interns/ +│ ├── __init__.py +│ ├── base.py — Intern protocol/dataclass +│ ├── bulk_text_read.py +│ └── transcript_distill.py +└── tests/ + ├── test_safety.py — path matcher tests + └── test_registry.py +``` + +**Server contract:** +- На startup `registry.load()` проходит config, для каждого интерна делает `@mcp.tool()` с typed signature. +- Tool name = `` — Claude harness сформирует `mcp__interns__` (где `interns` — имя сервера в `~/.claude.json`). +- Каждый tool принимает `paths: list[str]`, `question: str`, опционально `max_tokens: int`. +- **MCP-сервер сам читает файлы из переданных `paths`** — Claude передаёт пути, не содержимое. Это критично для safety: сервер видит пути и применяет always-ask matcher до того как файл уйдёт в endpoint. Если бы Claude слал content, политика могла бы быть обойдена случайно (Claude прочитал `.env`, переслал содержимое — поздно). +- Persistent HTTP client per-endpoint — для prefix-cache discount если endpoint его поддерживает (OpenRouter да, Ollama Cloud TBD). + +**Safety enforcement:** `safety.py` проверяет каждый input `path` против always-ask glob-списка. Match → возвращает `BlockedByPolicy` объект с указанием matched-pattern; Claude получает structured ответ и сам спрашивает пользователя. Безопасность на стороне сервера — Claude может «забыть» политику в длинной сессии, MCP не забудет. + +**Cross-platform:** Python 3.11+. Установка `pip install -e .common/lib/interns-mcp/`. Запуск как stdio: `python -m interns_mcp.server`. Без bash/PowerShell зависимостей в hot path. + +## Layer 3 — Skills + +Два скила, по prior art (`setup-context7` + `using-context7`, `setup-projects-meta` + `using-projects-meta`). + +### setup-interns (v0.1.0) + +**When:** «set up interns», «настрой интернов», «install interns», или когда `mcp__interns__*` отсутствуют в сессии где они нужны. + +**Steps:** +1. Проверить `.common/lib/interns-mcp/` существует. Если нет — инициализировать pустой через template (TBD: см. open question про source repo). +2. `pip install -e .common/lib/interns-mcp/` через активный Python interpreter. +3. Прочитать `.common/config/interns/config.yaml`, для каждого `endpoint..api_key_env` проверить наличие в `.common/secrets/interns.env`. Отсутствующие — спросить интерактивно, preview перед записью, write. +4. Зарегистрировать `mcpServers.interns` в `~/.claude.json`: + ```json + "interns": { + "command": "python", + "args": ["-m", "interns_mcp.server"] + } + ``` + Путь к Python — через `shutil.which("python")` или `where`/`which` в зависимости от платформы. +5. Попросить пользователя перезапустить Claude Code. + +**Migration mode:** detect устаревший layout (например, переезд с `~/.local/interns-mcp/` если когда-то такой был) и предложить миграцию. + +### using-interns (v0.1.0) + +**When:** активируется триггером `delegate to interns when allowed` в `CLAUDE.md`. Также явные команды: «use interns», «delegate this to an intern». + +**Policy:** + +1. **Старт сессии = ask-mode.** Перед первым вызовом `mcp__interns__*` Claude спрашивает: + > «Я бы делегировал чтение `` интерну `bulk_text_read` (DeepSeek Flash, ~$0.002 за вызов). Ок?» + +2. **Conversational grant.** + - «разреши интернов» / «allow interns» / «use interns» → grant до конца сессии. + - «отзови интернов» / «revoke interns» / «делай сам» → возврат в ask-mode. + +3. **Always-ask paths (даже с активным grant'ом).** Полный список: + - `**/.env`, `**/.env.*` — environment files со секретами + - `**/secrets/**` — каноническая папка секретов (включая `.common/secrets/`) + - `**/credentials*` — credentials.json и подобные + - `**/*.key` — private keys любого формата + - `**/*.pem` — PEM-encoded keys/certs + - `**/.ssh/**` — SSH ключи + - `**/.aws/credentials`, `**/.aws/config` — AWS credentials + - `**/.netrc`, `**/.npmrc`, `**/.pypirc` — registry credentials + - **Любой путь, который Claude в текущей сессии прочитал из такого пути и теперь хочет передать интерну** (transitive — нельзя обойти, прочитав файл сам и переслав содержимое). + - **Любой intern call с estimated cost >$0.10** (sanity-check, по-конфигу: `tokens × price`). + + Поведение: matched call → MCP возвращает `BlockedByPolicy{path, pattern, reason}`. Claude формулирует пользователю явный вопрос: «Файл `` matched always-ask pattern ``. Передавать интерну на endpoint ``?» + +4. **Что НЕ делегируется** (рекомендации в SKILL.md, не enforced): + - Архитектурные / design-решения + - Debugging — cheap model теряет тонкие баги + - Auth / payments / PII / deletion / production data (даже если файлы не в always-ask списке) + - Final commit messages, PR descriptions + - Финальный текст ответа пользователю + +5. **Конец сессии = reset на ask-mode.** Persistent grant отвергнут как менее безопасный (зеркало Rule 4 `project-discipline`). + +6. **Routing-подсказки** (внутри SKILL.md, чтобы Claude знал когда уместно): + - Файл >400 строк и не центральный для редактирования → `bulk_text_read`. + - ≥3 файлов нужно прочитать ради контекста → `bulk_text_read`. + - Перед обновлением `.wiki/log.md` или session-summary → `transcript_distill`. + +## Bootstrap integration + +`project-bootstrap` v1.5.0 → v1.6.0 (MINOR — capability added): + +- `assets/CLAUDE.md.template` — добавить строку `delegate to interns when allowed` сразу после `follow project discipline`. +- `bootstrap-manifest.md` — новые строки `using-interns` + `setup-interns` со своими version'ами. +- Step 5 commentary — параграф с объяснением (по образцу commentary для `follow project discipline`). +- Idempotent merge — существующие `CLAUDE.md` получат строку при следующем bootstrap (`bootstrap-claude-md-merge.md` уже умеет). + +## Cross-platform + +| Слой | Windows | Linux | macOS | +|---|---|---|---| +| `.common/lib/interns-mcp/` (Python 3.11+) | ✅ | ✅ | ✅ | +| `.common/secrets/interns.env` (`python-dotenv`) | ✅ | ✅ | ✅ | +| `setup-interns` install (`python -m pip`) | ✅ | ✅ | ✅ | +| MCP registration — путь к Python | `where python` | `which python` | `which python` | +| Always-ask matcher (`pathlib.PurePath.match`) | ✅ POSIX-style globs работают везде | ✅ | ✅ | + +`active-platform` скил уже знает что показывать пользователю в каждой платформе для shell команд, никаких дублей. + +## Как добавить нового интерна + +1. **Создать файл** `.common/lib/interns-mcp/interns_mcp/interns/.py`. Шаблон: + ```python + from .base import Intern, InternResponse + + class PdfRead(Intern): + id = "pdf_read" + description = "Extract text from PDF, including tables." + + def run(self, paths: list[str], question: str, **kwargs) -> InternResponse: + # 1. Validate paths existence + # 2. Optionally call safety.check(paths) — base.Intern может делать это в __call__ + # 3. Call external (LLM endpoint via self.client, or local subprocess like Marker) + # 4. Return InternResponse(text=..., usage={tokens_in, tokens_out, cost_usd}) + ... + ``` + +2. **Добавить запись** в `.common/config/interns/config.yaml` под `interns:`: + ```yaml + pdf_read: + description: "Extract text from PDF (tables, formulas)." + endpoint: null # local subprocess, нет endpoint + # OR: + # endpoint: ollama_cloud + # model: deepseek-v4-flash + max_tokens: 8192 + ``` + +3. **(Если новый endpoint)** — добавить в `endpoints:` секцию + ключ в `interns.env`. + +4. **Зарегистрировать tool в `server.py`** (для MVP — explicit, см. open question про auto-discovery): + ```python + from interns_mcp.interns.pdf_read import PdfRead + register_tool(mcp, PdfRead()) + ``` + +5. **Restart Claude Code** — новый MCP tool появится как `mcp__interns__pdf_read`. + +6. **Routing-подсказки** в `using-interns/SKILL.md` — добавить строку «если задача XYZ → `pdf_read`». + +7. **(Опц.) Test** в `tests/test_interns_pdf_read.py`. Минимум — проверка happy-path и safety-block для `**/.env*`. + +Если интерн использует local CLI (Marker, Repomix, etc.) вместо LLM API — `endpoint: null` в config, и реализация в `interns/.py` вызывает subprocess. + +## Action items + +- [ ] **`claude-skills`:** разработать и зашипить `setup-interns` (v0.1.0) + `using-interns` (v0.1.0). Bump `project-bootstrap` 1.5.0 → 1.6.0 с обновлением `assets/CLAUDE.md.template`, `bootstrap-manifest`, Step 5 commentary. +- [ ] **`.common`:** реализовать `interns-mcp/` MVP с двумя интернами (`bulk_text_read`, `transcript_distill`); написать `README.md` с секцией «Как добавить» (см. выше); pyproject.toml + минимум tests. +- [ ] **`projects-meta-mcp`:** мигрировать в `.common/lib/projects-meta-mcp/` для унификации (отложенный backlog item, отдельная задача). +- [ ] **`.meeting-room`:** вынести `ollama_cloud.api_key` из `config/config.yaml` в `.common/secrets/interns.env` (или отдельный `.common/secrets/llm-providers.env` если хочется общую точку для всех потребителей этого endpoint — meeting-room runner + interns-mcp + future). + +## Open questions + +- **Source repo для `.common/lib/interns-mcp/`.** Inline в `.common` или отдельный repo на Gitea + git-subtree/submodule? Текущее склонение — inline (это часть `.common`, не самостоятельный продукт). +- **Auto-discovery интернов** в `registry.py` (через `pkgutil.iter_modules`) vs explicit `register_tool` в `server.py`. Auto проще для расширения, explicit прозрачнее. Текущее склонение — explicit для MVP. +- **Cost tracking.** В первом релизе — нет. Если оботрётся в реальной работе — добавим в `safety.py` per-call estimate из config (`tokens_used × price_per_M`) и блокировку >$X через always-ask механизм. +- **Sharing endpoint между meeting-room runner и interns-mcp.** Сейчас `.meeting-room/config/config.yaml` имеет свой `providers.ollama_cloud` с собственным ключом; interns-mcp будет иметь свой в `.common/secrets/interns.env`. Дублирование. Унификация — отдельная задача (см. action item #4). +- **Persistent prefix-cache benefit с Ollama Cloud.** Документация Ollama Cloud не подтверждает prefix-cache discount явно (как делает OpenRouter). Если измерения покажут что cache не работает — рассмотреть переключение на OpenRouter как primary endpoint. + +## References + +- [.wiki/raw/research/tokens-economy/2026-05-05-i-gave-claude-code-a-...md](../.wiki/raw/research/tokens-economy/2026-05-05-i-gave-claude-code-a-$0.02call-coworker-and-stopped-hitting-pro-limits---here's-the-full-setup.md) — оригинальный Reddit thread, паттерн + ~125× cost reduction цифры. +- [.wiki/raw/research/tokens-economy/i-was-burning-through-...html](../.wiki/raw/research/tokens-economy/i-was-burning-through-claude-codes-weekly-limit-in-3-days-here-s-how-i-fixed-it.html) — Medium-статья от автора Reddit-поста. +- `claude-skills/.wiki/concepts/project-discipline-design.md` — Rule 4 (commit-yes-push-no per-session) — прототип permission-grant механизма для `using-interns`. +- `claude-skills/.wiki/concepts/skill-vs-plugin.md` — обоснование bare SKILL.md (skills here не нуждаются в plugin format). +- `claude-skills/.wiki/concepts/repo-layout.md` — конвенции `claude-skills` repo для скилов. +- `.brainstorm/modulair-rag.md:134` — selected line, повлияла на выбор архитектуры (b) специализированных интернов. diff --git a/.wiki/log.md b/.wiki/log.md index b1dd922..4889814 100644 --- a/.wiki/log.md +++ b/.wiki/log.md @@ -15,3 +15,4 @@ Events: `started`, `promoted`, `registered`, `archived`. 2026-05-05 promoted modulair-rag → .wiki/concepts/modulair-rag-brainstorm-trace.md (tasks_create skipped: target projects modulair-rag/meeting-room not yet registered in projects-meta; 3 actions deferred in trace) 2026-05-05 promoted meeting-room-redesign → .wiki/concepts/meeting-room-architecture.md (no tasks; plan executed during this same session — implementation plan archived separately as 2026-05-05-meeting-room-redesign-plan.md) 2026-05-05 archived meeting-room-redesign-plan (post-execution) +2026-05-05 designed interns (.brainstorm/interns.md — domain spec, target promote: claude-skills)