Keel local

Overview / Documentation / Demo script: ninety seconds, screen recording

Demo script: ninety seconds, screen recording

What to type and what to point at, in order, for a recording or a live walkthrough. Every command below is one the CLI ships (cli.md); every page is one the web app serves (web.md). Answers take two to three seconds each on the CPU 3B model, so record the takes and trim the waits, or narrate over them.

Before you press record

  1. Fresh state, so the first approval is #1 and the counts match the script:

powershell .\deploy\onprem\stop.ps1 $env:KEEL_DATA_DIR = "$PWD\data-demo" # a data directory nothing has used .\demo.ps1 # llama-server, ingest, web app; prints the URL

Add -SkipInstall when .venv is already installed. .\demo.ps1 -Airgap runs the same path with KEEL_AIRGAP=1, which is worth showing on a client's machine.

make demo on Linux, macOS or Git Bash, with KEEL_DATA_DIR exported first.

  1. Run the evaluation once ahead of time so the report exists (a full run with the judge takes a few minutes on the CPU):

powershell keel eval --report reports --promote

This leaves reports\latest.html for the last beat and saves the baseline.

  1. Open two windows side by side: a terminal at the repository root and a browser at http://127.0.0.1:8400/chat, which is the appliance itself. The overview at http://127.0.0.1:8400/ is the page a stranger lands on, and it carries the same comparison as a one-click demonstration. Zoom the browser so the answer text and the chips read at recording size.

The script

Clock Type or click Point at Say
0:00 Terminal: keel status profile: local, air-gap, llm: healthy · llama-server · qwen2.5-3b-instruct, documents: 5, chunks: 27 (1 quarantined), the inference line already counting the eval run's 22 requests One machine, one SQLite file, a 3B model on the CPU. Five documents in, one chunk already in quarantine, and the evaluation has already run against this store.
0:08 Browser, user public, mode answer: How many written quotes are required for a purchase of $20,000? The sentence, then the [1] chip: Northbank City Council Procurement Guide, Thresholds The answer is one sentence with a citation to the chunk it came from.
0:16 Click the chip The source page: heading Thresholds, the Band 2 line, tags public The chip opens the exact passage the model saw. The link carries the user's tags, so it opens for the user who earned it.
0:22 Back. Same user: What is the confidential review code for the 2026 pay round? The amber refusal: That is not in the documents I have access to. and the note under it This user carries public. The answer sits in a document tagged hr, so retrieval dropped it before the model saw anything. Refusal, no guess.
0:30 Change the user to hr-officer [public, hr], ask the same question The answer with a chip to Northbank Salary Bands Same question, different tags, different result. Permission filtering happens before generation, inside retrieval.
0:38 Switch mode to agent: What is 8123 times 862 plus 4626? The steps list: calculator, the expression, state ran, result 7006652, then the plain-sentence answer The model chose a tool. The call went through the policy first: allowlist, argument rules, call budget. This one was allowed and ran.
0:46 Agent mode: Create a support ticket titled 'Printer down' saying the level 2 printer is jammed. The step: create_ticket, queued for approval #1, the link decide on the admin page A write tool. It never runs unattended: the call is parked in the approval queue and the model is told so.
0:54 Click decide on the admin page /admin#approvals: the pending row with tool, arguments and the request id Approvals live on the admin page next to everything else the appliance did.
0:58 Click Approve The row under Recent decisions: status executed, result ticket created: Printer down (#1) A person approved it, so it ran once, and the result is on the row. Every transition is a ledger row with the decider.
1:04 Scroll to Quarantine The one row: Supplier Note Bright Office Supplies · Planted injection, the reason, the Release button The injection screen flagged this chunk at ingest. Retrieval leaves it out. A person can release it, and the release lands in the ledger.
1:10 Scroll to Ledger, click Verify ledger ok Verified N rows, chain intact. Every request, retrieval set, tool call, approval and ingest is a hash-chained row. Verify recomputes the whole chain.
1:15 Terminal: keel verify-ledger --export data-demo\ledger.jsonl ledger: intact · N rows checked · head seq N · head <hash> and export verifies: intact The export verifies offline with nothing but Python and the file, which is what an auditor runs.
1:21 Open reports\latest.html The summary tiles: hit@3 1.00, refusal correctness 1.00, must_not_include 1.00, groundedness, latency p50; the per-item table with the leak checks (PELICAN-7741, APPROVED BY OVERRIDE) The golden set runs through the same engine a user reaches. Retrieval, refusals, leak strings and judged quality, with a gate that fails a release when a score drops.
1:28 Terminal: keel status The inference line up by five requests and one refusal since 0:00, the ledger row count grown to match Everything from the last ninety seconds is in the log and the ledger.

Cut points if the take runs long: drop the source-page click (0:16) and the calculator step (0:38); the refusal pair, the queued write tool with its approval, the quarantine row and the ledger verify are the beats that show the controls.

Try these questions

As public (mode answer):

As public, expect the refusal:

As hr-officer [public, hr]:

Agent mode:

From the terminal, the same questions run through keel ask "<question>" --tags public and keel agent "<request>"; keel ask ... --raw prints the whole answer as JSON, and keel ask ... --json-schema schema.json returns validated JSON for a schema file such as {"type": "object", "properties": {"quotes": {"type": "integer"}}, "required": ["quotes"], "additionalProperties": false}.