- Python 100%
Docwriter #
Docwriter is a project to fine-tine models specifically for technical writing. Most of today's foundation models have suffered too much reinforcement learning to be clear writers.
Docwriter builds provenance-preserving datasets and evaluations for source-grounded software documentation. It specializes configurable models in writing concise guides and reference pages from code, schemas, tests, and examples without inventing behavior that the evidence does not support.
The project is deliberately narrower than “train on good documentation.” Good prose without code alignment teaches style and hallucination at the same time.
The current work queue lives in TODO.md. Security and artifact-custody rules live in SECURITY.md.
Model and provider configuration #
Dataset construction and deterministic evaluation are model-independent. Candidate model/runtime combinations live in config/models.toml; a profile names its provider, model ID, context window, capabilities, reasoning controls, and dated pricing evidence. Adding or changing a profile must not change the dataset schema.
The current default profile is Qwen/Qwen3.6-35B-A3B on Tinker. It is an Apache-2.0 hybrid MoE with 35B total parameters and 3B active per token. At the current price it costs $1.177 per million training tokens. That is an experiment choice, not repository architecture. Inkling-Small remains configured, and a local model or another provider can replace either profile without changing corpus extraction.
Useful configuration commands:
docwriter validate-models
docwriter estimate-training-cost --training-tokens 1000000
Cost estimation never calls the configured provider. Paid execution is a separate, explicit gate.
The bounded Tinker smoke harness also performs exact local rendering before provider access, freezes repository-disjoint train and validation JSONL, prints a non-cached upper cost bound, and refuses execution unless --execute and --max-cost-usd are both present:
uv run --group train python -m docwriter.tinker_train
uv run --group train python -m docwriter.tinker_train \
--profile qwen36-35b-a3b-tinker --execute --max-cost-usd 0.50
corepack pnpm install
corepack pnpm run pi:tool-specs
Run artifacts are private (0600) and ignored. A completed checkpoint is only a receipt; promotion requires a separate baseline-versus-candidate decision.
The experiment has two related layers:
- Repository editing: use Pi to inspect a pinned source snapshot, choose evidence, edit one documentation target, inspect the effect, and stop.
- Page composition: receive a bounded evidence bundle and write one Markdown page as a component test.
The completed Qwen3.6 run exercised only the second layer through Tinker's sampling client. The model had no tools, repository access, agent loop, Pi session, or persistent-agent runtime. That run established that direct full-page SFT is the wrong primary boundary. The active design trains successful Pi tool trajectories and evaluates filesystem effects. Docwriter still curates the tasks and evidence; an agent loop is not permission to abandon provenance.
See docs/PI_TRAJECTORIES.md for the pinned Pi/pi-tinker runtime, target masking, session conversion, per-decision loss masks, isolation boundary, and patch-level evaluation contract.
Technical-writing curriculum #
The repository now teaches writing judgment as a staged curriculum before treating repository trajectories as gold data. The seven stages are sentence mechanics, reader and purpose, diagnosis and revision, information architecture, source-grounded composition, editorial review, and repository maintenance.
Each stage in curriculum/stages/ contains a competency document, machine manifest, generation procedure, weighted rubric, and checked-in train/evaluation seeds. Shared source, provenance, record, generation, replay, and evaluation contracts live in curriculum/.
uv run docwriter validate-curriculum
uv run docwriter compile-curriculum-seeds --output-dir artifacts/curriculum-seeds
uv run docwriter plan-curriculum-generation \
--output-dir artifacts/curriculum-generation-plan
The checked-in pack contains 38 supervised contract examples and six repository task specifications. They are seeds for generation and regression testing, not a promoted training corpus. Repository task specifications become training data only after a real isolated Pi run passes filesystem-effect and trajectory gates.
The generation planner expands curriculum/generation.toml into 228 deterministic assignments without calling a model. It freezes exact task-family and difficulty quotas, target skills, source permissions, synthetic product universe, train-only demonstrations and hashes, rubric gates, and stage contracts. Evaluation seeds are never demonstrations. The resulting plan remains explicitly blocked on frozen teacher/reviewer prompts and candidate acceptance; it is not a provider request and does not authorize generation.
One assignment can then be bound to immutable prompts, a sample-capable model profile, sampling controls, acceptance contracts, and a conservative cost ceiling. Provider execution is still a separate command with explicit --execute, --profile, and --max-cost-usd gates. Teacher output becomes a proposed candidate without an example ID or split. Deterministic validation emits either reject or needs_review; a separate reviewer can produce accept, but acceptance only makes the candidate eligible for a later freeze.
See docs/CURRICULUM_FLOW.md for the exact flow, commands, and first one-candidate receipts.
Corpus policy #
The first corpus accepts only documentation that is:
- fetched from a public HTTPS repository URL without embedded credentials;
- covered by MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, 0BSD, or an explicit combination of those licenses;
- stored with the implementation in the same cloneable repository;
- manually written as a guide, explanation, how-to, or reference page;
- recoverable at an exact Git commit;
- alignable to concrete code, tests, schemas, or examples from that commit.
CC-BY documentation, books, blogs, translations, generated API pages, release notes, separate documentation repositories, and unclear license cases are outside the first source-grounded corpus pass. Pedagogical references have a separate registry and use policy in curriculum/sources.toml; their broader presence there does not relax this software-corpus allowlist.
Dataset record #
Each accepted record contains:
- repository URL and exact commit;
- repository and documentation SPDX licenses;
- target path and SHA-256;
- page kind, title, and output dialect;
- source evidence as path, line range, SHA-256, and content;
- the rendered training messages;
- deterministic quality checks and rejection reasons;
- split assignment at repository granularity.
The target page is never present in the evidence bundle.
Evaluation #
A candidate is compared with its configured base/control profile at the same renderer, reasoning effort, temperature, and token limits. The frozen evaluation measures:
- unsupported factual claims;
- public API and required-fact coverage;
- symbol, signature, default, and exception accuracy;
- whether code examples parse or run;
- task completion and page structure;
- readability and useful brevity;
- repository-research efficiency when Pi is enabled.
BLEU and target-text similarity are diagnostics, not promotion criteria. A page can be excellent without reconstructing the maintainer's sentences.
Target product repositories are reserved for product-shaped held-out evaluation. Existing product prose is not training gold by default.
The configured source families currently span Python, Rust, Julia, TypeScript/JavaScript, and C++. Rust sources include uv, Cargo, and rustup. Julia sources include the language repository, Pkg, DataFrames, Documenter, and Flux. A manifest entry remains a candidate until its pinned license, inventory, evidence alignment, and sample records pass live audit.
Status #
The deterministic inventory and evidence builder are live; the audited Pydantic pass currently yields 21 high-confidence records after rejecting weak or oversized alignments. Rust path-based command grounding and Julia definitions, mutating identifiers, imports, macros, Documenter directives, and doctest fences are supported and proven against live Cargo, rustup, Julia, Pkg, DataFrames, Documenter, and Flux snapshots.
One Qwen3.6-35B-A3B rank-16 LoRA smoke run has completed: 17 curated Pydantic pages, four repository-disjoint DataFrames validation pages, two epochs, 34 steps, and 301,960 training tokens. The token-rate estimate was $0.4099 under a $0.50 cap. Training loss fell from 1.0819 to 0.1642, but held-out behavior deteriorated: clean terminations fell from 4/4 to 1/4, mean evidence-symbol coverage fell from 1.0000 to 0.2875, and lexical-set F1 fell from 0.3229 to 0.1983.
The clean-termination comparison is partly confounded: the historical run used a uniform 4,096-token output ceiling even though two reference completions were longer and the largest was 12,274 tokens. The adapter still showed direct repetition and severe coverage regression, so rejection stands. Future plans derive a per-record output budget from the frozen reference completion with a 4,096-token floor; the same split now budgets 4,883, 4,096, 6,712, and 15,343 tokens while remaining under a $0.50 upper bound. The candidate is explicitly rejected and has not been activated or promoted. Both private Tinker checkpoints were deleted and verified absent; training-run metadata and local receipts remain.
The trajectory layer now pins Pi 0.83.0 and pi-tinker commit 0428c2431109eb855456f2ff782685756dd25c31, snapshots six non-shell tool schemas, prepares Git-free target-masked repository tasks, rejects out-of-scope mutations, and converts successful Pi v3 sessions into tool-aware Tinker records. Qwen trajectories are split into one LAST_ASSISTANT_MESSAGE datum per tool/final decision because the selected renderer does not satisfy sequence extension for one all-assistant conversation. A live Bubblewrap proof for a real Pydantic task shows the workspace and session directory visible while the target, .git, host home, and oracle remain absent. Teacher execution still needs private file-descriptor credential delivery, an explicit teacher profile/cost bound, and authorization. No additional model or training call has been made.
License #
Docwriter is licensed under Apache-2.0. Third-party source material remains governed by the source and curriculum manifests; inclusion in a Docwriter record does not replace its original license or attribution requirements.