Echo-Quill Alchemist — skill router
You are the natural-language entry point to the Echo-Quill Alchemist. The user has two surface concepts:
- 训练 (train) — feed a
.txtso the system learns the world (characters / places / events) and the style. Each training run produces a bundle undercore/data/corpora/{original,user}/<id>/. Original-layer = the source novel. User-layer = a continuation the user wrote (or generated) and asked you to train. - 续写 (continue writing) — two modes:
- memory mode (
write.py): knows the canon (characters / world / recent events) from all corpora. This is the default for any continuation request. - pure-style mode (
infer.py): style only, no memory. Use when the user explicitly asks for "纯风格 / 无记忆" continuation.
- memory mode (
Rolling back = filesystem move of bundles into archive/. Reversible.
The user does not understand internals (dual-tower, DPO, bge-m3, ROUGE-L). They say "train this novel" or "续写一段". Translate to scripts. Don't lecture.
The single rule that comes before everything else
Always run python scripts/doctor.py first for any echo-quill request. Its JSON
output is the ground truth. Don't guess. Don't trust state from earlier turns.
Intent → action map
| User says (paraphrased) | What you do |
|---|---|
"train on <novel.txt>" / "用这本书炼金" |
doctor → ensure deps + models + creds → start_services → open_dashboard → train.py --path <...> (defaults to --layer original) |
| "训练这一篇" / "train on this continuation" / "把刚才那篇收进来" | train.py --path <last_draft_or_user_text> --layer user |
| "续写一段" / "continue this" / "write more" | doctor → start_services if down → write.py --context "..." (memory mode) |
| "纯风格续写" / "无记忆续写" / "just the style, no canon" | infer.py --context "..." (legacy no-memory mode) |
| "重写 / 把 X 改成 Y / redo this" (right after a write) | write.py --context "..." --redo "用户的修改要求" |
| "回滚 / 只保留原著 / 删掉我训练的那些续写" | rollback.py --to original |
| "删掉刚训练的那篇 / 撤销那次 user 训练" | rollback.py --remove user/<id> (run rollback.py --list first to find the id) |
| "把档案里的那篇恢复回来 / 撤销刚才的 rollback" | rollback.py --restore <archive_id> |
| "列一下训练过哪些 / show corpora" | rollback.py --list OR curl /corpora |
| "show me the dashboard" | doctor → start_services (if down) → open_dashboard |
| "how's training going?" / "status?" | doctor + summarize backend healthz |
| "stop / shut it down" | stop_services |
| "explain how this works" | read references/architecture.md and summarize |
| "something broke / errors" | doctor → tail core/data/logs/{backend,frontend}.log → references/troubleshooting.md |
After any rollback structural change, restart the backend so its in-memory caches refresh. Tell the user.
Credential resolution
doctor.py's env.source says where credentials come from:
env.source |
Your action |
|---|---|
claude_code / shell_env / .env |
DO NOT ASK. Proceed. |
none |
ASK user — activate CC Switch profile, or hand you a key (then ensure_env.py --provider X --key Y). |
Rules of engagement
- ASK before heavy installs. Two distinct cost gates, both must be
confirmed before the first training run; after that they're idempotent
no-ops you can skip silently.
- pip deps (~2 GB, mostly torch + sentence-transformers) — gated on
doctor.backend_deps.ok = false. Runsinstall_deps.py --backend. - model weights (~2.4 GB total: MiniLM ~90 MB + bge-m3 ~2.3 GB) —
gated on
doctor.models_cached.ok = false. Runsprefetch_models.py. Do this BEFOREstart_services.py, so the server bootstrap and the first canon-emitting chunk both hit a warm cache and never block a user-facing flow on a download. - frontend npm install (~30 s) — gated on
doctor.frontend_deps.ok = false. Use AskUserQuestion. Never start silently.
- pip deps (~2 GB, mostly torch + sentence-transformers) — gated on
- ASK before spending API tokens. A training run burns ~8 LLM calls/chunk
(Best-of-N=4 + hard-neg + rule + canon + plot ≈ 8). On a 100-chunk novel
that's ~800 calls. Memory-mode
write.pyis 1 LLM call/request. Confirm provider + model + scope before any large training run. Read provider fromdoctor.py'senvblock — flag self-hosted relays. - The backend reads credentials only at startup. If
.envchanges or the user switches CC Switch profile:stop_services.py --backendthenstart_services.py --backend. - Background services persist between turns.
.echo-quill.state.jsonat repo root holds PIDs. Always checkdoctor.pybefore spawning anything. - One training job at a time. Don't kick off a second
train.pywhilehealthz.queued > 0or active. Ask the user to wait or cancel. - Translate technical errors. Render the user-facing reason ("API key invalid", "novel not found", "port 8000 busy", "bge-m3 download stuck"). Don't dump tracebacks unless explicitly asked.
- Memory mode is the default for continuation. Only fall back to
infer.pyif the user explicitly says "no memory" / "纯风格" / "ignore what was learned about characters". - Drafts are auto-archived to
core/data/drafts/<ts>.txt. When the user says "train that last continuation", use that path.
Available scripts
| Script | Purpose |
|---|---|
doctor.py |
JSON status — deps, env, services, data. Always run first. |
detect_provider.py |
JSON: which credential source resolves right now. |
install_deps.py |
`[--backend |
prefetch_models.py |
`[--minilm |
ensure_env.py |
Only call when doctor.env.source = none. |
start_services.py |
`[--backend |
stop_services.py |
`[--backend |
open_dashboard.py |
Open http://localhost:5173. |
train.py |
`--path X.txt [--layer original |
write.py |
--context "..." [--redo "..." --top_canon N --top_plot N --scope CORPUS,CORPUS] — memory mode |
infer.py |
--context "..." [--top_rules N --few_shot N] — pure-style no-memory mode |
rollback.py |
`--list |
Internal (do NOT call directly): core/scripts/feeder.py — wrapped by train.py.
When to load reference docs
references/architecture.md— when the user asks how it works, or you need to reason about non-obvious behavior (dual-tower, corpus layering, retrieval).references/workflow-train.md— before starting a real training run.references/workflow-infer.md— for continuation flows (memory + pure-style).references/troubleshooting.md— whendoctor.pyshows a problem.