An AI workflow can finish without producing something a business should use. A project brief may exist but omit its owner. An image description may include an unsupported claim. The operator needs a way to find those records, inspect the reason and hand the problem back to the right workflow.
This build turns twelve run records into a local browser console with status filters, individual record pages and a saved attention queue. It uses Datasette, SQLite and a small Python importer. The result is useful before committing to a custom operations dashboard.
Example boundary: Harbor Studio is fictional. Every record below was authored for this tutorial, including the timestamps and failure reasons. No model generated these outputs, no live collector is connected, and the artifact filenames are labels for files that are not supplied. This tests the review interface, not an AI model or a business result.
Where Datasette fits
Datasette provides a browser interface over SQLite data. Its table pages make a practical starting point for examining a prepared dataset. The project is available under Apache-2.0. See the official getting-started guide and license for the tested release.
The useful boundary here is export → snapshot → inspect. Your workflow remains responsible for producing records and resolving problems. This console does not approve an output, retry a job, update a CRM or send a customer message. If those actions are needed, build them behind explicit state and permission checks; the retryable CRM handoff example covers the separate delivery problem.
Use this approach when a developer or operator needs to browse a modest, sanitized batch. A spreadsheet may be enough for a one-off review. A shared work queue with assignments, signoffs and alerts needs more than this snapshot. Datasette supports writable canned queries, so read-only is a deliberate property of this configuration, not a blanket claim about the product. Canned-query documentation.
1. Install the tested version in a separate folder
The commands below are for Windows PowerShell. The example ran on Windows 11 with Python 3.14.3, SQLite 3.50.4 and Datasette 0.65.5. Use an empty working folder, a terminal and a browser. Installation requires internet access; the running example needs no model key or cloud account. The version is pinned to the tested PyPI release.
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install "datasette==0.65.5"
.\.venv\Scripts\python.exe -m datasette --version
Calling the environment’s Python directly avoids changing your shell’s activation policy. The command above pins Datasette but resolves its dependencies at installation time. For the full version set observed in this run, save the following as requirements-tested.txt and install it with .\.venv\Scripts\python.exe -m pip install -r requirements-tested.txt. This is an environment record, not a guarantee of support on other operating systems or a permanent security endorsement.
Complete tested dependency versions
aiofiles==25.1.0
anyio==4.15.1
asgi-csrf==0.11
asgiref==3.12.1
certifi==2026.7.22
click==8.5.0
click-default-group==1.2.4
datasette==0.65.5
flexcache==0.3
flexparser==0.4
h11==0.16.0
httpcore==1.0.9
httpx==0.28.1
hupper==1.12.1
idna==3.20
itsdangerous==2.2.0
janus==2.0.0
Jinja2==3.1.6
MarkupSafe==3.0.4
mergedeep==1.3.4
platformdirs==4.12.3
pluggy==1.6.0
python-multipart==0.0.32
PyYAML==6.0.3
setuptools==84.0.0
typing_extensions==4.16.0
uvicorn==0.54.0
2. Give each run a useful record
Save the complete fixture as runs.json. The six fields capture identity, workflow, status, reason, artifact reference and finish time. The timestamps use one sortable UTC format. Real exports should normalize their dates before import; this small builder checks field shape, not timestamp semantics.
| Status | Meaning in this example | Count |
|---|---|---|
ready |
A draft artifact exists. Human approval is not recorded. | 6 |
needs_review |
A draft has a specific unresolved question. | 4 |
failed |
The simulated run produced no draft. | 2 |
Keeping a reason alongside the status makes the queue actionable. “Review required” is less useful than “Project owner is missing from source notes.” For real systems, emit these fields from the workflow’s recorded state; do not ask a model to guess whether a job completed.
Complete fictional run records — runs.json
[
{"run_id":"R001","workflow":"brief","status":"ready","reason":"Draft artifact created; no human approval recorded","artifact_ref":"brief-001.json","finished_at":"2026-10-05T08:00:00Z"},
{"run_id":"R002","workflow":"brief","status":"needs_review","reason":"Project owner is missing from source notes","artifact_ref":"brief-002.json","finished_at":"2026-10-05T08:05:00Z"},
{"run_id":"R003","workflow":"catalog","status":"ready","reason":"Draft artifact created; no human approval recorded","artifact_ref":"catalog-003.json","finished_at":"2026-10-05T08:10:00Z"},
{"run_id":"R004","workflow":"alt_text","status":"needs_review","reason":"Description includes a claim not visible in the image","artifact_ref":"alt-004.json","finished_at":"2026-10-05T08:15:00Z"},
{"run_id":"R005","workflow":"brief","status":"failed","reason":"Upstream request timed out; no draft produced","artifact_ref":"","finished_at":"2026-10-05T08:20:00Z"},
{"run_id":"R006","workflow":"catalog","status":"ready","reason":"Draft artifact created; no human approval recorded","artifact_ref":"catalog-006.json","finished_at":"2026-10-05T08:25:00Z"},
{"run_id":"R007","workflow":"alt_text","status":"ready","reason":"Draft artifact created; no human approval recorded","artifact_ref":"alt-007.json","finished_at":"2026-10-05T08:30:00Z"},
{"run_id":"R008","workflow":"catalog","status":"needs_review","reason":"Source catalog revision is older than the requested revision","artifact_ref":"catalog-008.json","finished_at":"2026-10-05T08:35:00Z"},
{"run_id":"R009","workflow":"alt_text","status":"failed","reason":"Input image could not be decoded; no draft produced","artifact_ref":"","finished_at":"2026-10-05T08:40:00Z"},
{"run_id":"R010","workflow":"brief","status":"ready","reason":"Draft artifact created; no human approval recorded","artifact_ref":"brief-010.json","finished_at":"2026-10-05T08:45:00Z"},
{"run_id":"R011","workflow":"brief","status":"needs_review","reason":"Deadline in the draft is not supported by source notes","artifact_ref":"brief-011.json","finished_at":"2026-10-05T08:50:00Z"},
{"run_id":"R012","workflow":"alt_text","status":"ready","reason":"Draft artifact created; no human approval recorded","artifact_ref":"alt-012.json","finished_at":"2026-10-05T08:55:00Z"}
]
3. Build a snapshot, keeping its source
Save this as build_snapshot.py. It creates a new directory containing runs.db, a byte-for-byte source copy and a manifest with hashes. Run IDs are primary keys, and SQLite constrains the three status values. Inserts use parameters. The output directory must not already exist.
Complete snapshot builder — build_snapshot.py
"""Create one review snapshot from fictional/sanitized run records."""
import argparse
import hashlib
import json
from pathlib import Path
import sqlite3
COLUMNS = ("run_id", "workflow", "status", "reason", "artifact_ref", "finished_at")
def build(source, output):
source, output = Path(source), Path(output)
raw = source.read_bytes()
rows = json.loads(raw)
if not isinstance(rows, list) or not rows:
raise ValueError("Expected a nonempty list of run records")
for row in rows:
if not isinstance(row, dict) or set(row) != set(COLUMNS):
raise ValueError("Run fields differ from the documented schema")
if not all(isinstance(row[key], str) for key in COLUMNS):
raise ValueError("Every field must be text")
output.mkdir(parents=True, exist_ok=False)
db = output / "runs.db"
with sqlite3.connect(db) as conn:
conn.execute("""CREATE TABLE runs (
run_id TEXT PRIMARY KEY NOT NULL,
workflow TEXT NOT NULL,
status TEXT NOT NULL CHECK(status IN ('ready','needs_review','failed')),
reason TEXT NOT NULL,
artifact_ref TEXT NOT NULL,
finished_at TEXT NOT NULL
)""")
conn.executemany("INSERT INTO runs VALUES (?,?,?,?,?,?)",
[tuple(row[key] for key in COLUMNS) for row in rows])
conn.execute("CREATE INDEX runs_status ON runs(status)")
conn.execute("CREATE INDEX runs_workflow ON runs(workflow)")
manifest = {"source_sha256": hashlib.sha256(raw).hexdigest(),
"database_sha256": hashlib.sha256(db.read_bytes()).hexdigest(),
"rows": len(rows), "sqlite_version": sqlite3.sqlite_version,
"scenario": "Harbor Studio is fictional; all records are synthetic"}
(output / "source.json").write_bytes(raw)
(output / "manifest.json").write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
return manifest
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("source", type=Path)
parser.add_argument("output", type=Path)
args = parser.parse_args()
print(json.dumps(build(args.source, args.output), indent=2))
.\.venv\Scripts\python.exe -X utf8 build_snapshot.py runs.json snapshot-001
The manifest helps associate the inspected database with its source. It does not prove who created a record, whether its contents are true or whether an approval happened. The importer also keeps the entire input in memory and is not a streaming service. If a failed build leaves an incomplete directory, inspect the error and use a new output directory after correcting the data.
4. Add filters and a saved attention query
Save metadata.json beside the builder. The two facets let an operator narrow records by status and workflow; finished_at sorts newest first. These are built-in Datasette facets. The named query selects review and failure records together, without relying on someone to type SQL each time.
Complete console configuration — metadata.json
{
"title": "Harbor Studio - fictional AI run review",
"description": "Synthetic example data. Local review snapshot; ready means an artifact exists, not human approval.",
"databases": {
"runs": {
"description": "Twelve authored fixtures. No live business systems or model runs are connected.",
"tables": {
"runs": {
"description": "Filter by status and workflow. Artifact references are labels only; the referenced files are not supplied.",
"facets": ["status", "workflow"],
"sort_desc": "finished_at"
}
},
"queries": {
"needs_attention": {
"title": "Runs needing attention",
"description": "Failures and drafts flagged for review. This query does not approve, retry or send anything.",
"sql": "SELECT run_id, workflow, status, reason FROM runs WHERE status IN ('needs_review', 'failed') ORDER BY finished_at DESC"
}
}
}
}
}
Start the server from the same working folder:
.\.venv\Scripts\python.exe -m datasette serve -i snapshot-001/runs.db -h 127.0.0.1 -p 8766 -m metadata.json --setting default_allow_sql off --setting allow_download off --setting suggest_facets off
Open http://127.0.0.1:8766/runs/runs on that computer. Keep the terminal running; Ctrl+C stops it. If the port is occupied, stop the conflicting example or select another unused port and update the browser and test URLs consistently.
The -i option serves an immutable database; -h 127.0.0.1 binds the server to the local loopback interface. The -m option loads this metadata file. Those flags are documented in the CLI reference. Inspection of the tested release’s database implementation confirmed its immutable SQLite connection path.
Arbitrary SQL is disabled, raw database downloads are disabled, and automatic facet suggestions are disabled while the two configured facets remain. The settings documentation defines these controls. This setup adds no custom plugins, login system or public deployment.
5. Review the queue before changing anything
- On the runs table, click needs_review. Four records should remain.
- Click the brief workflow facet. You should see R011 and R002.
- Inspect the reasons: R011 has an unsupported deadline; R002 lacks a project owner. Open a run ID to inspect that record in full.
- Use the runs database breadcrumb, then Runs needing attention. The saved queue should contain six records: R011, R009, R008, R005, R004 and R002, in that order.
Those interactions were exercised in the actual browser. Both the filtered table and saved query rendered their expected records and reasons. The queue’s “Run SQL” button reruns the saved query; it is not a job retry or approval action.
For an operational next step, the person reviewing R002 would correct the missing source information in the owning workflow. R011 calls for checking the claimed deadline against its source. Failed runs need their underlying cause inspected before any retry. The console offers visibility; it does not close these issues.
What the running example actually passed
Save test_console.py and run it in a second terminal from the same folder while Datasette is running:
.\.venv\Scripts\python.exe -X utf8 test_console.py http://127.0.0.1:8766 snapshot-001
Complete HTTP and snapshot checks — test_console.py
"""Check a running local Datasette example over actual HTTP."""
import argparse
import hashlib
from importlib.metadata import version
import json
from pathlib import Path
import platform
import sqlite3
from urllib.error import HTTPError
from urllib.parse import urlencode, urlparse
from urllib.request import build_opener, ProxyHandler
def main(base, snapshot):
parsed = urlparse(base)
if parsed.scheme != "http" or parsed.hostname != "127.0.0.1":
raise ValueError("This test targets only the local example")
root = Path(__file__).resolve().parent
snapshot = Path(snapshot)
expected = json.loads((snapshot / "source.json").read_text(encoding="utf-8"))
manifest = json.loads((snapshot / "manifest.json").read_text())
db = snapshot / "runs.db"
before = hashlib.sha256(db.read_bytes()).hexdigest()
assert before == manifest["database_sha256"]
opener = build_opener(ProxyHandler({}))
checks = []
def get(path):
try:
with opener.open(base.rstrip("/") + path, timeout=10) as response:
return response.status, response.read()
except HTTPError as response:
return response.code, response.read()
def passed(name):
checks.append({"check": name, "passed": True})
status, body = get("/")
assert status == 200 and b"fictional AI run review" in body
passed("real HTTP homepage contains fictional-example title")
status, body = get("/runs/runs.json?_shape=objects")
all_rows = json.loads(body)
assert status == 200 and all_rows["filtered_table_rows_count"] == 12
assert sorted(all_rows["rows"], key=lambda r: r["run_id"]) == expected
passed("JSON table preserves all twelve source rows")
counts = {r["value"]: r["count"] for r in all_rows["facet_results"]["status"]["results"]}
assert counts == {"ready": 6, "needs_review": 4, "failed": 2}
passed("status facets count six ready, four review and two failed")
status, body = get("/runs/runs.json?status=needs_review&workflow=brief&_shape=objects")
assert status == 200 and {r["run_id"] for r in json.loads(body)["rows"]} == {"R002", "R011"}
passed("combined status/workflow filter returns the two intended briefs")
status, body = get("/runs/needs_attention.json?_shape=objects")
attention = json.loads(body)
assert status == 200 and [r["run_id"] for r in attention["rows"]] == ["R011", "R009", "R008", "R005", "R004", "R002"]
passed("canned review query returns six intended rows newest first")
(root / "attention-response.json").write_text(json.dumps(attention, indent=2), encoding="utf-8")
status, body = get("/runs/runs/R002.json?_shape=objects")
assert status == 200 and b"Project owner is missing" in body
passed("individual run record retains its review reason")
status, body = get("/runs.json?" + urlencode({"sql": "select count(*) from runs"}))
assert status == 403
passed("ad hoc SQL endpoint is denied to the anonymous test client")
status, body = get("/runs.db")
assert status == 403
passed("raw database download is denied")
status, body = get("/runs/runs.csv?status=failed")
assert status == 200 and b"R005" in body and b"R009" in body and b"R001" not in body
passed("filtered CSV still exports visible data despite raw-download restriction")
conn = sqlite3.connect(db.resolve().as_uri() + "?immutable=1", uri=True)
try:
conn.execute("UPDATE runs SET status='ready' WHERE run_id='R002'")
except sqlite3.OperationalError as error:
assert "readonly" in str(error).lower()
passed("SQLite immutable connection rejects a write")
else:
raise AssertionError("Immutable connection unexpectedly allowed write")
finally:
conn.close()
assert hashlib.sha256(db.read_bytes()).hexdigest() == before
passed("database hash unchanged after HTTP review and write rejection")
evidence = {"passed": len(checks), "checks": checks, "python": platform.python_version(),
"platform": platform.platform(), "datasette": version("datasette"),
"sqlite": sqlite3.sqlite_version, "base_url": base,
"database_sha256": before,
"limits": "Twelve synthetic records on a local Windows server. No LLM, live collector, login/roles, write plugin, remote deployment, load test or real business result."}
(root / "test-evidence.json").write_text(json.dumps(evidence, indent=2) + "\n", encoding="utf-8")
print(json.dumps(evidence, indent=2))
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("base_url")
parser.add_argument("snapshot", type=Path)
args = parser.parse_args()
main(args.base_url, args.snapshot)
All 11 checks passed against the actual local server: homepage availability; all twelve source rows preserved through JSON; the 6/4/2 facet counts; the two-brief filter; the six-row saved query; the individual review reason; denied ad hoc SQL; denied raw database download; working filtered CSV export; a rejected write through a separate immutable SQLite connection; and an unchanged database hash.
The last write test checks the SQLite connection mode directly. It is not a penetration test of every Datasette route. These results cover one synthetic dataset and a desktop browser inspection. They do not measure production throughput, AI accuracy, cross-platform behavior, authentication or multi-user operation. For a separate model-output regression workflow, see the routing-evaluation example.
Limits that matter before using real records
Download controls are not confidentiality controls. The test returned HTTP 403 for /runs.db, but HTTP 200 for the filtered CSV containing R005 and R009. JSON and visible table data remain accessible. Anyone who can reach this configured instance can read the dataset. Localhost narrows network exposure; it does not authenticate people or programs on that computer. Use sanitized records and omit prompts, client details, secrets and unnecessary payloads.
Snapshot freshness is your responsibility. This is not live monitoring. Do not update a database while it is served as immutable. Stop the server, create a new snapshot directory from a new export, and restart the same command with that directory’s runs.db. Keep the database filename unchanged so the metadata’s runs key still matches. Record the export time and retention policy when adapting this to a real workflow.
A ready row is not an approval. There is no reviewer identity or approval event in this schema. Adding a green badge cannot supply either. A future shared application needs authenticated reviewers, recorded decisions, a defined state transition and authorization around each consequential action.
A larger queue needs explicit paging and operating checks. Do not assume a saved query returns every row at any size. Verify result truncation and pagination, query timing, backups and failure handling against your actual dataset before relying on it. No scale claim follows from twelve records.
The practical deliverable is small: a reproducible batch, understandable reasons and a browser path to the records that need attention. That is enough to evaluate whether a richer business operations application is worth building.
Research and local tests: October 5, 2026. The featured image is an AI-generated conceptual illustration, not a Datasette screenshot.
Published by Jason Ead for AlgoThoughts AI News. My Biz Heroes connects this practical approach to websites, automation, business workflows, AI tools and training. Be Your Evolution. Advance yourself with AI.
