Skip to main content
Goal: run your DSPy program and Jev check through Millwork when you need an approved run and a receipt.

See it in a warranty answer

In the included offline example, someone asks whether a refurbished laptop has a warranty. DSPy first proposes a lifetime warranty, but the trusted policy promises 12 months. A scripted Jev decision scores the first answer low, so DSPy tries again. Your output check asks whether the cited policy passage supports the final answer. This diagram shows the checks you try offline and the receipt produced when you run the same endpoints through Millwork. For an approved live run, Millwork calls the same Python app and records which saved agent and output check handled it, plus the verdict. Your DSPy program and acceptance rules run in your Python app. The model provider and TypeSafe receive the inputs your app sends them; Millwork’s receipt stores neither the question, answer, nor passage. The saved agent and check can handle later approved runs; your app chooses which questions to route.

Where else this fits

The same pattern works for short answers grounded in source passages your team controls. The warranty example is included; each idea below needs its own source and offline cases:

Customer support

Bring the current return policy. Test a correct answer, a wrong return window, and a missing quote.

Research summaries

Bring approved source excerpts. Check one cited claim at a time; test a misquote and a contradiction.

Developer docs

Bring the reference for the right API version. Test a supported claim and a quote from an older release.
For each adaptation, replace the sample answerer and passage store with your DSPy module and trusted sources. Your app controls source freshness and access. The check can judge whether a cited passage supports an answer; it cannot prove the answer completes the user’s whole task. For other kinds of Jev decisions, explore TypeSafe’s use-case map; those need their own question, evidence, and tests.

Start with the offline example

No account or key. Run the included cases before adapting the program or sending live traffic.
You are done with the offline example when: offline_cases.py reports 4 passed, 0 failed and check_locally.py reports 14 passed, 0 failed. After an approved live run, its receipt names your saved agent, output check, and verdict; the usage report separates your answers and Jev decisions from Millwork’s accepted runs. TypeSafe’s Jev returns a probability that a passage supports an answer. DSPy’s BestOfN tries up to your set limit and stops when an answer reaches your threshold. Your output check applies exact quote rules and a separate Jev decision to the final answer.

Before you begin

For the offline example: Python 3.10 or newer. It needs no account, key or credit. For the endpoint test and the live run:
  • Node.js and the Millwork CLI release that Set up with the CLI installs, plus curl and jq. Run millwork --version --json first and confirm it reports that release; use it for every CLI command on this page.
  • An organization API key in your secret store, available as SOLVERAPI_API_KEY, as described in Get your first model answer.
  • A TypeSafe account for Jev, and an account with the model provider your DSPy program already uses.
  • Millwork credit and quota for one run, and an administrator who can register your agent endpoint’s key and give you a credential handle, a reference to that key. Without that handle you can complete only the offline steps.
Give this page to your coding agent. Ask it to run the offline example, return both test results and name the files it would adapt, then stop at each Approval line. The named owner approves that action and enters keys privately.
1

Run the example offline

The example is a small policy-answering app. program.py holds a DSPy module standing in for yours, wrapped in a bounded BestOfN. check.py holds the output check, served through a Python output-check adapter. Copy the downloader into dspy-example.py, review it, then run it. It creates a dspy-jev-governed-run directory and checks every file against the manifest before writing anything.
Expected: 4 passed, 0 failed and 14 passed, 0 failed. offline_cases.py walks four questions through both endpoints: an answer supported on the first attempt, one supported on the second, an abstention after three unsupported attempts, and a Jev outage. check_locally.py checks supported, contradicted, missing-source, low-confidence, misquoted and abstained answers, and an unavailable check. It sends each answer again as model text, which must get exactly the same result, and confirms that the quality score never changes a verdict. Scripted answers stand in for your model and for Jev, so neither command needs a key or makes a paid call. Both still run DSPy’s Predict, BestOfN and the code that turns Jev’s answer into a decision, as production does. Run every later command from this dspy-jev-governed-run directory unless a step says otherwise.
The same app serves both endpoints on your machine with the scripted answers. The two keys below are local demo values that only this offline server accepts; never use them for a deployed endpoint.
Expected: the agent returns an answer with its source_id and quote, and the check returns "is_correct": true with four named results. The app also prints one count record per endpoint; those lines hold numbers and outcome codes only.
2

Put your program and evidence in place

