Repository navigation
Releases: jonpol01/d1a
Release list
D1A 0.6.0
D1A also chats. An instruction-tuned D1A build served with its adapter unmerged answers OpenAI chat and embedding requests from the same model that makes decisions, and keeps the prompts it has seen, so an agent's next step costs seconds instead of a full read of its prompt. Decisions can depend on earlier answers, agents get a guide and a tool gate, and every decision stays as it was in 0.5.0.
What's new
- One model for decisions and chat.
d1a.serving.serveanswersPOST /v1/chat/completions(tools, JSON mode, streaming) andPOST /v1/embeddingson an unmerged-itMLX export, so OpenAI clients (mem0, n8n, chat UIs) and D1A's decisions share one model. A decision that arrives during a chat waits for one token or one prompt chunk, not the whole chat. See docs/USAGE.md, "Chat with D1A". - A prompt cache for agents. A chat that continues an earlier prompt prefills only its new tokens: on D1A-E4B-it, an agent step on a 20k-token prompt takes 1–1.7 s instead of 33 s (M1 Max), and 2–3 s instead of 39 s (M4).
- Conditional questions.
ask_ifasks a question only when earlier answers meet a condition, in one request. - D1A for agents. docs/AGENTS_GUIDE.md and
python -m d1a.agents.demoshow intake, tool gating, judging and routing;d1a.agents.claude_code_gateis a Claude Code hook that asks D1A before a tool call runs and falls back to asking you on any failure. - Instruction-tuned models. D1A-E4B-it and D1A-E2B-it v0.6 load and serve correctly (they now get the
<bos>they were trained with), and v0.6.1 adds a routing temperature to the default checkpoints. - More control over self-learning, all off by default.
- Decode rules: a learned rule moves a choice only where its condition holds, and the quality gate checks it fired everywhere it should.
- Flags can run in shadow mode, with thresholds that track the live scores.
- Recalibration can now work per option, hold out the newest outcomes, and keep the model's own answer for held options such as severity P0/P1.
- Logs you can read. The server prints a line per chat with its cached tokens, and docs/CONFIGURATION.md has a Logs section: what is written, and that none of it holds request or answer text.
Added
- The model server prints a line per chat: prompt tokens, how many came from the prompt cache, completion tokens and time (#306). It holds counts only, never text.
D1A_CHAT_LOG=0turns it off. docs/CONFIGURATION.md gains a Logs section: what the server writes, what it never holds, and where the Mac mini keeps it. - The OpenAI chat endpoint keeps a prompt cache (#304). A chat that continues an earlier one prefills only its new tokens: an agent's next step, a conversation's next turn, or a new session with the same system prompt. This covers up to 16 prompts in 1 GiB (
D1A_CHAT_CACHE_MB;0turns it off). With D1A-E4B-it on an M1 Max, an agent step on a 20k-token prompt takes 1–1.7 s instead of 33 s.usage.prompt_tokens_details.cached_tokensreports the tokens taken from the cache. python -m d1a.agents.demo: one task through an agent's day (intake, gate, judge, route). Each step is a D1A call with its top answers and the fail-safe advice.docs/AGENTS_GUIDE.md: what D1A decides for agents, the three ways to plug it in (MCP, the Claude Code gate hook, Python), real demo output, and the measured numbers with their limits. The README gains a "For Agents" section.d1a.agents.claude_code_gate, a Claude CodePreToolUsehook: D1A's gate preset decides allow, ask or deny before an agent's tool call runs. It fails safe: "allow" is passed on only withD1A_GATE_ALLOW=1, and an unreachable D1A asks a person. New settings:D1A_GATE_CARD,D1A_GATE_ALLOW.- Tests prove agents can use D1A: a real MCP client lists and calls the MCP server's tools over stdio on a tiny trained checkpoint, and CI installs the
mcpextra so the MCP server is tested at all. - Conditional questions (#36): a question may carry
ask_if({question id: condition}) and is answered only when the answers it names meet their conditions (a choice's option keys, a noul's true/false, a score's range), in later passes of the same request. Otherwise it is reported as skipped. It is never model input: a request withoutask_ifis answered exactly as before. The server,D1A.decideand the MCP/clients' pass-through take it; permute and separate refuse it. - Learned flags can run in shadow mode (
flags.<name>.mode = "shadow"): the flag is logged but not served, so the response is unchanged (#297). A tracked threshold (flags.<name>.track) is refit by the loop's tick from logged scores without labels, clamped, with aflag_alertnotification when the recent flag rate runs abovealert_rateortsits at a clamp. - Self-learning settings, all off by default (#294):
promotion.form = "vector"(per-option recalibration of choice questions),promotion.split = "time"(the gate holds out the newest outcomes), andpromotion.serve_choicewithpromotion.hold(serve the recalibrated top option, but keep the model's own answer for held options such as severity P0/P1). Flags and rules may name a local-path run. scripts/quality_gate.py --sequential(#286): the base server answers every request, stops, then the head server does, so only one model is in memory at a time. The Mac mini (32 GB, with its other services) cannot hold two, which stopped the
#285 baseline gate there. The latency floor is measured the same way, so the latency check still compares like with like.- OpenAI-compatible chat and embeddings on
d1a.serving.serve(#274), so apps written for the OpenAI API (mem0, n8n, chat UIs) can use D1A's model instead of a separate chat server.POST /v1/chat/completionsanswers with the served model's adapter switched off: an export made with--unmergedon an instruction-tuned base (D1A-E2B-it), whose base is then a chat model. It runs on the model thread between decision requests and supports streaming (token by token), tools (Gemma 4 tool calls as OpenAItool_calls),stopand JSON mode.POST /v1/embeddingsserves a GGUF embedder through llama.cpp (D1A_EMBED_GGUF, the newembedextra)./v1/modelsalso lists the models in OpenAI form beside the TypeSafe listing.- New settings:
D1A_CHAT_NAME,D1A_EMBED_GGUF,D1A_EMBED_NAME. Guide: "Chat with D1A" in docs/USAGE.md. - An
--unmergedexport also carries the base'sgeneration_config.json, so chat stops at its end-of-turn tokens.
- One model that decides and chats on Apple Silicon (#274):
scripts/export_mlx.py --unmergedkeeps the LoRA out of the weights (adapter/, attached at load byd1a.backends.mlx.attach_lora), andMLXDecisionModel.adapter(False)switches it off per request, giving back the base model exactly. On an instruction-tuned base (D1A-E2B-it) that base is a chat model.D1A_MERGE=0now attaches a training run's adapter the same way on MLX. The export also carries the base'schat_template.jinjawhen the run has none. D1A-E2B-it, unmerged 8-bit: 0/42 flips against fp32 on the 28-request bench (max |dp| 0.062, 288 ms per request; the merged build: 0/42, 0.015, 160 ms). scripts/export_mlx.py --q4-0writes 4-bit weights in groups of 32 on llama.cpp's Q4_0 grid (scale = d, bias = −8·d;d1a.backends.q4_0, numpy only, andd1a.backends.mlx.q4_0_mlx) instead of mlx-lm's min/max scales, for a base trained for Q4_0 such as Gemma 4 12B-it QAT (#270). On that checkpoint Q4_0's weight error is about 20% below mlx-lm's 4-bit group 32 and 30% below group 64. The export'sd1a_config.jsonrecords"grid": "q4_0".- Gemma 4's unified model (the 12B,
google/gemma-4-12B-it-qat-q4_0-unquantized) as a base on both backends (#270). PyTorch runs it through transformers as it already ran the E-series. MLX loads it through mlx-lm's Gemma 4 classes (d1a.backends.mlx.unified_classes): the unified checkpoint's vision embedder is dropped on load, and the unified model's zero per-layer-embedding and KV-sharing sizes are stated rather than taken from mlx-lm's E-series defaults. No D1A-12B is trained yet. - Pull-request checks adapted from NousResearch/hermes-agent (MIT):
blob size(no new file, and no changed binary, over 500 KiB;scripts/check_blob_size.py, exemptions in.github/blob-size-allowlist.txt),shared history(the branch shares history with main) anduv.lock in sync(uv lock --check, uv pinned), plus a weekly OSV-Scanner run onuv.lock(Security tab, detection only) (#269). - Served decode rules (
rules.<name>in theD1A_LEARNINGsettings,d1a.learning.rules): for a request that sends the rule'suse_caseto the run it was fitted for, the served choice of one question becomes the rule's option when the model's choice is one offromand its probability of that option reachest, and the answer names the rule (rule: name, from, p, t). Only the choice moves: the probabilities, the confidence and the decision log's answers stay the model's, and the log records what was served (meta.served,meta.rules). Written for the live PR labeler: severity's P3/P4 answers are served as P2 when p(P2) >= 0.20, which brought D1A-E4B v0.6 closer to hermes-agent's written label definitions on held-out pull requests (#252). A client that does not send the use case gets exactly the answers it got before. scripts/quality_gate.py --head-learning <file> --labeler-use-case <name> --expect-flips <question>: gates a decode rule; the head server reads the settings file, the labeler replay sends the use case, flips on that question are listed with the probability that crossed the threshold, and every other change still fails.- Pull-request hygiene in CI, inside the existing required checks:
verified commitsrefuses AI attribution in commit messages or the pull request body;package builds and installsrefuses a pull request without itschanges/<PR>-<slug>.mdfragment (Dependabot and releases exempt) and any added line ...
D1A 0.5.0
D1A learns from its outcomes on its own: a settings file for the self-learning loop, replay of every earlier outcome onto a new model, a scheduled gate, and learned flags served beside the answer. The 0.3 module paths are gone, and the model server no longer aborts when photo, voice or video requests overlap other work on Apple Silicon.
What's new
- Self-learning you can configure.
D1A_LEARNING=<file>holds the loop's settings (python -m d1a.learning.feedback config init|show|set|validate);feedback tickreplays earlier outcomes onto the served model, fail-closed on anything it trained on, and runs the promotion gate after N new outcomes or at a daily time. See docs/CONFIGURATION.md, "Self-learning". - Learned flags in answers. A flag fitted offline with
python -m d1a.learning.flagsand listed in the settings file is served inside its question's answer (flags.<name>), without changing the answer. None is configured by default. - A stable server on Apple Silicon. Overlapping media and text requests no longer crash the server (#244): a 45-minute stress run of 756 media and 2,136 text requests ended with no abort, where 0.4.0 aborted within seconds.
- A smaller package surface. The 27 flat module paths of 0.3 are removed, and new runs are saved without
head.pt.
Added
recipes/swe-verifier: OpenHands runs (nebius/SWE-rebench-openhands-trajectories, CC-BY-4.0) can be scored, throughfrom_openhands.pyandzero_shot.py/self_improve.py --source openhands(#248). Also new:--keep-issue-md(keep the harness's issue.md diff, to measure that shortcut),--exclude(a sample disjoint from an earlier draw) and--issues. The default source, the SWE-agent runs, gives the same rows as before.- The server serves learned flags (#237, part 2): a flag in the
D1A_LEARNINGfile'sflags, fitted for the served run, is added inside its question's answer (flags.<name>:on,p,t,space), computed on the model's own probabilities before the outcome calibrator, and kept in the decision log's meta. The answer never changes, and the file is re-read when it changes, so a flag is served or withdrawn without a restart. - Self-learning v2 (#233, #240):
- Settings file.
D1A_LEARNING=<file>holds the loop's settings, validated, with a last-valid fallback and an audit log.python -m d1a.learning.feedback config init|show|set|validatemanages it. Unset, everything behaves as before. - Replay.
feedback replayandfeedback tickre-score an earlier model's decisions that have outcomes through the live server (headerx-d1a-replay-of), so a new model starts with every earlier outcome. It is fail-closed: no replay without the served model's complete trained manifest (feedback trained), and none for a decision whose PR or state is in that model's training, or that has no PR id. For E4B v0.6, v0.2 run 1's mix is lost, so its subsets are covered by the whole partitions they came from (night2 dates and unknowable, the hard, devtools and documents train partitions): the exclusion is a superset by partition. - The tick runs the gate after N new outcomes or at a daily time. Each gate report gives
mde, the smallest log-loss gain its held-out set can detect, and the gate's interval level is a setting. d1a/core/settings.pylists everyD1A_*setting with its default and reader, every module reads them through it, andpython -m d1a.serving.serve --show-configprints them (secrets masked).
- Settings file.
Changed
- A new run is saved without
head.pt(d1a_config.jsonandhead.safetensorsonly), as 0.4 announced. A run from before 0.4 keeps itshead.pt, rewritten with the new metadata (a recalibration, say), so its two copies never disagree; readinghead.ptstays.
Removed
- The flat module paths of D1A 0.3 (
d1a.serve,d1a.feedback,d1a.train, and the 24 others;python -m d1a.<old>), shims since 0.4: import and rund1a.serving.serve,d1a.learning.feedback,d1a.training.train, ... instead.d1a/_layout.pykeeps the old-to-new map for the provenance scripts. No caller of ours used an old path.
Fixed
- The model server could abort on Apple Silicon ("commit an already committed command buffer") when photo, voice or video requests overlapped other work (#244): torch's MPS stream is not thread-safe, yet an MLX model's thread synced it on every batch while the media encoder used it from a request thread. With an MLX model the model thread no longer touches torch's MPS stream (MLX waits for its own work), and the encoder copies its result to the CPU under its lock. Text and media still run side by side.
Models tested with this release
| Checkpoint | Hugging Face tag |
|---|---|
| D1A-E4B v0.6, PyTorch | JohnP1/d1a-e4b@v0.6 |
| D1A-E4B v0.6, MLX 8-bit (with photo, voice and video encoders) | JohnP1/d1a-e4b-mlx-q8@v0.6 |
| D1A-E4B v0.6, Core ML (text, ≤ 512 tokens) | JohnP1/d1a-e4b-coreml@v0.6 |
| D1A-E2B v0.6, PyTorch | JohnP1/d1a-e2b@v0.6 |
| D1A-E2B v0.6, MLX 8-bit (with photo, voice and video encoders) | JohnP1/d1a-e2b-mlx-q8@v0.6 |
| D1A-E2B v0.6, Core ML (text, ≤ 512 tokens) | JohnP1/d1a-e2b-coreml@v0.6 |
The Mac mini serves JohnP1/d1a-e4b-mlx-q8@v0.6 with this code (demo smoke test 145/145), and the quality gate on real weights finds no changed answer between 0.4.0 and 0.5.0. Package 0.5.0 serves every model tag from v0.1 to v0.6.
Upgrade notes (from 0.4.0)
- The 0.3 module paths (
d1a.serve,d1a.feedback,d1a.train, ...) are removed: used1a.serving.serve,d1a.learning.feedback,d1a.training.train, ... (d1a/_layout.pylists every old-to-new name). - New runs have no
head.pt, so D1A 0.3 cannot load them; 0.4 and later can. Runs that have ahead.ptstill load. - New settings:
D1A_LEARNING(the self-learning settings file). With it unset, nothing changes. - An answer may carry a
flagsfield when a flag is configured; clients that read answers strictly should allow it.
D1A 0.4.0
D1A on the v0.6 models: a package layout of its own, release rules with statistical gates, use-case temperatures, Core ML builds for the Apple Neural Engine, one page for every setting, and the first learned decision flags.
What's new
- v0.6 models. D1A-E4B v0.6 and D1A-E2B v0.6 are the defaults (
d1a.core.versions.LATEST). E4B labels current pull requests better than v0.5 (severity 79.1%, change type 90.6% on 487 PRs newer than its training). E2B v0.6 learns every D1A skill at a fraction of the cost (0.20 s per request on a Mac mini M4). Both ship as release exceptions, with the reasons in their model cards. - Apple Neural Engine. Both models run through Core ML (
JohnP1/d1a-e4b-coreml,JohnP1/d1a-e2b-coreml, text up to 512 tokens, with a reference runner); README "Apple Neural Engine (Core ML)" compares all four builds. - Every setting in one place.
docs/CONFIGURATION.mdlists eachD1A_*variable and server flag with its default and when to change it, a test keeps it complete, andCLAUDE.mdpoints coding agents at it. - Safer releases and training.
scripts/decide.pyandscripts/quality_gate.pygate model and serving changes on real weights; a fine-tune refuses a mix that drops a trained skill; calibration refuses data the model trained on; every pull request needs signed (Verified) commits and the CI checks to merge. - Use-case temperatures and learned decision flags (
d1a.learning.flags: a threshold on a group of options, fitted offline, reported beside the answer; served from 0.5.0).
Added
d1a.learning.flags(#237, part 1): learned decision flags. A flag is a group of a choice question's options plus a threshold, raised when the top answer is in the group or the group's probability reaches the threshold; it is read on the model's raw probabilities and never changes the answer.python -m d1a.learning.flags fitsets the threshold at a flag-rate budget on a labelled kit,gateserves it only if it catches more positives than the top answer on a later kit (bootstrap over groups) within the budget. Serving the flag and its settings come with part 2, after #233.- A
verified commitscheck fails a pull request when any of its commits lacks GitHub's Verified badge. With the CI tests, lint, the package build and the license check, it is required before anything merges intomain(CONTRIBUTING.md, "Pull requests"). - docs/CONFIGURATION.md: every setting in one place: the server's flags, all 20
D1A_*variables (12 were documented nowhere), the self-learning loop and its commands, photos and voice, agents and evaluation. Each entry gives its default and when to change it. tests/test_configuration_doc.py keeps the page complete, and a CLAUDE.md points coding agents at AGENTS.md and the page. - README: an "Apple Neural Engine (Core ML)" section with a table comparing the four D1A builds (E2B and E4B, MLX and Core ML): inputs, length, API, size, speed and answer changes. The Core ML builds are private previews (#224, #231).
- AGENTS.md: a "Mac resources" section (mandatory). Long jobs on the M1 Max start behind a disk and RAM preflight, fp32 models of 4B or more run only overnight with 25 GB free, a guard pauses D1A jobs on low disk or fast swap growth, and Hugging Face cache entries are deleted only through
scan_cache_dir(#228). d1a.serving.serve(#221): a request that sends the headerx-d1a-decision-log: offis answered but kept out of the decision log (D1A_FEEDBACK_LOG), on/v1/systemone,/v1/systemone/media,/permuteand/separate. The playground's demos and its smoke test send it, so the log's pending decisions are the ones an outcome can still follow.recipes/swe-verifieranalysis tools (#220), each run on savedzero_shot.pyoutputs, CPU only and with no model:recompute_gate.py(the promotion gate between two scored files, with Platt scaling and with D1A'sOutcomeCalibrator, out of fold by repository),refit_heuristics.py(the heuristics' regression refitted on dev, on train and on both, scored on the same test rows),token_lengths.py(the token length of every scored input, after checking the rebuilt selection against the saved file run for run) andlatency.py(seconds per run).best_of_k.py --pair A:Badds the paired difference between two scorers without moving its other numbers.recipes/swe-verifier/reproduce.sh(#220) downloads the study's scored rows and r1 adapter (JohnP1/d1a-swe-verifier-eval,JohnP1/d1a-swe-verifier-r1; private until the release,REVISIONpins them), checks them against their manifest, reruns the scripts that wrote each output and compares every one byte for byte.recipes/mix/build_mix.py, the plan-driven training-mix builder that built D2 and D2-skills (#202), with its plans (recipes/mix/plans/) and a verifier (recipes/mix/verify_mix.py). A plan sets each source's count, stratification and weights, label quotas and an "only unseen" filter. Every eval partition and eval-only kit is screened out (exact state, PR id including the hand-labelled PRs', near-duplicates of the nearest eval state), and the same plan and seed give the same bytes, whatever PYTHONHASHSEED is. A plan that leaves out a training source or decision-v7 is refused unless it names it with a reason, and the sidecar is the oned1a.training.trainchecks. The verifier checks every record byte for byte against its source and reruns the leak screens.scripts/quality_gate.py --head-run: gates a new checkpoint against the one it replaces (#175).- The head server and the head suites load the new weights; the base keeps
--run. - Changed answers are listed for review instead of failing, since a new checkpoint is meant to change some.
- Still failures: lower accuracy or worse calibration on a suite, latency above the noise floor, or a request one side does not answer or answers with other options.
- The head server and the head suites load the new weights; the base keeps
scripts/compare_checkpoints.py: a training run's verdict table from its benchmark folders (#175).- Per suite: each checkpoint's accuracy, and its difference from a reference with a paired 95% bootstrap interval that resamples whole records.
- ECE at each checkpoint's fitted temperature, from the rows' logits.
- A pre-registered bar (
--bar SUITE=MIN,--floor): exit 1 when a run misses it.
recipes/skills/: the skills stage D1A-E4B never had (#175).- The mix: every hard-v1 and devtools-v1 training record plus replay, 15,320 records with the trainer's decision-v7 replay.
- An HF Jobs script. It first runs 5 steps on the 40 longest states to test memory, then trains and calibrates as v0.4 was.
- The #167 B0 pilot (300 steps) already raised hard-v1 and devtools-v1 by about 5 points each.
- Fine-tunes keep the skills (#167):
recipes/pr-labeler/mix.py --replay-skills N(default 500 per suite) replays the hard-v1, devtools-v1 and documents-v1 train partitions;--replay-skills 0rebuilds the v0.3 and v0.4 mixes byte for byte.scripts/quality_gate.py --card-suitesalso scores the five card suites.recipes/README.mdand AGENTS.md make both the rule for any run that starts from a trained checkpoint. scripts/quality_gate.py --base <ref> [--head <ref>](#156): the quality gate for model, loading and serving changes. Twod1a.serveinstances, one per version, on the same real weights answer every demo example (regenerated from a d1a-playground checkout with--playground) and the labeler replay, alternating which goes first. A base/base run measures the noise floor. It fails on a changed choice, a probability moving more than--tol(1e-6), a request only one side answers, latency above the floor, or, with--suites, a frozen suite whose accuracy or calibration gets worse; exit 1 on FAIL. AGENTS.md makes it the required step for model and serving PRs and playground pin moves.python -m d1a.feedback promote <log> --calibrator <file>(#148): the promotion gate on a live decision log. Whole groups (an outcome's optionalgroup, e.g. a pull request;POST /v1/feedbacktakes it) go to the fit or the held-out side. A calibrator fitted on the fit side replaces the served one for a question only if it passes the gate on the held-out side; the file is written whole and renamed into place, sod1a.servereloads it safely.- Self-learning for choice questions (#139).
d1a.feedback'sOutcomeCalibratorfits one temperature per choice question on the outcomes, which changes how sure an answer is but never which option it picks;gate_choice()applies the promotion rule to the multi-class log loss. Outcomes name their source (POST /v1/feedback'ssrc, ormeta["src"]): labels merge per question, and a person's (human) beats another model's (e.g.reviewer) whatever arrived last.python -m d1a.feedback status|records|calibrate --srckeeps one source. First stream: the PR labeler's type and blast-radius questions, with the review bot's labels as outcomes. - A versioned format for training runs (#64):
d1a_config.json(formatd1a-torch, version 1: the base, the head's size, the temperature,weights, and the run's recorded arguments as JSON) with the pointer head inhead.safetensors, the file names MLX exports already use.d1a.trainandd1a.calibratewrite it, and loading it unpickles nothing. A run that has both it and ahead.ptmust say the same in both, or loading stops and names the field. Runs saved before it (head.pt only) load as before, with no end date; published checkpoints are unchanged. - Conformance against committed golden vectors (#64):
tests/golden/tiny-gemma4/holds a tiny Gemma 4 checkpoint (weights in git, 556 KB, built bytests/golden/build_tiny_gemma4.py) andgolden.json, with 40 requests and 154 questions.tests/test_conformance.pyrequires its token ids exactly and every probability to 1e-5, through the model,d1a.serveandscripts/golden_vectors.py compare. - Training recipes (#63): `python -m d1a.recipe run <recipe.yaml> --out <dir...
D1A 0.3.0
D1A learns from outcomes. Every decision can be logged with the model that made it; when the real outcome comes back (the tests passed, a maintainer corrected a label), D1A recalibrates on it at once and promotes a retrained model only through a statistical gate. Measured first on a coding-agent verifier: three rounds of 300 outcomes cut the calibration error from 0.068 to 0.017, and the gate kept the model each time retraining was not clearly better. This release also makes D1A more its own: Kev's playground, study harness, training-history suites and suite builders are gone (70% of the code is Kev-derived, from 77%), and Gemma 4 runs about a third faster on torch and 20–30% faster on Apple Silicon.
Added
recipes/swe-verifier: D1A as a verifier of coding-agent runs (issue, patch and the end of the run → P(the patch resolves the issue)), on public SWE-agent runs (nebius/SWE-agent-trajectories, CC-BY-4.0) held out by repository: zero-shot and LoRA scoring, held-out evaluation with repository-bootstrap intervals and within-issue AUROC, best-of-k, a training-data filter, anytime restarts under a budget, a split-leakage study (random runs vs by issue vs by repository), the self-improvement rounds throughd1a.feedback, competition-harness input (from_swegemma.py), verified references, and the paper draft.GET /metrics(d1a.serve): Prometheus text format: whether the model is loaded, requests and batches served, queue depth, recent batch latency (p50, p95, max), prefix-cache hits, misses and states, peak process memory and the model's device memory. Like/v1/models, it does not load an unloaded model.- README: the self-learning loop's measured rounds (calibration error 0.068 → 0.017; the gate kept the model when retraining was not clearly better), also on the diagram.
- Self-learning as a service (
d1a.serve):D1A_FEEDBACK_LOGlogs every decision and returns adecision_id;POST /v1/feedbackrecords its real outcome;D1A_OUTCOME_CALIBRATORappliesd1a.feedback's calibrator to yes/no answers and re-reads it when the file changes./v1/modelsreports the learning state. - README: Self-Learning. A section on how D1A learns from outcomes (usage, CLI, the verifier's measured numbers), step 5 in How It Works, and an animated diagram of the loop (
docs/arch/learning-{light,dark}.svgfrommake_svgs.py). d1a.feedback: learning from outcomes. AFeedbackLogrecords each decision and, later, what actually happened (a patch passed its tests, a label was corrected);OutcomeCalibratorrefits each yes/no question on those outcomes (Platt scaling on the logit, which also corrects a shifted base rate that one temperature cannot);records()turns them into training data for a small LoRA update;gate()promotes a candidate only when its held-out log loss is lower with a bootstrap interval clear of zero and no frozen suite regressed.python -m d1a.feedback status | records | calibrate. Fitted values match scikit-learn's logistic regression on D1A-E4B v0.4's verifier outputs.d1a.versions: the newest released checkpoint in one place.latest("JohnP1/d1a-e4b-mlx-q8")givesJohnP1/d1a-e4b-mlx-q8@v0.4; the Space takes its default from it.tests/test_versions.pyfails whenLATESTand the first row per model of the "Model versions" table below disagree, so a release cannot leave a default behind.- A Hugging Face Space, ready to publish (
space/,space/push.sh; not hosted yet: a Gradio or ZeroGPU Space needs HF PRO): model routing, guardrails, tool-call gating, the PR labeler (paste a GitHub PR link) and zero-shot photo and voice checks on D1A-E4B v0.4. Oned1a.media.MediaModelanswers all of them:MediaModel.probsnow also scores a text-only record (media None) on the same weights. - Kev's eval suites as D1A suites:
external(semif-v1, typesafe-v1, wanli-v1, wanli-v2) andnight2(dates, unknowable facts, assertions) areevals/d1a/externalandevals/d1a/night2, with the data byte for byte in the private datasetJohnP1/d1a-evals.d1a.benchmark --data evals/d1a/external:semif-v1scores the same as the Kev suite did (D1A-E4B: identical probabilities on all 252 rows).python -m d1a.suites freeze --provenancerecords where a suite's data came from. - D1A suites (
d1a/suites.py,evals/d1a/). Our own datasets, pinned: a manifest per dataset holds its Hub commit and each partition's sha256, record count and role (train or eval), while the text stays in the private dataset.evals/d1a/<suite>:<partition>works wherever--datadoes (d1a.train,d1a.benchmark). The partition is fetched at the pinned commit and checked before use, andd1a.trainrefuses an eval partition before loading the model.pr-labels,ja-jglueandroutingare frozen. The PR-labeler recipe reads them throughrecipes/pr-labeler/mix.py, which replaces the job's inline mixing script (byte-identical mixes, round 2's included) and records the inputs' hashes, the parameters and the mix's sha256 beside it.EXTRAnow names partitions (train-ja train-blast), andDATAis gone. - Video.
POST /v1/systemone/mediatakes{"type": "video"}: an MP4, MOV or WebM clip up to 32 MB, of which 16 frames are sampled evenly and read through Gemma 4's own video path (timestamped frames, the same vision encoder), by the same model on MLX and ind1a.media. The sound track is not used. Zero-shot: on single-scene clips D1A-E4B v0.3 answers as it does for the still photo; questions about the order of events are not reliable yet. New dependency in themediaextra:av(PyAV). - One model for text, photos and voice.
d1a.serveanswersPOST /v1/systemone/media(a request plus a photo or voice clip) with the model it already serves: Gemma 4's vision and audio encoders turn the media into soft tokens, and the same MLX language model reads them. The encoders (~1 GB for E4B) ship in an export'smedia/folder (scripts/export_mlx.py --media, or--media-onlyfor an existing export), and are fetched and loaded on the first photo or voice request. On the playground's 10 samples D1A-E4B v0.3 matches its own PyTorch path (0 of 20 answers change, max |Δp| 0.065).python -m d1a.mediastays for PyTorch checkpoints. d1a.serve --idle-unload SECONDSdrops the model (and the media encoders) after that long without a request and loads it again on the next one: 2.0 s for D1A-E4B on an M1 Max, which goes from 6.4 GB in use (encoders loaded) to 1.3 GB.GET /v1/modelsanswers without loading the model and reportsloadedandidle_unload_s. Default 0 (always loaded), as before.recipes/pr-labeler: how D1A-E4B v0.3 learned to label pull requests, as scripts: fetch labeled PRs from GitHub, build records with the labeling job's own questions, translate them into other languages (on MLX or any OpenAI-compatible server), train and score on one GPU (Hugging Face Jobs or your own), and read the results.
Changed
-
Served PR requests about 20% faster on Apple Silicon (MLX). The question rows ran through all of Gemma 4's layers; its 18 KV-shared layers now run only at the positions the pointer head reads, each as a length-1 row at its own rope offset with an explicit causal (and sliding-window) mask over the shared keys (
MLXDecisionModel._picked_hidden). D1A-E4B 8-bit on an M1 Max, 30 PR requests: 2,503 -> 1,955 ms per request (p50; the question rows 1,689 -> 1,013 ms). Exact in fp32 (D1A-E2B, 90 questions: max |dp| 2.3e-6); at 8 bits it moves answers less than running the rows one at a time instead of batched already does (max |dp| 0.015 vs 0.019). -
Gemma 4 trains and scores about a third faster on torch (the path HF Jobs uses). Its last 18 of 42 layers (E4B; KV-shared) take keys and values from earlier layers, so in the packed forward they now run only over the positions the pointer head reads (each question's
<decide>and option ends, about 10-20 per record) instead of the whole document (d1a.backbone.Gemma4.picked_hidden). D1A-E4B v0.4 on 2 PR records (Apple M1 Max, bf16): scoring 4.84 -> 3.00 s, a LoRA training step with checkpointing 12.9-14.0 -> 8.5-8.9 s. Exact in fp32 (D1A-E2B, 120 questions: max |dp| 1.8e-6, gradients equal to 1e-5); in bf16 it moves answers less than changing the batch size does (438 questions: mean |dp| 0.0011 and 2 flips, against 0.0060 and 5 for batch 1 vs 2). -
Model families behind one interface.
d1a.backbonenow also decides whether the MLX backend runs a base (Gemma 4 and Qwen3.5; it was amodel_typelist ind1a.checkpoint) and loads Qwen3.5's CUDA serving kernels (d1a.fused_qwen35,d1a.cuda_graphs) throughQwen35.serve_cuda, so Gemma 4 is the main path and Qwen3.5 a plug-in.d1a.model.is_hybridandd1a.model.sliding_windoware gone: used1a.backbone.for_config(config). Answers and latency are unchanged. -
PR-length requests are about 30% faster on Apple Silicon, with identical answers. The state pass (the document, run once before the questions) now stops before Gemma 4's KV-shared layers: those layers keep no cache of their own and nothing reads the state's own outputs, so for the state they were pure waste (18 of D1A-E4B's 42 layers). On an M1 Max, 60 held-out pull requests (median 1,584 state tokens): state pass 3,470 -> 2,137 ms, whole request 4,465 -> 3,082 ms (medians), every probability bit-identical; photos, voice and video too (a 16-frame clip's state pass 1,805 -> 1,079 ms). Other backbones run the full pass as before.
-
Downloading a model from the Hub skips an export's
media/folder (1 GB) until a photo or voice request needs it.
Removed
- Kev's suite builders (about 6,900 lines, 93-100% Kev's): the hard-v1, breadth-v1, devtools-v1, longdoc-v1, documents-v1/v2 and transfer-v9 builders and generators,
build_binding_diagnostic.py,devtools_v1_licences.json, and the tests that only exercised them. The suites stay as frozen data; `tests/test_frozen_su...
D1A 0.2.0
The first D1A release: Kev's decision model moved onto Gemma 4, with trained checkpoints, an Apple Silicon backend, agent tools, model routing, and photos and voice.
What's new
- Gemma 4 is the default base. D1A trains and serves on Gemma 4 E2B and E4B. The Qwen bases Kev was built for still work.
- Better models. D1A-E4B v0.2 (hybrid) adds Kev's later training stages, Japanese decisions and agent routing on top of v0.1: Japanese 0.664 → 0.835, English on new sources 0.680 → 0.704, English on trained sources unchanged (0.848), agent-factory routing 0.933.
- Fast on a Mac. On Apple Silicon D1A runs on MLX with 8-bit exports that are checked against the PyTorch reference, and Gemma 4's per-layer embeddings are read from disk per request: E2B uses 3.0 GB instead of 4.2 GB.
- Use it from agents. Ready-made question sets (intake, judge, tier, gate, route) and an MCP server turn each decision into one tool call, and D1A can say whether a prompt needs a small, medium or large model.
- Use it in your own code.
from d1a import D1Aloads a checkpoint once and answers questions in-process, with no server. - Photos and voice notes.
d1a.mediaanswers the same typed questions about an image or a short audio clip, using Gemma 4's own vision and audio encoders (no captioning or speech-to-text). It loads its model on the first request and frees it when idle.
Added
- MLX backend for Gemma 4 (sliding-window and KV-shared layers), quantized MLX exports (
scripts/export_mlx.py) and golden-vector parity checks (scripts/golden_vectors.py) (#39). - Gemma 4 per-layer embeddings read from the weight files per request instead of held in memory, with bit-identical results: 1.3 GB less for the 8-bit E2B export, 1.5 GB less for E4B (
D1A_PLE_FLASH=0to decline) (#90). d1a.D1A: load a checkpoint once and answer questions in-process (#47).- Agent-factory presets and an MCP server (
d1a.mcp_server, extramcp):d1a_intake,d1a_judge,d1a_tier,d1a_gate,d1a_route,d1a_decide(#52). d1a.media: System One questions about a photo or a voice clip (POST /v1/systemone/media, extramedia) (#83), loaded on demand and dropped after--idle-unloadseconds (#85).d1a.serve:--devicewith a kernel probe and a startup self-check of the readout (#43); recent batch latency in/v1/models(#48); a warning when the checkpoint is not calibrated, andcalibratedin/v1/models(#54).- Training: resume points for LoRA runs (#46);
--extra_suitesto train on several frozen suites in one run (#67). d1a/backbone.py: what differs between model families in one place, for the torch and MLX paths (#70, #77).scripts/kev_share.py: how much of D1A is still Kev's code, by area (#76).- Reports: model routing v0.1 findings, English and Japanese (#56); Apple's Core AI and Foundation Models (macOS and iOS 27) compared with D1A (#86).
- README: GIFs of the eleven playground demos, and animated architecture diagrams generated by
docs/arch/make_svgs.py(#44, #83, #84). - CI: Python 3.12 and 3.13, MLX tests on Apple Silicon, a lint pass, a package build and install check, and this release workflow.
Changed
- The package, modules and settings are D1A's:
kev→d1a,KEV_*→D1A_*environment variables, model named1a-latest(kev-latestandjev-lateststill answer) (#1). - The default training base is Gemma 4 E2B, and
backend="auto"picks MLX for Gemma 4 on Apple Silicon (#1, #39). - Model routing sends a prompt to the small tier only at p(small) ≥ 0.7, so close calls go up a tier (#55).
- The frozen suites are pinned at kev-suites
cc4bac8, which adds the hard-v1 and documents-v1 training partitions (#68).
Fixed
- A proxy in front of
d1a.serve(the playground's) could reuse a connection the server had just closed (ECONNRESET): the server now keeps idle connections for 75 s (#53). - A GPU whose driver or PyTorch build cannot run a kernel no longer crashes the server on its first request: the device is probed at startup and the server falls back to the CPU (#43).
- A non-finite loss or gradient skips its batch instead of ending the run; three in a row still end it, because then the weights themselves are broken (#46).
Removed
- Kev's research tooling, deployments and records that D1A does not use: 26 files, about 5,000 lines (#69).
Models tested with this release
| Checkpoint | Hugging Face tag |
|---|---|
| D1A-E4B v0.2 (hybrid), PyTorch | JohnP1/d1a-e4b@v0.2-hybrid |
| D1A-E4B v0.2 (hybrid), MLX 8-bit | JohnP1/d1a-e4b-mlx-q8@v0.2-hybrid |
| D1A-E2B v0.2.1 (2 epochs, calibrated), PyTorch | JohnP1/d1a-e2b@v0.2.1-2epoch-calibrated |
| D1A-E2B v0.2, MLX 8-bit | JohnP1/d1a-e2b-mlx-q8 |
Upgrade notes (from Kev)
- Rename imports and commands:
python -m kev.serve→python -m d1a.serve,from kev...→from d1a..., andKEV_*environment variables →D1A_*. - On Apple Silicon the server now uses MLX for Gemma 4 by default; set
D1A_BACKEND=torchfor PyTorch MPS. - Optional features are extras:
serve(HTTP server),mlx(Apple Silicon),media(photos and voice),mcp(agent tools).