Files
claude-skills/.wiki/concepts/interns-design.md
vitya ef3d38e79d feat(setup-interns, using-interns): secrets at ~/.config/projects-secrets/
Aligns claude-skills with secrets-out-of-common etap-1 migration: the
canonical home for plain-text local-dev secrets is now ~/.config/projects-secrets/,
outside any git tree.

setup-interns [v0.3.0 → v0.4.0, MINOR — write target changed]:
- Phase 1 drops gitignore-sanity check (no longer needed)
- Phase 2 plan block drops Gitignore line
- Phase 3 backs up ~/.config/projects-secrets/interns.env if present
- Phase 5 writes ~/.config/projects-secrets/interns.env (mkdir -p ahead)
- Phase 6 cwd documentation: secrets path no longer relative to cwd; uses
  INTERNS_SECRETS_PATH env var (or ~/.config default) — independent
- Common-mistakes drops "missing gitignore rule" entry

using-interns [v0.2.0 → v0.2.1, PATCH — wording]:
- Always-ask paths section reflects new canonical secrets home
- Prerequisites text updates setup-interns write target

interns-design.md (wiki concept): path refs updated for ASCII layer
  diagram, Layer 1 example block, Phase 5 description, comparison
  table, and final cross-cutting note. **/projects-secrets/** added
  to always-ask documentation pattern.

dist/: setup-interns.skill + using-interns.skill rebuilt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 14:22:51 +03:00

291 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
date: '2026-05-05'
status: design-approved
source_brainstorm: .meeting-room/.brainstorm/interns.md
topic: interns
title: interns-design
type: concept
ingested_at: '2026-05-05T13:58:59.608Z'
ingested_by: OpeItcLoc03@DESKTOP-NSEF0UK
source_project: .meeting-room
---
# 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-статьёй того же автора, обе в `.meeting-room/.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 к среде.
Локальный сигнал из `.meeting-room/.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 │
│ ~/.config/projects-secrets/interns.env — API ключи (outside git) │
└──────────────────────────────────────────────────────────────┘
```
Слои ортогональны. Добавить интерна = одна запись в 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.
```
`~/.config/projects-secrets/interns.env` (outside any git tree; canonical home since `secrets-out-of-common` migration):
```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 = `<intern_id>` — Claude harness сформирует `mcp__interns__<id>` (где `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/` существует. Если нет — инициализировать пустой через template (TBD: см. open question про source repo).
2. `pip install -e .common/lib/interns-mcp/` через активный Python interpreter.
3. Прочитать `.common/config/interns/config.yaml`, для каждого `endpoint.<name>.api_key_env` проверить наличие в `~/.config/projects-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 спрашивает:
> «Я бы делегировал чтение `<files>` интерну `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/**`, `**/projects-secrets/**` — каноническая папка секретов (после миграции `secrets-out-of-common`: `~/.config/projects-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 формулирует пользователю явный вопрос: «Файл `<path>` matched always-ask pattern `<pattern>`. Передавать интерну на endpoint `<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+) | ✅ | ✅ | ✅ |
| `~/.config/projects-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/<intern_id>.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/<id>.py` вызывает subprocess.
## 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 будет иметь свой в `~/.config/projects-secrets/interns.env`. Дублирование. Унификация — отдельная задача.
- **Persistent prefix-cache benefit с Ollama Cloud.** Документация Ollama Cloud не подтверждает prefix-cache discount явно (как делает OpenRouter). Если измерения покажут что cache не работает — рассмотреть переключение на OpenRouter как primary endpoint.
## References
- `.meeting-room/.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 цифры.
- `.meeting-room/.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.
- `claude-skills/.wiki/concepts/repo-layout.md` — конвенции `claude-skills` repo для скилов.
- `.meeting-room/.brainstorm/modulair-rag.md:134` — selected line, повлияла на выбор архитектуры (b) специализированных интернов.