Skip to the content
Krizaka Blog
All posts

Orazaka

Plugging a product into an AI engine: connectors, without the magic

How Yomizaka talks to Orazaka: what goes out, what comes back, what is hard (versions, long jobs, failures, privacy), the analyst's side, what the industry does, and what we chose.

A studio patch bay, cables of different colours carefully plugged into rows of sockets, warm light

There is no call to an AI model anywhere in Yomizaka's code. Not one. Yet Yomizaka reads a lease and tells you which clauses are a problem. That is not an oversight, it is the rule I set from the start: a product never talks to a model. It talks to an engine, and the engine, Orazaka, picks the model.

The reasons are simple. A model costs money, and someone has to keep the count. A model changes: today's will not be the one in six months, and I do not want to reopen five products for that. A tenant's document should not travel just anywhere. And when someone asks "what really ran?", the answer has to be written down.

Here is how the two plug together, what was hard, and how others do it. I only describe what runs, and I say "not yet" when it does not.

What goes out, what comes back

Diagram of two boxes, the product and the engine, joined by four arrows: the product starts a Studio with its inputs; the engine answers with a run id that the product polls until it finishes; the product sends pseudonymised events; the engine returns indicators. Webhooks and progress event streams are marked « not yet »

From the product to the engine, it is an ordinary HTTPS call. Yomizaka sends the engine the text of the lease, the jurisdiction and a date, "the rules in force on". The date matters: replaying a run with the same date replays the same rules, and a lease signed in 2025 is judged by the rules of 2025. The call carries a service key that belongs to the product, which the engine's front door exchanges for a session. A key can be limited to exact paths (one method, one path, everything else refused). A revoked key stops working at once, because that exchange is deliberately not cached.

Here is the heart of Yomizaka's client, shortened. It is the only place in the product that talks to the engine.

ts
const base = "https://dev.orazaka.com/edge";
const headers = {
  authorization: `Bearer ${process.env.AI_API_KEY}`, // service key, never in the code
  "content-type": "application/json",
};

// 1. Start a run of the "compliance-check" Studio
const started = await fetch(`${base}/api/v1/studios/compliance-check/runs`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    inputs: { document: leaseText, jurisdiction: "QC", asOf: "2026-10-17" },
  }),
  signal: AbortSignal.timeout(20_000),
});
const run = runSchema.parse(await started.json()); // refused if the shape is not the expected one

// 2. Keep the id and the Studio's version, then poll later
await save({ runId: run.id, blueprintVersion: run.blueprintVersion });
const current = await fetch(`${base}/api/v1/studios/runs/${run.id}`, { headers });

From the engine back to the product, the answer comes straight away with a run id and a status. The product keeps the id, shows it on screen, then asks for the run again until it is finished. On dev, one lease check recorded for our site took about four seconds: three steps, only one of which calls a model.

What comes back is read with care. The product checks the report's shape with a schema, and a shape it does not recognise becomes "nothing", never a guess.

