pretext
Turn a one-page persona into a runnable social-engineering exercise.
Narrative, opening message, escalation path, and the red flags defenders should have caught, generated, validated, then rendered where you need it.
Authorized training only. Everything produced is a fictional exercise. Domains, names, and systems are invented unless you say otherwise. pretext never sends anything to a target; it writes the material and the analysis. Run an exercise against your own people only after your security team agrees to it, and check the lures before sending.
Why
Security-awareness decks are generic ("you receive an email with a typo"), so staff learn to spot a template rather than a technique. A useful exercise is grounded in one role at one company: what that person controls, who they trust, and what an operator would do next.
Doing that by hand costs an afternoon per persona. pretext takes a YAML profile
and produces the exercise in one call, defender's side included, so training value
does not depend on whoever wrote the scenario remembering to add it.
What you get
| Section | Content |
|---|---|
| Pretext narrative | The operation, attacker-side: harvested detail, forged artifact, why contact lands today |
| Opening message | Usable verbatim, in the sender's voice, carrying the sending address and one lure |
| Escalation path | 3-6 stages across channels, each handing over a new prop or introducing a new person |
| Red flags | Observable tells tied to this exercise, each with a defender action, including one only clear in hindsight |
docs/running-an-exercise.md covers authorization, delivery, scoring and the
debrief, which is the half the tool cannot do.
Every output carries a disclaimer. sample-output.md is a
complete Markdown export; docs/demo.png is the terminal view.
Install
uvx --from pretext-sim pretext generate persona.yaml # no install
pipx install pretext-sim # or a permanent command
Python 3.11+. The distribution is pretext-sim; the command is pretext, so uvx
needs --from. For the source tree: uv sync --extra dev then uv run pretext.
Quickstart
pretext example -o persona.yaml # fictional profile to edit
pretext validate persona.yaml # schema check, no API call
pretext generate persona.yaml --dry-run # resolved config + exact prompt, no call
pretext generate persona.yaml -f markdown -o exercise.md
Point it at any OpenAI-compatible chat completions endpoint with a flag, env var, or
.env:
export PRETEXT_API_KEY=sk-...
export PRETEXT_BASE_URL=http://localhost:11434/v1 # local proxy, if you prefer
export PRETEXT_MODEL=gpt-oss:20b
Or name a known host instead of the URL: --provider openai|openrouter|venice|ollama|lmstudio
(PRETEXT_PROVIDER), which fills the base URL. An explicit --base-url wins. Local
providers need no key.
pretext config shows what resolves, with the key masked.
Profile
Only four fields are required (name, role, company, communication_style).
The rest sharpen the result.
name: Dana Iyer
role: Senior Payroll Analyst
company: Northwind Logistics
communication_style: Brisk and formal, mixes payroll shorthand with chatty asides
department: People Operations
tenure: 3 months
reports_to: Marta Kwan
systems: [Workday, the payroll mailbox]
notes: New enough to the team to be unsure of process.
Optional: department, tenure, reports_to, systems, notes. Unknown keys are
rejected so typos surface early. Fixture personas live in
tests/fixtures/profiles.
Command reference
| Command | Purpose |
|---|---|
generate PROFILE |
Produce one exercise |
validate PROFILE |
Validate without calling the API |
example |
Print the bundled fictional profile |
batch DIR |
One exercise per *.yaml in DIR (files starting _ are skipped) |
doctor |
Probe the endpoint: config, TLS, reachability, model, JSON mode |
schema |
Print the JSON Schema model output must satisfy (--all adds the profile) |
config |
Show resolved settings, key masked |
Flags on generate (endpoint, --env-file, --sections and --format also apply
to batch):
| Flag | Effect |
|---|---|
--channel |
Force the opening channel (email, phone, sms, chat, in_person, linkedin) |
--extra |
Extra direction for the model |
--format |
rich (default), markdown, json |
--out |
Write to a file, creating parents; plain text unless a format is chosen |
--sections |
Render only some of pretext,opening,escalation,red_flags |
--from-file |
Replay a saved response; no API call, no key |
--dry-run |
Print resolved config and prompt, send nothing; --out writes the prompt |
--usage |
Token counts on stderr |
--base-url, --model, --api-key |
Override endpoint settings |
--temperature, --max-tokens, --timeout, --retries, --seed |
Sampling and transport |
--env-file |
Read a different dotenv file (default ./.env) |
--no-color |
Plain terminal output |
Exit codes: 0 success, 1 bad profile/config/API/schema failure, 2 usage error,
130 interrupted.
--from-file accepts a --format json export, a bare simulation object, or a raw
chat completion body, so formatting work stays free and offline.
Diagnose first
pretext doctor # config, TLS, /models, JSON mode
pretext doctor --full # also runs one real simulation through the schema
Exits 1 on any failure and prints the provider's own error, so a wrong model name
or a blocked key shows up before a batch run wastes retries.
Batch runs
For a whole team, point batch at a directory and it writes one file per profile,
continuing past failures:
pretext batch profiles/ --out-dir packet/ --format markdown
Profiles run four at a time by default (-j/--concurrency, max 8); progress and a
wrote 7/9 summary go to stderr, the failure reasons are listed, and
the exit code is 1 if anything failed. Endpoint flags are the same as generate;
--sections, --format and --usage apply to the run as a whole.
Configuration
First hit wins:
- Flags (
--base-url,--model,--api-key,--provider) - Environment: the same names under
PRETEXT_(PRETEXT_API_KEY,PRETEXT_MODEL, ...) .envin the working directory, or the file given to--env-fileOPENAI_API_KEYfor the key, thenhttps://api.openai.com/v1andgpt-4o-mini
The key is never printed and never written to output.
How it works
profile.yaml ─> Profile ─> prompt ─> POST /chat/completions ─> extract JSON
│
rich | markdown | json <─ render <─ validate <────────────────┘
Model output is untrusted. It is parsed out of fences or prose, validated against a
strict pydantic schema, and only then rendered. Escalation stages must run 1..n with
no gaps, red_flags needs at least two entries, and unknown keys are rejected, so a
loose model fails loudly instead of producing a half-filled exercise.
Diagnostics go to stderr, keeping stdout pipeable: pretext generate p.yaml -f json | jq.
Releasing
Tagging vX.Y.Z runs release.yml: it checks the tag against __version__, gates
on lint, format, types and tests, builds, then publishes a release whose notes come
from CHANGELOG.md. Version bumps and changelog sections land before the tag.
Development
uv run pytest --cov=pretext # offline: MockTransport and a stubbed client
uv run ruff check src tests
uv run mypy
The suite needs no key. conftest.py chdirs each test into a temp directory so your
own .env cannot leak in, and pins the terminal width so Rich reflow stays stable.
scripts/capture_corpus.py refreshes tests/fixtures/corpus, which
tests/test_corpus.py checks for structural promises.
AGENTS.md documents the layout, the hardening rules, and the traps for agent
contributors. CONTRIBUTING.md covers process; security reports follow SECURITY.md.
Status
Pre-1.0: the CLI surface and JSON schema can still move between minor versions;
CHANGELOG.md records what changed.
Output has been checked against gpt-4o-mini, gemini-2.5-flash, claude-haiku-4.5 and llama-3.3-70b through OpenRouter, plus venice-uncensored-1-2 and qwen3-235b-a22b-instruct on Venice. All six held the JSON contract and used distinct channels. Weak models will still need prompt tuning, and the suite covers the pipeline rather than any one model's prose.
Not planned: delivery to targets, click tracking, an SMTP client. pretext writes the exercise; sending stays your job.
License
MIT. See LICENSE.
Metadata
Release files for pretext-sim 0.3.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pretext_sim-0.3.3.tar.gz | 570.0 kB | With provenance | Details for pretext_sim-0.3.3.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pretext_sim-0.3.3-py3-none-any.whl | Python 3 | none | any | Details for pretext_sim-0.3.3-py3-none-any.whl |
Total release size: 594.5 kB
Release files / pretext_sim-0.3.3.tar.gz
| Download URL | pretext_sim-0.3.3.tar.gz |
|---|---|
| Size | 570.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7c40114d586e8d759505310a7a42d9fcb77aa14f3fed0c30aa63af69264e481d
|
|
BLAKE2b-256 checksum How to use checksums |
0fed1afdc19e7867fdb034d4bd790e7a25094749c977911e4a02f9dc53e93795
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logRelease files / pretext_sim-0.3.3-py3-none-any.whl
| Download URL | pretext_sim-0.3.3-py3-none-any.whl |
|---|---|
| Size | 24.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
938658279ce1bd82805fcb9bc9a8776105ae80035cf69c4a4c8f672647877d80
|
|
BLAKE2b-256 checksum How to use checksums |
15db9e96631760176c14d2d292efc94753ac2779fbd0ecc20a446a91cdb15e57
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logSecurity for pretext-sim 0.3.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for pretext_sim-0.3.3.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.