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>
291 lines
20 KiB
Markdown
291 lines
20 KiB
Markdown
---
|
||
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) специализированных интернов.
|