Skip to main contentSkip to footer
Julian SchmidtJulian Schmidt

Applied AI systems · Reproducible ML engineering

From Repository History to a Verified Local Coding Agent

Julian Schmidt · July 16, 2026
Repository history flowing through an audited corpus, Gemma 4 LoRA adapter, GGUF conversion, and a verified Ollama and Codex runtime

Executive summary

I set out to answer a focused engineering question: how far can I take my own repository history toward a local coding agent 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 current corpus audit covers 60 repositories, matches 3,018 of my commits, and retains 18,361 deduplicated rows from 58 repositories. I published that audited dataset on Hugging Face and deployed the local compatibility adapter as gemma4-e4b-julian:latest.

StageStateEvidenceMeaning
Repository provenanceComplete60 processedMy contribution history measured
Dataset buildComplete18,361 rowsPublished 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, dataset revision, 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 in my contribution history. This is a provenance view, not the retained-dataset row mix; documentation and configuration formats become more prominent after corpus construction.
Figure 3. Language distribution by changed lines. Added plus removed lines for every retained language in my contribution history. This is a provenance view, not the retained-dataset row mix; documentation and configuration formats become more prominent after corpus construction.

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 8,970 considered files, the builder selected 8,924 source candidates spanning 7,913 files and produced 20,858 candidate chunks. Exact SHA-256 deduplication removed 1,470 rows; MinHash near-deduplication removed another 1,027. The retained corpus contains 18,361 rows, 12,472,126 lexical tokens, 1,305,187 emitted lines, and 50.52 MiB of UTF-8 source text.

MeasureValueControl
Repositories with rows5852 train, 6 valid
Source files7,9138,924 selected candidates
Candidate chunks20,858896-token target
Exact duplicates removed1,470SHA-256
Near duplicates removed1,027MinHash at 0.85
Rows retained18,36115,226 train, 3,135 valid
Lexical tokens12,472,126all retained rows
Emitted source1,305,187 lines50.52 MiB UTF-8
Table 3. Dataset ledger. Canonical source: datasets/raw-max/statistics.json.

Published corpus composition

Documentation and TypeScript account for most retained rows

18,361 total rows

Markdown6,55935.7%
TypeScript6,17033.6%
JSON2,82815.4%
Python1,5328.3%
Other formats1,2726.9%
Row counts from the published Hugging Face revision and its matching datasets/raw-max/statistics.json. “Other formats” combines the remaining 31 language and repository-file labels.

Split integrity

I assign train and validation data at repository granularity, so no repository contributes to both sides. The current split uses 52 training repositories and six validation repositories, producing 15,226 training rows and 3,135 validation rows. The generated dataset passed its own datasets.load_dataset(...) self-test, and I reloaded the published Parquet splits at Hugging Face revision 9f9829f9dbd75400595ea2e211e291fbcc47ba0f with both 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

18,361 total rows

0 to 255 tokens2,847
256 to 511 tokens2,515
512 to 1,023 tokens12,999
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.

Reference environment

I captured the current workstation profile alongside the report so the hardware and software assumptions are explicit. Run-specific telemetry still comes from the immutable training and deployment records; this snapshot documents the reference system used to operate and verify the project without publishing device identifiers.

LayerMeasured version or capacity
HardwareApple M4 Pro · 14 cores · 24 GB unified memory
Operating systemmacOS 26.5.1 (25F80)
Python3.14.2
MLX / MLX-VLM0.32.0 / 0.6.4
Ollama0.32.0
Codex CLI0.144.5
Table 6. Reference workstation snapshot. Captured 2026-07-16; canonical source: report/environment.json.

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 7. 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

These commands rebuild the pipeline from repositories visible to the authenticated GitHub account. They do not recreate my exact private source snapshot for another user; the immutable Hugging Face revision below is the reproducible public data boundary.

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

Load the published revision

uv run python - <<'PY'
from datasets import load_dataset

dataset = load_dataset(
  "JulianAT/personal-codex-model",
  revision="9f9829f9dbd75400595ea2e211e291fbcc47ba0f",
)
print({name: len(split) for name, split in dataset.items()})
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
What was published?HF dataset revision 9f9829f9…
How was training invoked?runs/.../run.json + train.log
Which model revision was used?config.yaml + plan.json
Did deployment pass?deployment/.../verification.json
Which environment was recorded?report/environment.json
What identity is embedded?deployment/system-prompt.txt
Table 8. 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. I explicitly approved the staged public dataset, but privacy, copyright, and repository licenses still require ongoing 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.