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.
| Stage | State | Evidence | Meaning |
|---|---|---|---|
| Repository provenance | Complete | 60 processed | My contribution history measured |
| Dataset build | Complete | 18,361 rows | Published corpus reproducible |
| Training compatibility | Passed | return code 0 | MLX LoRA path verified |
| Adapter packaging | Passed | 343 tensor pairs | PEFT and GGUF exports verified |
| Local deployment | Passed | Ollama + Codex | Agent workflow operational |
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.


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.
| Language | Commits | Added | Removed | Changed | Net |
|---|---|---|---|---|---|
| TypeScript | 1,079 | 694,800 | 264,736 | 959,536 | 430,064 |
| Python | 313 | 110,038 | 19,476 | 129,514 | 90,562 |
| YAML | 73 | 23,322 | 9,635 | 32,957 | 13,687 |
| JavaScript | 138 | 21,455 | 2,488 | 23,943 | 18,967 |
| CSS | 123 | 11,640 | 4,731 | 16,371 | 6,909 |
| SQL | 48 | 3,453 | 809 | 4,262 | 2,644 |

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.
| Measure | Value | Control |
|---|---|---|
| Repositories with rows | 58 | 52 train, 6 valid |
| Source files | 7,913 | 8,924 selected candidates |
| Candidate chunks | 20,858 | 896-token target |
| Exact duplicates removed | 1,470 | SHA-256 |
| Near duplicates removed | 1,027 | MinHash at 0.85 |
| Rows retained | 18,361 | 15,226 train, 3,135 valid |
| Lexical tokens | 12,472,126 | all retained rows |
| Emitted source | 1,305,187 lines | 50.52 MiB UTF-8 |
Published corpus composition
Documentation and TypeScript account for most retained rows
18,361 total rows
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
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.
| Property | Value |
|---|---|
| Family | Gemma 4 E4B instruction tuned |
| Training checkpoint | unsloth/gemma-4-E4B-it-UD-MLX-4bit |
| Quantization | 4-bit MLX |
| Trainer | MLX-VLM |
| LoRA | rank 8, alpha 16, dropout 0 |
| Trainable parameters | 19.441488 M |
| All model parameters | 7,665.78256 M |
| Trainable share | 0.254% |
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.
| Metric | Measured value |
|---|---|
| Iterations | 1 |
| Training loss | 1.38960338 |
| Training throughput | 96.345 tokens/s |
| Trained tokens | 169 |
| Peak unified memory | 8.906 GB |
| Wall time | 16.308 s |
| Return code | 0 |
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.
| Layer | Measured version or capacity |
|---|---|
| Hardware | Apple M4 Pro · 14 cores · 24 GB unified memory |
| Operating system | macOS 26.5.1 (25F80) |
| Python | 3.14.2 |
| MLX / MLX-VLM | 0.32.0 / 0.6.4 |
| Ollama | 0.32.0 |
| Codex CLI | 0.144.5 |
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.
| Stage | Artifact | Recorded proof |
|---|---|---|
| MLX training | adapters.safetensors | rank, scale, target keys |
| PEFT export | adapter_model.safetensors | SHA-256 + 343 pairs |
| llama.cpp conversion | adapter.gguf | pinned converter revision |
| Ollama package | gemma4-e4b-julian:latest | Modelfile + 32K context |
| Agent gate | Codex task fixture | pytest passed |
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()})
PYRecreate 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
| Question | Canonical 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 |
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.

