Turns a vague request into an explicit work order, so local coding models execute instead of guessing.
you: "build out the gtm adapters package"
promptx: 1. Create src/gtm_adapters/gtm_adapter.py — abstract base class
GTMAdapter with method process_event(event: dict) -> dict
2. Create src/gtm_adapters/strategy_council.py — class
StrategyCouncil, method evaluate(genome) -> float
3. Create src/gtm_adapters/strategy_genome.py — dataclass
StrategyGenome with fields: id, traits, fitness
4. ONLY THEN edit src/gtm_adapters/__init__.py to import them
5. Verify: python -c "import src.gtm_adapters"
Do NOT rewrite __init__.py to fix an ImportError. The error names
__init__.py but the cause is the missing sibling modules above.
You paste the second thing into your coding agent instead of the first.
A local 80B model was asked to "build out the gtm adapters package." It wrote
src/gtm_adapters/__init__.py, whose imports referenced three sibling modules
that did not exist. The import failed. The model saw an error naming
__init__.py, so it rewrote __init__.py. Same failure. It did this ten
times before it got killed.
The model wasn't broken. It was fixing the file the error named instead of creating the files that were missing — a diagnostic step smaller models frequently get wrong.
The lesson generalizes: local models follow explicit instructions well and infer badly. Ambiguity is what makes them loop.
So split the job. A cheap fast cloud model (~$0.10 per million tokens) converts your intent into a precise spec. Your local model — excellent at execution, weak at inference — runs it. Neither is good at both, and the pairing costs a fraction of a cent per request.
The free, nothing-leaves-your-network path now has two lanes, side by side in the hosted picker — choose per request:
- Spark (local · vLLM) — the quicker lane. Same hardware, faster
inference. One model is live at a time, so point
PROMPTX_SPARK_VLLM_MODELat whatever's loaded and go. - Spark (local · Ollama) — the always-loaded lane. Works even when vLLM is serving something else or is down.
- Either slot takes any OpenAI-compatible endpoint — Ollama, vLLM, LM Studio — via env vars. Your hardware, your call.
The tool that started with a 400-file ceiling and one cheap model grew teeth:
- Huge projects are in scope now.
--scanindexes 2,000 files by default — five times the old cap — and renders ~60K tokens of structure. Both limits are env overrides (PROMPTX_MAX_FILES,PROMPTX_MAX_RENDER_CHARS) for when your monorepo laughs at 2,000. Even without scanning, the expander now sees 600 paths instead of 120. - Two heavyweights joined the cheap fleet.
qwen/qwen3.8-max— flagship Qwen with a 1M context window, for maps so big the flash tier loses the plot. Anddeepseek-v4-flash, billed direct by DeepSeek: 284B MoE, 1M context, $0.14/M, with its own key and a thinking-sized token budget. - You can see what's running. Every surface carries the version:
promptx --version, both web UI footers, the server's startup log, andGET /api/version. No more wondering which copy the NAS is actually serving. - Docker deployment where the image IS the version. Tagged per release,
hardened by default: read-only rootfs, read-only project mounts,
no-new-privileges, keys never touching the filesystem. Update or roll back
with one
docker compose up -d.
git clone https://github.com/NathanMaine/promptx.git
cd promptx
./install.shThat puts promptx and promptx-web in ~/bin and makes sure ~/bin is on
your PATH.
API key. promptx reads OPENROUTER_API_KEY from the environment, and falls
back to OpenCode's auth store at ~/.local/share/opencode/auth.json if you
already use OpenRouter there. Copy .env.example to .env if you'd rather set
it explicitly. Get a key at openrouter.ai.
No dependencies — Python 3.9+ standard library only, on purpose. It should run on a NAS with no pip.
promptx -c . --copy "add retry logic to the api client" # expand, copy to clipboard
promptx -c . -x "add retry logic to the api client" # expand, then run via opencode
promptx -c . "add retry logic to the api client" # just print it
promptx --models # list suggested models
promptx --version # which promptx is this?
promptx -m anthropic/claude-haiku-4.5 "..." # pick a different model
promptx --local "..." # use your own GPU, free
promptx -c . --snap "add retry logic" # spec + record baseline
promptx -c . --check # verify what the agent didThe -c . matters more than anything else here. It sends the real file
tree along with your request, so the expander names paths that actually exist
instead of inventing plausible ones. Without it you get confident fiction.
promptx-web # opens http://localhost:7331A textarea, a project-folder field it remembers between sessions, a model picker with descriptions of what each one is good at, and the result with a Copy button and a Try again button. It keeps your recent expansions so you can compare two phrasings side by side.
Run server.py on a NAS or any always-on box and it's a bookmark from your
phone, your laptop, anywhere on the LAN. The hosted picker also carries two
free local engines — vLLM (quicker) and Ollama (always loaded) — so anyone on
your network can expand against your own GPU at zero cost. See
docs/deployment.md — it covers the recommended Docker
deployment (the image carries the version; updates and rollbacks are one
docker compose up -d), plus a systemd unit, because nohup does not
survive a reboot and you will forget that it was running under nohup.
By default promptx sees file names only. That is enough to build things, but a good model will correctly refuse anything that depends on what the code actually says:
"I cannot write this work order. 'Update all documentation' requires me to read the actual content of existing docs and code to know what is outdated. I cannot see file contents — only names."
That refusal is right, and --scan fixes it:
promptx -c . --scan # build the map (incremental — only changed files)
promptx -c . "update all documentation for this project"Now the same request produces a file-by-file work order that names real functions and real gaps.
It sends structure, not source. Per file: imports, class and function signatures, and docstrings. Per document: the heading outline. That is ~50 tokens per file instead of ~4,000, so a whole project fits in a few thousand tokens. Full contents would blow past any context window and cost real money on every call.
Since 0.2.0 the map holds 2,000 files by default and renders up to ~60K
tokens — big enough for most real codebases out of the box. Bigger still is
one export away — PROMPTX_MAX_FILES=5000 PROMPTX_MAX_RENDER_CHARS=500000 promptx -c . --scan — paired with a 1M-context expander like qwen3.8-max,
which exists precisely for the result.
The map is cached as JSON under ~/.promptx/index/. Re-running --scan after
editing two files re-reads two files — everything else is matched by size and
mtime and reused.
promptx --folders # what's indexed, how many files, how long ago
promptx -c . --scan --push # also upload the map to the hosted instanceThe hosted copy on a NAS cannot see /Users/you/myproject. --push scans
locally — where the files are — and uploads only the map. The hosted UI then
lists that folder and can expand against it from any device, including ones that
have no access to those files at all.
What travels: signatures, docstrings, headings, and config key names. Not file bodies, and not config values.
An agent once told me "All 6 tests pass" after running one file with 2 tests in it. Another claimed 19/21 for a 23-test suite and listed a file it never edited. The fix is not a sterner prompt — a claim cannot be verified by asking the claimant more firmly.
--snap records the state of your project (content hashes, plus the current
test result) together with the work order. --check looks again afterwards:
SPEC NAMED 2 PATH(S)
[changed] src/calc.py
[UNTOUCHED] README.md <- named in the spec, never changed
UNSPECIFIED (1) - changed but never named in the spec
! tests/test_calc.py
TESTS (run here, not reported by the agent)
before: 1 failed, 1 passed
after: 3 passed
FAIL: 1 specified path(s) never changed; 1 unspecified change(s)
The tests are run by promptx, on your machine — the agent's pasted output is
never consulted. Exit code 1 on any mismatch, so it composes:
promptx -c . --check && git commit -am "done".
Use it for BUILD tasks. Creating files, adding features, refactoring — anywhere the agent needs a precise target. This is where the loops happen and where the payoff is.
For INVESTIGATE tasks — audits, code review, "what does this module actually
do" — run --scan first. Without an index promptx sees only file names and
will correctly refuse; there is nothing there to reason from. With one it
becomes a good planner. Asked to audit this project for security problems it
named safe_root() (path traversal), store_index() (what it accepts and
writes), and scan() (symlink handling) — the right functions out of the
codebase, with what to check in each.
What it still will not do is tell you whether safe_root() actually has a bug.
That needs the function body, which the map does not carry, so it instructs the
agent to read that specific file rather than inventing a finding. Investigation
gets a focused plan, not an answer — the right division of labour, since your
coding agent can open the files and promptx cannot.
Skip it when you're already specific. "Fix the typo on line 42 of
utils.py" does not need expanding, and promptx will pad it if you insist.
Default is google/gemini-2.5-flash-lite — about $0.10 per million input
tokens, fast, and it never leaks reasoning traces into the output.
| Model | Cost | What it's good for |
|---|---|---|
google/gemini-2.5-flash-lite |
$0.10/M | The default. Balanced and clean. |
qwen/qwen3.7-flash |
$0.03/M | Cheapest paid option, 1M context — good for very large project trees. |
qwen/qwen3.8-max |
$2/M in · $6/M out | Flagship Qwen, 1M context — for huge scanned maps where the cheap tier drops structure. |
deepseek-v4-flash |
$0.14/M in · $0.28/M out | DeepSeek direct (hosted picker): 1M-context reasoning model with its own key and a bigger token budget. |
meta-llama/llama-3.1-8b-instruct |
$0.05/M | Rigid and literal. Use when you already know exactly what you want. |
inclusionai/ling-3.0-flash:free |
free | Rate limited, but fine for occasional use. |
anthropic/claude-haiku-4.5 |
~$1/M | Catches the things you did not say. Worth the money on a gnarly refactor. |
--local |
free | Your own GPU — private and free. The hosted picker splits this into two engines: vLLM (quicker) and Ollama (always loaded). Verbose, <think> traces get stripped. |
Avoid reasoning models. This is one-shot rewriting, not a problem to solve.
Reasoning models burn tokens thinking about it and leak traces into the output.
Cheap and literal wins. (One sanctioned exception — deepseek-v4-flash, cheap
enough and carrying a 1M window — earns a bigger budget instead. The reasoning
behind that exception lives in the longer comparison.)
Longer comparison in docs/models.md.
promptx commits confidently to one interpretation of an ambiguous request. Asked to "review the platform end to end and make sure there is 100% coverage," it picked test coverage and produced a spec for writing a test file. That's a defensible reading. It may not have been the intended one.
That is exactly what the Try again button is for. Reword, re-expand, and only then paste. A bad spec you catch in ten seconds beats a wrong implementation you catch in an hour.
| Path | What it is |
|---|---|
| main.py | The CLI. Installed as promptx. |
| web.py | Local browser UI. Installed as promptx-web. |
| server.py | The hosted version — multi-user, model descriptions, history. |
| Dockerfile + docker-compose.yml | The hosted deployment, containerized — the image tag carries the version. |
| promptx_index.py | The structural indexer behind --scan. |
| promptx_version.py + VERSION | Which release this is — shown by --version, in the UI footers, and at GET /api/version. |
| install.sh | Installer. |
| systemd/promptx.service | Keeps server.py alive across reboots. |
| scripts/add-promptx-tile.sh | Adds a promptx tile to a containerized nginx dashboard. |
| docs/ | Full reference, model notes, deployment, prompt design. |
- docs/documentation.md — complete reference: every flag, every env var, the HTTP API
- docs/models.md — picking an expander model, and why reasoning models are wrong here
- docs/deployment.md — hosting it on a NAS or server: Docker (recommended), systemd, and pointing the local options at your own hardware
- docs/prompt-design.md — why the system prompt says what it says, and what happened when it didn't
- docs/FIELD-NOTES.md — running log of bugs found in real use, what fixed them, and what is still open
MIT — see LICENSE.
Open an issue — the bug template asks for the two things that matter most: the work order verbatim, and whether the folder was indexed.
docs/FIELD-NOTES.md lists every bug found so far with its diagnosis, plus the problems still open. Worth a look before filing — and the open ones are the best place to contribute.