Project Story — Enzo: On-Device Cybersecurity Agent

Inspiration

When a friend sent us the Africa Deep Tech Challenge 2026, we saw a chance to solve a problem we'd lived firsthand. Across Africa, cybersecurity analysis is mostly prohibitively expensive and requires sharing sensitive internal information to external services which may not be all safe. University students in Lagos, small-business owners in Dakar, and DeFi teams in Nairobi all need to audit their internal systems, but they can't send sensitive network data to external APIs.

We decided to build a small, specialised cybersecurity model that can run entirely offline.

What it does

Enzo is a 2B-parameter cybersecurity language model that can run 100% offline via llama.cpp. It can:

  • Audit internal networks: analyze infrastructure for vulnerabilities, map CVEs/CWEs to findings, and propose concrete fixes
  • Generate secure code: write Python/JavaScript with security-aware patterns (input validation, path traversal prevention, SQL parameterization)
  • Act as an autonomous agent: propose exact tool commands (nmap, nikto, wpscan, sqlmap, nuclei), reason about results, and ask permission before installing tools
  • Teach cybersecurity: explain vulnerabilities, attack vectors, and defenses in multiple languages
  • Solve reasoning tasks: handle math, logic, and scientific reasoning as secondary capabilities

Served via an OpenAI-compatible API, making it easy to integrate into existing security workflows.

How we built it

Three iterations, each teaching us what not to do.

V1 — Qwen3-4B-Instruct-2507: We started with a 4B model and a 513-line dataset pipeline pulling from FORGE-Curated, ScaBench, GoPlus, and CGT datasets. We built 7 Colab notebook variants trying to get training to complete. The fine-tuning never finished.

V2 — Phi-3-mini-4k-instruct: Pivoted to Phi-3 for stronger code performance. Built a 490-entry identity dataset and 934-line strategy document. After training, we tested the model and it was hallucinating, this taught us how to ensure it doesn't happen again.

V3 — unsloth/Qwen3.5-2B: At this point we have learnt from our previous challenges and wrote better scripts and cleaner datasets.

# Training config (Unsloth + TRL SFTTrainer)
model = FastLanguageModel.get_peft_model(
    model,
    r=16, lora_alpha=16, lora_dropout=0,
    target_modules=["q_proj","k_proj","v_proj","o_proj",
                     "gate_proj","up_proj","down_proj"],
    use_gradient_checkpointing="unsloth",
    random_state=3407,
)
trainer = SFTTrainer(
    model=model, tokenizer=tokenizer,
    train_dataset=dataset["train"],
    eval_dataset=dataset["validation"],
    args=TrainingArguments(
        per_device_train_batch_size=2,
        gradient_accumulation_steps=4,
        num_train_epochs=1,
        learning_rate=2e-4,
        fp16=not torch.cuda.is_bf16_supported(),
        bf16=torch.cuda.is_bf16_supported(),
        logging_steps=1,
        optim="adamw_8bit",
        save_strategy="steps", save_steps=100,
        seed=3407,
    ),
)

The dataset was built from scratch — 61,839 curated rows:

Category Rows %
Cybersecurity 26,203 42.4%
Reasoning 14,654 23.7%
Tool-calling 11,068 17.9%
Chat 9,914 16.0%

Challenges we ran into

  1. Compute: Our first attempts on Colab free tier timed out mid-training. We switched to Colab A100 GPUs, which gave us enough VRAM and sustained compute to finish 3,600 steps.

  2. Model size vs quality: V1 (4B) and V2 (3.8B) were too large — after loading the model and KV cache on an 8GB laptop, less than 2GB remained for the OS and other apps.

  3. Dataset quality: Our early datasets had malformed JSON, role mismatches, and duplicate conversations. We built three rounds of scrubbing; parse_convs with raw JSON walking, messages→{role,content} normalization, and schema validation; before the data was clean enough for training.

Accomplishments that we're proud of

  • From zero to working model: After two failed iterations, V3 produced a model that converges, serves correctly, and responds with real cybersecurity analysis
  • 61K curated dataset from scratch: No existing dataset fit our needs. We built a synthetic generation pipeline

What we learned

  • Dataset cleaning is 80% of the work. Studying how bigger models structure their training data taught us that format consistency matters more than volume. Our three-round scrubbing pipeline was the breakthrough.
  • Unsloth is critical for small-team fine-tuning. gradient_checkpointing="unsloth" and native GGUF export made the difference between "training never finishes" and "training converges in 3,600 steps."
  • Smaller models fine-tune better on limited compute. The 2B model converged faster and more reliably than our 4B attempts.

What's next for Enzo Models

  • Mixture of Experts with Inhibitory Control: we're exploring a MoE architecture where a tiny "controller" network acts like biological inhibitory control in the brain. Instead of a traditional router that fires all experts, the controller learns which experts to activate based on the current context, like how the brain doesn't fire all neurons at once. This keeps inference efficient on 8GB hardware while allowing specialization across security domains (network audit, code review, threat intelligence)

  • Tool integration: wiring Enzo to actually execute nmap, nikto, and nuclei commands via a local agent loop

  • Accuracy improvements: expanding the training dataset with more CVE analysis and pentest methodology examples (the 50% accuracy component is where the biggest score gains remain)

Built With

Share this project:

Updates

Submission history