Most AI products ask you to trust the output. A production budget doesn’t work that way — an estimate you can’t interrogate is a guess with a decimal point on it. So this page is the architecture, the decisions behind it, and the parts of it you can go and read.
screenplay-pdf-to-json and comes back as
a breakdown: scene counts, INT/EXT, day/night, the locations that recur, the cast that carry it.
No one retypes a slugline.Every AI product claims to be careful. These are the specific places we spent effort, and what each one cost us.
A change to an agent’s instructions can break it as surely as a change to its code, and it breaks silently — the output still looks like a budget. So the prompt has a regression suite.
The invariants are things true of any valid budget: tax is stored as a decimal and not a percentage, the required post sections are present, the post-to-production ratio sits in a sane band, the totals actually reconcile, confidence markers are valid. Golden fixtures hold budgets we’ve reviewed and trust. And there are negative tests that deliberately corrupt a good budget to prove each check fails on the defect it targets — because a test that never fails isn’t a test.
It runs offline in CI on every change, with no API key. After a prompt or rate-card change it runs live against real agent output.
Sending a call sheet messages forty real people. You cannot un-send it, and a wrong unit call costs a shoot day.
So the send is split. propose stages it and returns a preview — it
sends nothing. A human approves. confirm executes, and is idempotent, so a
retry can’t double-send. To any agent driving MARK these are two separate tools, which
means an unattended agent is structurally unable to message a crew in one
step. Not discouraged from it. Unable.
The synchronous budget path is bound by the browser’s edge timeout, so it runs the fast model and returns while the producer is still sitting there. The async path returns a job you poll, isn’t bound by that timeout, and so requests the stronger model.
Same prompt surface, different constraint. Most products pick one model and make the product fit it. The constraint should pick the model.
Model, latency, tokens, whether the cache hit, and what errored. Sent to a proper observability backend when one is configured, and kept in a local ring buffer plus stdout when it isn’t — so tracing degrades rather than disappears.
You cannot improve an estimate whose cost and shape you can’t see. This is also how we know which line items the agent gets wrong most often, which is what the next prompt change is aimed at.
The MCP server exposes MARK’s capabilities as typed tools, and every one of them is a thin wrapper over an existing endpoint. No logic is reimplemented at the edge.
The practical effect: a rate-card correction lands in one place, and the web app, the agent harness and anyone driving MARK from their own tooling all get it at the same moment. Duplicated logic is how two surfaces start quoting different numbers for the same shoot day.
Most tools let AI act on your behalf inside their product. MARK does that, and also works the other way: your own agent can drive it. Claude Desktop, Claude Code, Cursor or any MCP client can generate a budget, refine it in plain English, render a call sheet into your template and stage a send for approval.
If a production office already runs its own agent, MARK should be something that agent can call — not another tab someone has to remember to open.
Everything above is in a public repository. If you want to know whether the eval suite is real rather than a slide, the invariants, the golden fixtures and the negative tests are sitting there to read, and so is the architecture diagram and the MCP tool definitions.
We would rather you checked.
A creative reviews every output before it reaches a producer. The AI does the work nobody wanted to do anyway; the judgement stays with the person whose name is on the call sheet.
The working principle