Two things are not there, and it is better to say so. The engine does not push its events yet: no progress stream, no signed webhook to say "it's done". That is an open request (orazaka#189), so the product polls. And the call that starts a run has no idempotency key yet: if the network drops after the engine accepted, a run can exist without the product knowing. Yomizaka protects itself from a double click (one analysis at a time per document and Studio), not from that.

Finally, Studios are data. A Studio is a file that declares its inputs in a schema. When a field is a choice of model, it does not hold the list: it says "one of the catalogue's models for this kind" (an x-options key) and the engine fills the menu. Adding a model in the database makes it appear in the forms with no new release. Adding a jurisdiction is a rules file, not a line of engine code.

What is hard

Versions. A Studio has versions. When a product installs a Studio, it pins the newest one, and moving up is never automatic. Each Yomizaka analysis keeps the version that produced it. That is the server-to-server side; for mobile apps, a minimum version and forced update rule is still a proposal (.github#75).

Long jobs. A lease check takes four seconds, a video takes minutes. Polling holds for one and not the other: while seeding Orochia's database, polling about 360 runs per pass would have exceeded the key's quota. That is the reason for the stream and webhook request.

Failures, and who pays. I learned this one the hard way. The compliance check had a model judgment step, set to carry on if the model went down, to keep a report that rules had almost entirely computed. The flaw: a deliberate refusal from the model looked exactly like an outage, and was swallowed into a report the user would believe complete. Since then a failure says why (ADR-053), and the billing rule fits in one sentence: we bill for work done correctly, never for our own failure. An engine fault gives the credits back. On the product side, every refusal becomes a stable code, and Orochia's client stops calling for a minute after three failures in a row: AI is an addition, never a dependency. What a missed result costs, and who pays for it, I cover in the real cost of an image.

Privacy. What leaves the product is the text of the lease (or a PDF of at most 1 MB for authenticity). Yomizaka keeps the document at home and keeps, for each analysis, only the parameters, not the text. On the engine side, the compliance Studio is classed "sensitive", and the retention follows that class down to the jobs (ADR-065). The exact duration on the dev engine I did not measure, so I do not write it.

Testing across repositories. What breaks most slyly is what changes on one side without the other knowing. We have met the dumb version: a component update that desynchronises the lockfile of the whole workspace (orazaka#64). For the API, a recorded contract file is kept in two copies, on the engine side and on Orochia's; each has tests against that file, and their fingerprints are identical today. No test compares the two copies yet: it is a convention. What protects us more is the engine's pipeline, which clones every repository and builds the whole at once, nightly too. And Yomizaka refuses to build without its recorded runs.

Seeing what ran. Every run has an id, a state per step with its number of attempts, and the Studio's version. Yomizaka's screen shows the id, and the engine's request log is kept thirty days.

The analyst's side

A product does not only need to have documents read, it wants to know what its people do. With us that goes through the same engine (ADR-081). The product sends events, and never a user id: it sends a pseudonym, an HMAC fingerprint computed with a key only it holds. The engine cannot undo it.

json
{
  "events": [
    { "id": "evt-8c1f", "kind": "view", "user": "3f1d2a9c…", "item": "video-12", "value": 80, "at": "2026-10-17T12:00:00Z" }
  ]
}

Events go in batches (500 at most), the engine ignores those it already received (same id), and the product writes them to a local queue in the same transaction as the action, so nothing is lost. In return the analyst gets figures computed on read: indicators with the previous period, a daily series, the most liked items, weekly cohorts, funnels of two to six steps. No figure is returned for fewer than three different people (k ≥ 3): it is marked suppressed, not shown small. Events older than 180 days are erased, and a pseudonym can be erased on request.

So what the analyst can ask is limited, on purpose: those five shapes, over a period of at most a year, for the product itself. No free SQL, never two products crossed. For the rest, a Studio in the same pack takes a pasted CSV or JSON (20,000 characters) and a question, then answers in text and a dashboard. The model never writes code: it describes the interface as data (A2UI, version 0.9) with a closed catalogue of components, and the client validates it before drawing it (ADR-082). Honestly here too: in the recording we made, the default model mislabelled some days and wrote "5x" for a ratio of 15. The renderer checks shapes, not truth.

Yomizaka does not send events yet. Orochia is the first to.

How the industry does it

There are seven answers, and each is good somewhere.

ApproachGood forWhere it hurtsWith us
REST API described in OpenAPI, SDKsA clear, typed, well-tooled callThe SDK ages, and has to be kept current in every languageYes: a small client per product, no shared SDK
Webhooks (Stripe)Being told without pollingDuplicates, no guaranteed order, signature to verify, retries for daysNot yet for runs
Event buses (CloudEvents, AsyncAPI)Several subscribers, decouplingOne more component to runAn internal broker between services, never exposed
iPaaS (Zapier, Make, n8n)Plugging fast, without a developerLogic hides in boxes nobody versionsNo
ELT connectors (Airbyte, Fivetran)Copying tables to a warehouseIt moves data, it does not answer a questionNo
MCPGiving a model tools and contextEvery tool is code that runs: consent and trust to manageOrazaka is an MCP client
OAuth 2.0Acting for a user, with their permissionTokens to renew, consent to explainFor people signing in; products have service keys

A word on MCP, which everyone talks about. It is a protocol (JSON-RPC 2.0 messages) where a server offers tools, resources and prompt templates, and a client plugs them into a model. The specification itself says tools are arbitrary code execution and that their descriptions are not to be trusted unless the server is. With us, Orazaka is an MCP client: before a conversation turn it queries the servers an administrator declared, and the user's own. In the code and documentation I reread, I found no MCP server that would expose our Studios: a product calls the engine through the API above.

How we do it, and why

We chose a plain HTTPS API, with a service key that can only do what was declared for it, inputs described by a schema, answers checked at the product's border, and polling to wait. It is boring, and on purpose: any product can plug into it with a fetch.

We refused to let the product call a model, for the four reasons at the start. We refused, for now, webhooks and streams: they have to be designed properly (signatures, retries, duplicates), and the request is open. We refused a shared SDK between repositories: the engine copies the shape of an answer instead of depending on a common library, because two repositories tied by a dependency deploy together, and we want to deploy them apart. We refused automation tools and table-copy connectors, because none is built for "run this Studio and give me the proof".

What it costs, I know. Polling is noisy and slows long jobs. A copied shape can drift without anyone seeing, hence the tests on both sides, and the comparison test I have not written yet. And without an idempotency key, a cut at the wrong moment leaves an orphan run. If you plug a product into an engine, write down first what you will not do, and check every answer at the door. The rest can be added.

Keep it, pass it on

Related posts

Costs · 8 min read

The real cost of an image: paying for intent, not execution

A friend pointed out that I measured runs, not results. The cost of an accepted result, the workflow loop, who pays for it, and my measured numbers.

Measured

Orazaka · 6 min read

Orazaka, from one Mac to anywhere: making a project portable

At first, Orazaka only ran on my laptop. How it became services, containers, model routing in a database and GPUs rented by the hour, the options weighed at each step, and what stays hard.

Building Orazaka

AI · 5 min read

Agentic, agnostic: getting out of vibe coding without fooling yourself

What really makes a system agentic, what an "agnostic" engine buys and what it costs, and how I went from vibe coding to agents held by rules, tests and CI.

The basics