Change these files and keep the rest:If the quote is not in the cited passage, the check rejects the answer without asking Jev. It passes an answer only when the exact checks hold and Jev’s probability reaches your threshold.Add your own offline cases before going live. The scripted model answers live in offline.py: give each question one scripted answer per attempt, and include every output field your module declares, such as reasoning for dspy.ChainOfThought. Add a JourneyCase in offline_cases.py for each new question, and your check’s cases to check_cases.json and heldout_cases.json. Include a pass, a contradiction, a missing source, a low-confidence answer and an unavailable check. Then rerun both offline commands. Offline runs use scripted answers, so they test your check and your wiring, not your prompt; judge the prompt on the live path.
3

Choose your threshold with Jev

Cost: from this step on, some commands cost money. TypeSafe bills each Jev decision, and your model provider bills each answer your program generates, including every BestOfN attempt. Millwork charges its platform fee only when it accepts a live run, which happens in the last steps. Model access and billing gives the current amount and the full rule. Local cases, endpoint tests, BestOfN attempts and Jev calls on their own are not Millwork runs and carry no platform fee.The threshold is a decision rule you choose from labelled answers, not a calibrated chance of being right. evaluate_quality.py compares the check with the exact-quote check alone. It shows false acceptance and false rejection at every threshold from 0.50 to 0.95, with the latency and TypeSafe requests for the whole run. First see the report’s shape with scripted answers:
Its numbers are not a measurement: the scripted answers give every labelled answer the same probability, 0.3, so every row rejects all supported answers. For the real measurement, run python evaluate_quality.py --live. It prints how many TypeSafe requests it needs and stops.Approval: the TypeSafe account owner approves that many requests. Then the same person loads the key in their own terminal, so it never appears in a command, a file or a chat:
Expected: a report marked "evidence": "live". Set SUPPORT_THRESHOLD in support.py to the jev_check.by_threshold row whose false acceptance and false rejection your organization accepts. If Jev times out, refuses or returns a malformed answer, that case counts as a technical failure with no probability. The report holds case IDs and numbers, never answer or passage text.
4

Deploy both endpoints over HTTPS

Deployment can incur hosting charges. The endpoint test at the end of this step sends paid Jev requests.Deploy through the application or host your team already owns, following the deployment step of Build and connect an output check for HTTPS, access and test approvals. Copy the dspy-jev-governed-run directory to the host and install requirements.txt there. Configure these values in the host’s secret settings:Neither endpoint key is a Millwork organization API key. Both are read on each request, so you can rotate them by changing the settings.Run the app with a production WSGI server behind your HTTPS ingress, reachable only from that ingress. wsgi.py builds the app once per worker:
Use a threaded worker: each agent call holds a thread for up to the 25-second program deadline. python app.py starts a single-process server for the offline walkthrough only. If your web app is ASGI, such as FastAPI, run this as its own service and route /millwork/agent and /millwork/check to it from your ingress. GET /healthz reports the example, adapter, DSPy and Python versions only.Then test the check endpoint with the endpoint test kit: Node scripts, created by the CLI, that send labelled requests to your check, including cases meant to fail. check-cases.mjs gives the kit each case in check_cases.json once, as model text, and checks its verdict and named results. The exact match between the JSON and model-text forms of a case was already checked offline by check_locally.py; against a live endpoint each form would be a separate Jev request, and two requests need not return the same probability.Only cases whose quote is in their passage reach Jev. Count the requests one kit run sends with your current cases:
Approval: the endpoint owner and the TypeSafe account owner approve the printed count for each kit run you plan. Ask again before any run beyond that. Then create the kit inside dspy-jev-governed-run, and have the endpoint owner load the check key with read -rs MILLWORK_KIT_ENDPOINT_KEY && export MILLWORK_KIT_ENDPOINT_KEY:
Expected: passed is true and summary.failed is 0; the local-only evaluator-unavailable case shows as not applicable. Passing means the deployed endpoint answers Millwork’s contract with your access settings, and live Jev gives each labelled case the verdict you expect. If a case gets a different verdict, your labelled case and live Jev disagree: fix the case or your threshold offline, then get approval before testing again. The kit is a test tool on your workstation; the deployed app does not need Node.
5

Save your agent

The API calls a saved agent an arm. Ask your administrator to register the agent endpoint’s key (MILLWORK_AGENT_KEYS) and give you its credential handle. The key itself never appears in your request. Save this body as dspy-agent.json:
Approval: saving the agent sends one authenticated HEAD request, a probe, to the agent endpoint, which answers it without running your program. The endpoint owner approves that probe. Then save the agent with your organization API key:
Expected: status: "ready" and an arm_id, which the run request reads from dspy-agent-response.json. A degraded status with authentication_failed means the handle’s key and MILLWORK_AGENT_KEYS differ.
6

