sitewitness
An agent over your site that shows its work.
Point it at a folder of texts (HTML or Markdown) and JSON data files, and you get a question-answering API with a trust protocol enforced in code, an evaluation runner, and a static page that shows every answer's trace. First instance: bsandova.com/ask.
pip install sitewitness # add [embed] for local embeddings (sentence-transformers)
sitewitness index # texts → passages + BM25 (+ embeddings), data files → schemas
sitewitness serve --port 8000 # POST /ask {question} · GET /health
sitewitness ask "How many …?" # one question from the terminal, trace on stderr
sitewitness eval golden.json --api http://localhost:8000
sitewitness page ask/ --api https://api.example.com --example "How many …?"
The protocol
Five rules, each a check in protocol.py, each a flag in the trace. The first violation replaces
the answer with a refusal and names itself in trace.enforced.
| rule | what it catches |
|---|---|
require_tool_use |
an answer produced without calling any tool |
numbers_need_tool |
a digit in the answer with no read_data or search result in the conversation |
forbid_code |
fenced code, def/import, or HTML in the answer |
quotes_need_verification |
a quotation of 40+ characters without a quote call that came back found |
sources_must_exist |
a Sources: line naming a file or page the site does not have |
Plus caps: tool calls, output tokens, wall time; a budget per UTC day; a rate per address per
hour. Rules are switched only in sitewitness.toml, never by a prompt.
Tools the model gets
list_sources · describe_source (schema of a JSON file) · read_data (dotted path, find
filter, clipped) · search (BM25, plus embeddings with reciprocal rank fusion when present) ·
quote (verbatim check through quotecheck's fold).
Config
[site]
name = "example.com"
base_url = "https://example.com"
texts = ["texts/**/*.html"]
[site.data]
"surf/data.json" = "Surf forecast verification: skill by lead, quality counts."
[model]
name = "claude-sonnet-5"
max_output_tokens = 700
price_in_per_m = 3.0 # configured list prices; the trace says so
price_out_per_m = 15.0
[limits]
max_tool_calls = 8
daily_budget_usd = 4.0
per_ip_per_hour = 12
store = "sqlite:.sitewitness/limits.db"
[server]
allowed_origins = ["https://example.com"]
eval_key_env = "SITEWITNESS_EVAL_KEY" # X-Eval-Key bypasses the per-address limit, never the budget
ANTHROPIC_API_KEY in the environment. Every trace carries: model requested and used, turns,
tools with inputs and sizes, tokens, cost from the configured prices, the protocol flags, what was
enforced, and today's budget.
Evaluation
golden.json: {"questions": [{"q": "…", "kind": "number|text|quote|decline", "expect": "…"}]}.
sitewitness eval scores each question (substring match, or a refusal when expect is
DECLINE), counts protocol violations from the traces, writes eval.json and appends to
eval-history.json, and exits 1 when misses exceed --allowed-misses, so it can gate a deploy.
No framework, no network in the 44 tests. Apache-2.0.
Metadata
Release files for sitewitness 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| sitewitness-0.1.0.tar.gz | 159.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sitewitness-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 190.3 kB
Release files / sitewitness-0.1.0.tar.gz
| Download URL | sitewitness-0.1.0.tar.gz |
|---|---|
| Size | 159.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b1f1119664b97019c5ae5215eea72ad567bb089563a8611527c9cdd76feb3f5d
|
|
BLAKE2b-256 checksum How to use checksums |
e0d5e57ca8dd920a72033814e8865bd1a62120cefd53b2b5b761e41920c81c7a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Sep 22, 2026.
Transparency logRelease files / sitewitness-0.1.0-py3-none-any.whl
| Download URL | sitewitness-0.1.0-py3-none-any.whl |
|---|---|
| Size | 30.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
06fc87e65d49d8c33150fa50aa082be103f7fdf64edeeb8b0ed2e81d5c973e4b
|
|
BLAKE2b-256 checksum How to use checksums |
b68f1754f2185cfdf5f89d5ba86a2857db50c18c219a845ceebf91a9cb57df51
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Sep 22, 2026.
Transparency log