| name | plan-feature |
| description | Use ONLY when the user asks to plan a new feature, refactor, or architectural change. Guides a structured research-and-review cycle: discover codebase, identify risks, iterate on the plan, then produce an implementation-ordered plan committed under docs/. Do NOT use for bug fixes, small edits, or single-file changes. |
Plan a Feature
You are planning a non-trivial feature, architectural change, or refactor in
this repository — a PyTorch sentiment-analysis pipeline with Ray Train, Ray
Serve, an intent router, embeddings, diffusion, and metrics. Produce a reviewed,
risk-adjusted plan before writing any code.
Do not run mdformat on this file or any other .skills/**/SKILL.md. It
rewrites the --- frontmatter delimiters into horizontal rules and collapses
the YAML into a heading.
When to use this skill
- A multi-step change spanning several files or modules.
- A change touching shared infrastructure (
Trainer, SentimentPredictor,
the metrics pipeline, create_fastapi_app(), Ray session lifecycle).
- The user explicitly asks to "plan" or "review" before implementing.
Do NOT use this for one-off edits, bug fixes, or changes confined to one file.
Phase 1: Discover
- Read
AGENTS.md at the repo root. There is no CONTRIBUTING.md.
- Read the
.rules/ files matching your change area. AGENTS.md carries
the routing table. At minimum, any Python implementation requires
.rules/python-style.md, .rules/dry.md, and .rules/solid.md; a
multi-milestone plan requires .rules/plan-review.md and
.rules/testing-workflow.md. Logging, timeouts, or long-running work also
requires .rules/observability.md. These are mandatory, not background
reading.
- Check
.skills/ for a task-specific workflow. A concurrency-sensitive
change has its own skill (change-concurrent-code-safely); load it rather
than improvising.
- Map the affected surface area with
glob/grep/read:
- Every call site of any function whose signature you plan to change. Count
them; the number goes in the plan.
- Config dataclasses and the YAML/env overrides that feed them.
- Model factory and loader sites (
trainer.py::_train_func,
workflows/helpers.py::_load_model`).
- Test files covering the affected area.
- Optional dependency groups in
pyproject.toml.
- Read 2–3 existing implementations end-to-end. Adding a new model means
reading
models/rnn.py, models/encoder.py, or models/modernbert.py
completely — not skimming one.
- Verify library capability from source. Check what Ray Train, Ray Serve,
or Transformers actually support before planning around it. Marketing
READMEs often overstate coverage.
- Verify, don't inherit, dated observations. Behavior recorded in an
older plan or doc may be stale. Re-check it, and record the verification
date next to anything you rely on.
Phase 2: Draft the plan
The plan is a committed document at docs/<slug>-plan.md.
It must open with a **Status:** line carrying a state and a date —
📋 Not started, ⏳ Partially implemented, or ✅ Completed.
.rules/plan-review.md is the authority on this and on the status lifecycle;
follow it rather than inventing a format. Preserve prior status text as an
italic _Original status: …_ line instead of deleting it.
Required sections
- Goal — one or two sentences on what the feature does.
- Scope — what is in, and explicitly what is out.
- Architecture — file tree with
NEW/MOD markers.
- Public interfaces — endpoints, CLI surfaces, model signatures,
and response shapes. Serve endpoints are namespaced by domain
(
/v1/sentiment/{...}, /v1/router/{...}, etc.).
- Implementation order — milestones, each a commit-sized unit, each
ending in a
make ci gate.
- Testing strategy — named test modules and the scenarios each must
cover.
- Risk review — a table of risk → resolution (Phase 3).
- Assumptions — every empirical claim the plan rests on.
- Convention updates — changes needed in
AGENTS.md, .rules/,
docs/troubleshooting.md, or relevant doc files.
For each change, specify
- New classes and functions with complete signatures — no placeholder types. A
signature naming a type the plan never defines is not decision-complete.
- Modified functions with before/after signatures and the call-site count.
- Config/schema field additions with types and defaults.
- For a new model type: the config dataclass, model class, factory import in
_train_func, and loader import in _load_model.
Phase 3: Risk review
.rules/plan-review.md owns the general review cycle — at least one explicit
revise-and-re-review pass. This section adds the risk classes that recur in
this repository. Walk them explicitly.
- 3-class contract preservation. All models output
(B, 3) logits;
labels are 0=negative, 1=neutral, 2=positive. Any metric, loss, or serving
change must respect this. LABEL_NAMES and NUM_CLASSES in config.py
are the single source of truth — never re-declare.
- Base class coupling. If the new subclass overrides
__init__, does it
skip parent initialization that sets required state (STEP_SCHEDULER_PER_BATCH,
SUPPORTS_ONNX)? Document which parent methods are called/skipped and why.
- Scheduler contract.
_LinearWarmupCosineScheduler uses (step + 1) / warmup_steps (not step / warmup_steps) and clamps progress to 1.0.
Any scheduler change must preserve these invariants.
- Metrics pipeline coherence. Adding a training metric requires changes in
seven places (see "Adding a new training metric" in AGENTS.md). Missing one
silently drops the metric or breaks the exporter.
- Ray lifecycle.
_train_func runs on Ray workers — it must not depend on
driver state loaded before trainer.fit(). train.get_context() and
train.get_dataset_shard() are standalone functions inside workers only.
Checkpoints are directory-based (Checkpoint.from_directory()), not
dict-based.
- Ray Serve contracts. Never define
__call__ on a deployment class.
Use create_fastapi_app() so each deployment gets its own app instance.
Middleware order is behavior — CORS is outermost.
RAY_ENABLE_UV_RUN_RUNTIME_ENV module-level. Any new Serve entry point
must set os.environ.setdefault("RAY_ENABLE_UV_RUN_RUNTIME_ENV", "false")
at module level, before any import ray.
- Lazy loading contracts.
SentimentPredictor lazy-loads the router;
_ensure_router_loaded() memoizes. Changing load ordering can break the
contract or add startup cost.
- Optional heavy dependencies. Keep them behind import guards so the base
service imports and serves unaffected routes without them. Requesting an
unavailable backend must raise a clear capability error, not a deep
ImportError.
Phase 4: Iterate
Present the plan and ask what is actually undecided — not a fixed script.
Useful questions:
- Does the scope match what you intended?
- Which risks should I dig into further?
- Should we adjust the optional dependency boundary?
- Is any milestone large enough to land as its own change?
Revise and re-review at least once. Do not implement until the user approves.
Phase 5: Implementation order
After approval, produce a numbered implementation checklist ordered by
dependency (things that must exist before other things can reference them).
Each item should be a single commit-sized unit:
- Import guard / compat module (no other code depends on it yet)
- Config changes (new fields, registry, env vars)
- New implementation classes (behind the import guard)
- Factory/wiring (routes config → implementation)
- Serve layer / integration points
- Optional dep group in pyproject.toml
- Tests (unit tests for factory, config; integration tests behind skipif)
- Convention docs (AGENTS.md,
.rules/, etc.)
Every milestone ends with make ci — per .rules/testing-workflow.md, not
only at the end. A milestone that cannot pass the full gate is not finished.
Blocking prerequisites come first. If the plan depends on live model behavior
or data, verify it empirically before writing tests against assumptions.
Tips
- Copy the existing pattern. If the codebase has an import-guard + factory
pattern (like
TRANSFORMERS_AVAILABLE + new_modernbert_model()), copy it
exactly. Consistency matters.
- Immutable config dataclasses. If the existing config uses
frozen=True,
your new fields must have defaults or use the same pattern for construction.
- YAML config precedence. New env vars need corresponding entries in both
the
_ENV_OVERRIDES dict and the YAML file. Don't forget the _FIELD_TYPES
dict for non-string types.
- Return type consistency. If the existing interface returns a specific
shape, the new backend must return the same type. Wrap the library's return
type if it differs.
- Web-verify library claims. Before planning around a library's supported
models or features, check its actual source code, model directory, or API
docs. The plan's risk review is the right place to flag "supported models"
vs "assumed models."
- Report environment-dependent failures rather than hiding or bypassing
them.
- Documentation-only changes verify with
git diff --check; skip the
Python suite.