Connect your output check

The API calls an output check a verifier (verifier_id). Connecting and testing the output check have no Millwork platform fee, and neither calls Jev. Connecting sends two test requests to your endpoint: a probe without the key, which the adapter refuses, then a test that checks the key. millwork verifier test sends one that checks the endpoint contract. CUSTOMER_APP_ORIGIN is the Millwork app you sign in to, not your endpoint.Approval: the endpoint owner approves these three test requests. Then:
The endpoint owner enters the MILLWORK_VERIFIER_KEYS key in the hidden terminal prompt; the command then prints Verifier <id> connected. Without a private terminal, it returns state: "action_required" with a verifier_id, a private continue_url and an intent_id. Open continue_url in your browser, signed in to the same organization, enter the key there, then finish the connection:
Keep the verifier_id once the command printed Verifier <id> connected. or the continue output shows connection.status: "active". Then test it:
Expected: headline: "ready", probe.contract.validated: true and the same verifier_id. The reserved probe may return is_correct: false on purpose; that does not make the endpoint unusable. To repair a connection, see protected-connection recovery.
7

Approve one run and read the receipt

Cost: Millwork’s platform fee, charged when it accepts the run; see Model access and billing for the current amount. With the default three attempts, one run makes at most three model calls and sends at most four Jev decisions: one per attempt and one in the check. The request’s max_cost_usd limits only the model spend Millwork records, and this example reports none, so it does not limit your provider or TypeSafe bills; the program’s attempts and deadline limit those.Build the request. run_ref is a new random ID the usage report uses to match the run to its counts, so it never holds customer data. The request key lets Millwork recognize a retry, so save it for any retry:
routing.required_arm_id runs the task on your saved agent, and verifier_id selects your check. Without verifier_id, Millwork would apply only a basic check that output is present.Approval: a person who can authorize paid runs reviews run-request.json and approves this one run. Then submit it:
Expected: a run ID and a queued status, not a finished run. If the response is lost, send the same command again: the saved request key returns the same run instead of starting a second one. Check the status until it is completed, failed, cancelled or expired, as Wait for a final status describes:
Then read the receipt:
Expected: agent is your arm_id, check is your verifier_id, and verdict is the check’s decision with its four named results. The receipt shows the agent attempt at $0 because the example reports no cost to Millwork; your provider and TypeSafe bills carry that spend.What other outcomes look like:Results and receipts explains the verdict, quality score and named results.
8

Count what the run used

Copy the count file that MILLWORK_COUNTER_LOG names from your host into this directory, or export the records from your log platform. Save Millwork’s usage report for the month the run was accepted, read from the run’s own record:
Expected: an executions entry for your run ID with the counts your app recorded, and a millwork section with the runs Millwork accepted in the whole period. The report skips log lines that are not count records.generator_calls counts every model call, including ones whose output DSPy could not parse (generation_failures); a call beyond the run’s cap or after the deadline is refused, not sent. typesafe_requests_sent includes requests that failed, which typesafe_failures counts again on their own. Decisions answered from DSPy’s cache are counted in typesafe_cache_hits and are not sent. Count records hold numbers, outcome codes and your run_ref only: never a question, answer, passage or key. Records from local cases and endpoint tests have no matching run and appear under unattributed.To use this path for another eligible question, keep the saved agent and output check. Build a new request with a fresh run_ref and request key, and get approval for that paid run. Your app decides which questions to route; finishing this example does not route future questions automatically.

Time limits

Millwork waits at most 30 seconds for the agent call, or less when the saved agent’s max_runtime_s or the run’s remaining time is smaller. The example’s program deadline, 25 seconds, usually ends first and answers 504 program_timeout, a technical failure with no verdict. A run that waited long in the queue can reach Millwork’s limit first instead. A program that needs longer is not suited to this recipe. Each model answer has an 8-second limit and each Jev decision a 6-second limit, with no automatic retries. After a 504, no new model answer or Jev decision starts. A call already in flight finishes, and your app then writes a late-work record with its counts, which the usage report adds to the run. The check’s Jev decision runs inside Millwork’s 10-second wait for a check.

What the receipt does not show

The receipt records the check’s identity, verdict, quality score and named results. It does not keep the question, the answer, the passage or Jev’s response. The check sees only the answer. It checks whether the cited passage supports the answer, not whether the answer completed the customer’s task.

Recover without starting work twice

For a Jev check without DSPy, start from Check citations with Jev.