Skip to main contentSkip to footer
Julian SchmidtJulian Schmidt

Applied AI systems · Apple Silicon

Engineering a Private, Source-Traceable Coding Model

Julian Schmidt · July 16, 2026
Engineering a Private, Source-Traceable Coding Model

Executive summary

I set out to answer a focused engineering question: how far can I take my own repository history toward a private local coding assistant while preserving provenance, evaluation boundaries, and operational control?

I built and verified the complete systems path: repository discovery, contribution attribution, privacy filtering, repository-level splitting, deduplication, MLX-VLM LoRA training, PEFT and GGUF conversion, Ollama packaging, and Codex integration. The pipeline audited 60 repositories, matched 3,018 of my commits, retained 8,361 deduplicated rows from 53 repositories, and produced a local Gemma 4 E4B adapter under the tag gemma4-e4b-julian:latest.

StageStateEvidenceMeaning
Repository provenanceComplete60 processedMy contribution history measured
Dataset buildComplete8,361 rowsFiltered corpus reproducible
Training compatibilityPassedreturn code 0MLX LoRA path verified
Adapter packagingPassed343 tensor pairsPEFT and GGUF exports verified
Local deploymentPassedOllama + CodexAgent workflow operational
Table 1. Current pipeline state. Only measured or directly recorded states are shown.

The current evidence supports three concrete conclusions:

  • I can trace every stage. Repository selection, contribution history, filtering, deduplication, split assignment, token counts, model revision, adapter metadata, converter revision, and runtime checks all have machine-readable records.
  • The local agent path works end to end. My Ollama model selected a native tool, cleared explicit latency and throughput thresholds, and completed a real file-edit task whose test passed.
  • The measurement boundary is explicit. I report systems compatibility and deployment behavior here. I keep held-out model-quality claims separate until they are backed by a controlled base-versus-adapter evaluation.

Corpus provenance

I use repository history as the source material rather than treating the current checkout as a folder dump. My discovery stage includes every visible, non-fork, non-archived repository allowed by configuration. The statistics stage walks full Git history with rename detection and attributes commits against my configured email identities.

The resulting snapshot processed all 60 discovered repositories and attributed 3,018 commits, 868,278 added lines, and 302,760 removed lines to me. My net contribution is 565,518 lines across 1,171,038 changed lines. These figures measure contribution history, not physical lines in the current checkout.

Monthly commits matched to my configured identities. Activity rises sharply in 2026, with 2,721 of 3,018 commits recorded that year.
Figure 1. Personal commits over time. Monthly commits matched to my configured identities. Activity rises sharply in 2026, with 2,721 of 3,018 commits recorded that year.
Added, removed, and net lines by month. Large swings are preserved as contribution churn rather than presented as current physical lines of code.
Figure 2. Lines changed over time. Added, removed, and net lines by month. Large swings are preserved as contribution churn rather than presented as current physical lines of code.

Language profile

TypeScript dominates my historical contribution profile, followed by Python. Language commit counts are non-exclusive: one commit is counted once for every retained language it touches.

LanguageCommitsAddedRemovedChangedNet
TypeScript1,079694,800264,736959,536430,064
Python313110,03819,476129,51490,562
YAML7323,3229,63532,95713,687
JavaScript13821,4552,48823,94318,967
CSS12311,6404,73116,3716,909
SQL483,4538094,2622,644
Table 2. Leading languages by changed lines. Canonical source: stats/stats.json → by_language.
Added plus removed lines for every retained language. The profile explains why the resulting corpus is primarily TypeScript and Python.
Figure 3. Language distribution by changed lines. Added plus removed lines for every retained language. The profile explains why the resulting corpus is primarily TypeScript and Python.

The statistics exclude binary changes, ignored current paths, configured generated and vendored directories, lockfiles, minified files, and unsupported extensions. Deleted historical paths can only be filtered by their historical name and extension because today's ignore rules no longer exist at that commit.

Dataset construction

I transform eligible files into prefix-and-completion rows while preserving repository, path, language, and source metadata. From 5,501 considered files, the builder selected 5,455 and produced 9,443 candidate chunks. Exact SHA-256 deduplication removed 533 rows; MinHash near-deduplication removed another 549. The retained corpus contains 8,361 rows and approximately 4.97 million lexical tokens.

