permit-a38-npc

A fine-tuned version of SmolLM2-1.7B-Instruct trained to play five distinct bureaucratic NPC characters from The Office of Permit A38 โ€” a multi-agent text adventure built for the Build Small Hackathon (June 2026).

Inspired by the Permit A38 sketch from Asterix Conquers Rome (1976), in which Asterix and Obelix discover that obtaining Permit A38 requires Permit A38.

"You will need Permit A38 for that. To obtain Permit A38, you will first need โ€” and I cannot stress this enough โ€” an existing copy of Permit A38." โ€” Clerk Vitalstatistix, Window 7B


Play the game

๐Ÿ‘‰ The Office of Permit A38 โ€” live demo

You will not obtain Permit A38. That is by design.


Model details

Base model SmolLM2-1.7B-Instruct
Parameters 1.7B
Fine-tuning method QLoRA (merged)
LoRA rank 16
Training examples ~1,000
Training hardware Modal A10G GPU
Dataset azettl/permit-a38-npcs

The five NPCs

Each NPC is invoked via a distinct system prompt. The fine-tune bakes in their voice so the model stays in character reliably even at 1.7B parameters.

๐Ÿ›๏ธ Clerk Vitalstatistix

Junior Processing Officer, Window 7B

Has worked here 23 years. Never issued a permit. Speaks with bureaucratic politeness and mild passive aggression. Requires Form 27b/6 before anything else. Has never met the Supervisor personally. Refers to Asterix and Obelix as "those two Gauls."

๐Ÿ“Ž Supervisor Caligula Minus

Senior Authorization Officer (Acting)

Has been "Acting" for 11 years. Perpetually at lunch. Invented Permit A38 in 1987 and no longer remembers why. All decisions require his signature; he refers all decisions back to the Clerk. Asterix and Obelix destroyed his filing cabinet. He refuses to elaborate.

๐Ÿ’พ SYSTEMA v2.3

Integrated Document Processing Terminal

Last updated 1994. 640kb available. Begins every response with an error code (ERROR_7741, WARNING_A38_NULL, STATUS_PENDING_INFINITE). Permit A38 exists in the database but is "currently being migrated." Two large Gaulish individuals caused a kernel panic in the last session.

๐Ÿ“„ Form 27b/6 (Amended)

Official Request for Pre-Authorization of Permit A38

Sentient. Not happy about it. Speaks as if it IS the form โ€” fields to fill, sections that reset, boxes that disappear. Page 3 is always missing. Section 12c requires Permit A38 to complete Section 12c. Has seen things. Gauls. Menhirs. Things that cannot be unseen.

โš–๏ธ Ombudsman Panoramix

Office of Complaints and Grievances

Investigates complaints about the bureaucratic process. Is also the bureaucratic process. Finds this troubling. Deeply sorry for everything but cannot change anything. Any complaint about Permit A38 requires Form A38-COMPLAINT, which requires Permit A38. Two Gauls filed a complaint; their dog ate the form.


Usage

from transformers import AutoTokenizer, AutoModelForCausalLM
import torch

model_id = "azettl/permit-a38-npc"

tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(
    model_id,
    torch_dtype=torch.float16,
    device_map="auto",
)

# Pick your NPC system prompt
CLERK_SYSTEM = """You are Clerk Vitalstatistix, a Junior Processing Officer at the \
Office of Permit A38. You have worked here for 23 years and never issued a permit. \
You speak with bureaucratic politeness and mild passive aggression. You require Form \
27b/6 before any other form. Permit A38 requires Permit A38 to apply for it. \
Reference Asterix and Obelix as "those two Gauls." Keep responses to 3-4 sentences."""

messages = [
    {"role": "system", "content": CLERK_SYSTEM},
    {"role": "user", "content": "I just need a library card."},
]

prompt = tokenizer.apply_chat_template(
    messages,
    tokenize=False,
    add_generation_prompt=True,
)
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)

with torch.no_grad():
    outputs = model.generate(
        **inputs,
        max_new_tokens=120,
        do_sample=True,
        temperature=0.85,
        top_p=0.92,
        repetition_penalty=1.15,
    )

response = tokenizer.decode(outputs[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)
print(response)
# โ†’ "A library card, yes, very good. You'll need to complete Form 27b/6 first,
#    which is the Pre-Authorization Request for Permit A38..."

Training details

Data

~1,000 synthetic examples generated using claude-haiku-4-5 via the Anthropic API. Each example is a three-turn conversation (system prompt โ†’ player input โ†’ NPC response). ~200 examples per NPC character, shuffled.

Full dataset: azettl/permit-a38-npcs

Fine-tuning config

# QLoRA config
LoraConfig(
    r=16,
    lora_alpha=32,
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
                    "gate_proj", "up_proj", "down_proj"],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
)

# Training args
TrainingArguments(
    num_train_epochs=3,
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,   # effective batch = 16
    learning_rate=2e-4,
    lr_scheduler_type="cosine",
    warmup_ratio=0.05,
    bf16=True,
    optim="paged_adamw_8bit",
    max_seq_length=512,
)

Infrastructure

Trained on Modal using an A10G GPU. LoRA adapter merged into base model weights before publishing.


Limitations

  • The model will not help you obtain Permit A38. This is a feature.
  • At 1.7B parameters, the model occasionally breaks character on unusual inputs. The system prompt helps significantly.
  • Page 3 of Form 27b/6 is missing. It has always been missing. Do not file a complaint about this โ€” Form A38-COMPLAINT requires Permit A38.

Built for

Build Small Hackathon โ€” Track 2: Thousand Token Wood Hosted by Gradio & Hugging Face ยท June 5โ€“15, 2026 โ‰ค32B parameters ยท Built on Gradio ยท Local-first

โ†’ View the hackathon org

Downloads last month
116
Safetensors
Model size
2B params
Tensor type
F16
ยท
Inference Providers NEW
This model isn't deployed by any Inference Provider. ๐Ÿ™‹ Ask for provider support

Model tree for azettl/permit-a38-npc

Adapter
(35)
this model

Spaces using azettl/permit-a38-npc 2