MeasureValueControl
Repositories with rows5348 train, 5 valid
Files selected5,4555,501 considered
Candidate chunks9,443896-token target
Exact duplicates removed533SHA-256
Near duplicates removed549MinHash at 0.85
Rows retained8,3617,172 train, 1,189 valid
Lexical tokens4,968,790all retained rows
Table 3. Dataset ledger. Canonical source: datasets/raw-max/statistics.json.

Split integrity

I assign train and validation data at repository granularity, so no repository contributes to both sides. The current split uses 48 training repositories and five validation repositories, producing 7,172 training rows and 1,189 validation rows. The generated dataset passed its own datasets.load_dataset(...) self-test with both split counts intact.

This boundary matters. A row-level random split could place near-identical conventions, modules, or neighboring file regions from the same project on both sides. Repository-level isolation gives me a more credible basis for held-out evaluation.

Sequence-length distribution

Most rows use the upper half of the context budget

8,361 total rows

0 to 255 tokens1,891
256 to 511 tokens1,495
512 to 1,023 tokens4,975
Lexical-token buckets from datasets/raw-max/statistics.json. Training caps model input at 1,024 tokens.

Safety and quality filters

I reject configured sensitive paths such as environment files, private keys, credentials, and identity keys. The builder also applies high-confidence credential signatures, file-size limits, Git ignore rules, generated-directory rules, and supported-extension checks. In this build, it rejected 31 sensitive-path hits and one OpenAI-key signature. I treat this as a precision-oriented safety layer, not as publication clearance.

Model and adapter

I fixed the model target before evaluation rather than selecting it retrospectively from a leaderboard. Training uses unsloth/gemma-4-E4B-it-UD-MLX-4bit at revision f29de68cb284ca208446e647b339569935025ef3, with upstream lineage google/gemma-4-E4B-it. I deploy against the matching Ollama base, gemma4:e4b.

I attach LoRA adapters only to language-model linear layers while keeping the vision and audio stacks frozen. The profile uses rank 8, alpha 16, dropout 0, completion-only loss, gradient checkpointing, a 1,024-token sequence cap, and a learning rate of 1e-5. The extended training profile uses batch size 1, four-step gradient accumulation, and one complete data pass.

PropertyValue
FamilyGemma 4 E4B instruction tuned
Training checkpointunsloth/gemma-4-E4B-it-UD-MLX-4bit
Quantization4-bit MLX
TrainerMLX-VLM
LoRArank 8, alpha 16, dropout 0
Trainable parameters19.441488 M
All model parameters7,665.78256 M
Trainable share0.254%
Table 4. Model contract. Values are pinned in config.yaml and recorded again in each run manifest.

Compatibility and memory gate

Before allocating a longer training window, I ran a deliberate one-step gate across the complete stack. It completed in 16.308 seconds with loss 1.3896, 96.345 training tokens per second, 169 trained tokens, and 8.906 GB peak unified memory. This establishes data-format compatibility, gradient flow, adapter persistence, and memory headroom on the target machine.

MetricMeasured value
Iterations1
Training loss1.38960338
Training throughput96.345 tokens/s
Trained tokens169
Peak unified memory8.906 GB
Wall time16.308 s
Return code0
Table 5. Compatibility-gate telemetry. Canonical sources: the completed smoke run.json and train.log.

Verified deployment

MLX adapters cannot be attached directly to Ollama's Gemma 4 base, so I built an explicit conversion boundary. My deployment stage validates the MLX package, transposes and renames paired LoRA factors into PEFT Safetensors, converts the PEFT adapter to GGUF with a pinned llama.cpp revision, and creates an Ollama model with a 32,768-token context window.

The resulting GGUF adapter contains 343 tensor pairs and is 38,926,080 bytes. I pin llama.cpp revision b15ca938ad00aa6b3ee6c2edda7363fd02826b18 and record the source adapter, PEFT metadata, output hash, Modelfile, runtime metrics, and Codex result as deployment evidence.

StageArtifactRecorded proof
MLX trainingadapters.safetensorsrank, scale, target keys
PEFT exportadapter_model.safetensorsSHA-256 + 343 pairs
llama.cpp conversionadapter.ggufpinned converter revision
Ollama packagegemma4-e4b-julian:latestModelfile + 32K context
Agent gateCodex task fixturepytest passed
Table 6. Packaging chain. Each stage has a durable local artifact and a recorded revision or hash.

Native tool call

Passed

read_project_status selected

Decode throughput

85.58 tok/s

20 tok/s minimum

First token

0.301 s

5.0 s maximum

Codex task

Passed

edited file, pytest passed

I set the runtime gate at a minimum of 20 decode tokens per second and a maximum of five seconds to first token. The local model measured 85.583 tokens per second and 0.301 seconds to first token. It also selected the named read_project_status tool through Ollama's native tool-call interface.

For the final integration gate, I launched Codex against a deliberately broken Python fixture. The model identified subtraction where addition was required, edited the file, ran the test, and stopped after 1 passed. The recorded agent task took 65.778 seconds.

Personalization

I separate personalization into two independent layers. The LoRA checkpoint validates the adaptation and deployment path; the system layer provides explicit behavioral alignment and durable working context.

My persistent prompt captures my GitHub identity, Vienna location, portfolio, primary stack, and representative projects. It directs the model to communicate precisely, challenge weak assumptions, inspect before editing, make narrow changes, verify its work, avoid speculative abstractions, and keep private context private.

I manage the prompt as a versioned deployment input. The deployment tool reads it from configuration, rejects unsafe Modelfile delimiters, embeds it as a multiline SYSTEM instruction, and recreates the Ollama tag. This makes the behavior contract repeatable across deployments instead of relying on a hand-edited generated file.

Operate as my local personal AI assistant, based on Gemma 4 E4B.

Use my context when it is relevant. Do not force personal details into unrelated responses.

Be direct, honest, precise, and practical. Work with me as a technically capable collaborator.

This shortened rendering captures the intent. The canonical prompt contains the complete behavior, coding, privacy, and formatting contract.

Reproducibility

I keep preparation, training, evaluation, and deployment as separate stages. Every heavy command first writes a machine-readable plan and exact argv. A COMPLETED marker appears only after a zero return code and verified adapter output.

Build the corpus

uv sync --extra train --extra deploy
gh auth status

uv run python fetch_repos.py
uv run python stats.py
uv run python build_dataset.py

Recreate the compatibility adapter

uv run python train.py --smoke --execute

uv run --extra train --extra deploy python deploy_ollama.py --adapter-path runs/gemma-4-e4b-it-4bit-raw-max-smoke/adapters --tag gemma4-e4b-julian:latest --execute

Use it with Codex

codex --oss --local-provider ollama --model gemma4-e4b-julian:latest -c model_context_window=32768 -c model_auto_compact_token_limit=24576

I set context limits in both the Ollama Modelfile and the Codex client. Ollama controls server allocation; Codex controls its prompt and compaction budget.

Evidence map

QuestionCanonical file
What did I contribute?stats/stats.json
What entered the corpus?datasets/raw-max/statistics.json
How was training invoked?runs/.../run.json + train.log
Which model revision was used?config.yaml + plan.json
Did deployment pass?deployment/.../verification.json
What identity is embedded?deployment/system-prompt.txt
Table 7. Canonical evidence map. Generated presentation files never override these machine-readable sources.

Evaluation scope

I keep the claims in this report aligned with the measurements they come from.

  • The published checkpoint is a systems-validation artifact. Its measurements establish compatibility, conversion correctness, and runtime behavior; they are not a held-out coding-quality benchmark.
  • Repository history is correlated. Thousands of commits do not represent thousands of independent programming tasks. Repeated frameworks, scaffolds, and project conventions reduce effective diversity.
  • Filtering is not publication clearance. Secret signatures and sensitive-path rules reduce obvious risk, but privacy, copyright, and repository licenses still require explicit review.
  • Performance is machine-specific. I measured throughput, latency, and peak memory on my local Apple Silicon environment; other hardware will produce different results.
  • The Codex gate is an integration test. It verifies the agent loop under a controlled edit-and-test task rather than claiming broad software-engineering capability.

My next evaluation layer is a controlled held-out comparison between the pinned base and an adapted checkpoint, using repository-isolated data, fixed sampling, perplexity, next-line exact match, throughput, and peak-memory measurements.