ArombaDocsChangelogSecurityStatus

Docs

Aromba — Instruments and the intake

Aromba can be the hands of another product on the same machine (PLAN D207, D210). The product has the brain and the people: it knows what one person wants and holds that person’s data. Aromba has the builder: it turns what is wanted into an instrument — a page the product fills with that person’s data and shows to them — and keeps every instrument it made, so the next person who wants the same thing gets it in seconds for a fraction of a cent.

This page is the contract. A product that implements §1–§5 gets hands, and nothing in Aromba changes for it. whoopy is the first.

0. What holds, whatever a product does

1. Naming a product

aromba intake add whoopy http://127.0.0.1:3000/factory -header x-factory-key

The address must be http:// on 127.0.0.1, ::1 or localhost, with a port. Anything else is refused. The key is asked for at the terminal without being shown, and is kept in /etc/aromba/worker/intake.toml, mode 0600, the Worker’s own file. It is never an argument, never in a journal line, never in the chat and never in a prompt. -header names the header the key goes in; without it the key goes as Authorization: Bearer <key>. Naming a product asks its brief at once, so the terminal can say whether the address and the key are right. aromba intake lists the named products, and aromba intake remove <name> stops asking one.

While a product is named, the Worker asks it for work every 20 seconds. That is one request to loopback, and nothing is asked while no product is named.

2. The six endpoints

Each one sits under the address and carries the key. Every answer is JSON, 256 KB at most.

EndpointAromba sendsThe product answers
GET brief—{"brief": "…", "placeholders": ["params", "state"]}
POST claim{}a job, or {"job_id": null} when there is none
POST publishan instrument for one job (§4)anything 2xx
POST failed{"job_id", "error"}, and "detail" for a job under the owner’s ruleanything 2xx
POST spent{"job_id", "usd"}{"over": false, "spent": 0.0021}
POST incidentthe answer to an incident’s report (§ The firefighter)anything 2xx

The brief is what an instrument for this product must be: how it fills a page, what the data under each placeholder looks like, and anything else the builder must know. It is the start of every build prompt, so it is short, stable, and written for a model. It is fetched for every job, so an edit reaches the next page written (PLAN D237).

A job:

{"job_id": "J-17", "asked": "a nutrition board in my own colours", "need": "page",
 "input_schema": {"type": "object", "properties": {"palette": {"type": "array"}}},
 "example": {"state": {"meals": [{"name": "oats", "kcal": 420}]}},
 "budget_usd": 2}

A job is claimed only when it can be worked on. Aromba does not claim, and the job stays in the product’s queue, while any of these holds:

3. The instrument

One folder per instrument, in /var/lib/aromba/worker/instruments/<slug>/:

How a page gets its data. The product replaces each placeholder with that value as JSON, so the page’s script reads it as a JavaScript value:

<script>
const params = {{params}};   // the settings for one person, fitting input_schema
const state = {{state}};     // that person's data, as the product holds it
</script>

{{params}} is always there. The other placeholders are the ones the brief names. The product escapes what it puts in, so nothing in the data can close the script. Aromba’s preview does it the same way.

Files for the person (PLAN D250). A page hands the person its data as a file through arombaFile, a script Aromba puts in every page that calls it: csv, tsv, xlsx (a real workbook), text for TXT or MD, json, pdf for an A4 document of the parts the page hands it — a title, headings, text, tables and its own charts, each page drawn in the browser and kept as an image, so any script comes out as it is shown (PLAN D274, D275) — and png for an image of a card, drawn from the page’s own SVG (the element or its markup) or canvas, or taken as a PNG data URL or Blob the page drew itself (PLAN D262, D264); anything else is an error the page can show, never a quiet nothing. A page that names arombaFile at all gets the kit, and a file whose name has no extension gets its format’s (PLAN D270). The file is made in the browser from what the page was given; nothing is loaded or sent. The kit is Aromba’s, never a model’s: a copy a model wrote is replaced by it, and every page goes out — to the product, and to the owner’s preview — with the kit as it is now, so a fix to the kit reaches pages already approved; the approval stays bound to the page as kept (PLAN D264). print, the browser’s print of the page, stays for the pages that call it. The product’s frame needs allow-downloads for the files and allow-modals for print.

The firefighter (PLAN D279). A product may send, the same way, its report of an error one of its users met: need and occasion incident, its id I-<n>, with incident (its kind, api or crash, the page, the user’s last action, the request’s method, path and status, the message, the build, the browser, the time, and how many reports and people joined it), lang (the user’s language), server_log (the api’s lines for that request, and around it: other routes’ errors in the minute before, the user’s refused calls, the page’s own last calls with their answers — query strings, keys, addresses and ids taken out), recent_deploys, known (the causes already found, newest first, each {signature: {page, method, path, status, message}, verdict, cause}, so a repeat is matched on its signature), and screenshot, a JPEG or PNG data URL under 2 MB, with screenshot_masked. Aromba answers on POST incident with {"job_id", "verdict", "user_message", "team_note", "confidence", "page"}: a verdict of bug, not_ours, user_error, transient, stale_tab or unknown; one to three sentences for the user, in their language, 400 characters at most, with no link, no code, no address and no email; the cause and the fix for the team, 2,000 characters at most; how sure it is, 0 to 1; and, when a page would help that user now, the order in one sentence.

The example the writer reads is cut to the first and last items of each long list, with a note of how long each is (PLAN D251): a page is written from the shape of the data and draws any length of it. The preview is filled with the whole example.

The example names only placeholders the brief names. A job whose example holds a key the brief does not name is handed back before any model is asked, saying which: the product fills only what its brief names, so no page could read it (PLAN D272). And it is at most 128 KB: the owner’s preview carries it whole, and a job whose example is bigger is handed back the same way, saying its size. A made-up example only has to show the data’s shape, because a page draws any length of the real data: about a thousand hourly candles fit (PLAN D276).

What a page may not do, checked in code before anything is kept:

These checks catch a model’s mistakes. The product’s no-network CSP is the wall, and the owner’s review is the gate. When Aromba starts, every approved page is checked again as it would go out, and one that fails today’s page checks leaves the library for .retired/, with a journal line saying why: no ask is answered with it again, and the next like it is written anew (PLAN D268). The owner can take any page out the same way at a root terminal: aromba intake retire <page> (PLAN D273). Jobs are built three at a time: one is claimed as soon as a place is free, so a morning of a product’s users’ orders is not one queue behind a four-minute page (PLAN D277).

4. What comes back

POST publish:

{"job_id": "J-17", "slug": "nutrition-board", "title": "Nutrition board",
 "description": "The day's meals against a calorie goal, in the person's colours",
 "kind": "page", "input_schema": {…}, "template": "<!doctype html>…",
 "params": {"palette": ["#2a9d8f"], "goal_kcal": 2200},
 "reused": false, "usd": 0.0021, "seconds": 48, "model": "z-ai/glm-5.3"}

The same instrument comes back whole for every job it answers. usd is what the job cost, for the record; what is charged is spent, sent after it. model is the model that wrote the page or answered from the library (PLAN D229). added is what a next version added to the page it replaces — “a heading and a second accent colour” — sent with the job that made it and with no other (PLAN D239). params are this job’s settings, and reused says it came from the library. A product keeps one copy per slug and the params per person — otherwise every person has their own and the cache is empty. A publish that fails is sent again at the next pass. The exception is a job the product no longer has — expired while the page waited for the owner, or never known. The product answers that with 410 Gone, and the job ends and is not sent again; the instrument stays approved in the library for the next ask like it. A 400 or 422 is the product refusing what it was sent: the job ends, failed carries the first line of what the product answered, and the page stays in the library (PLAN D258). No other answer means either. A 404, which is also what a wrong key gets, is sent again once the key is right. spent follows each publish, and failed any job that will not be done, with the reason in one line. A job under the owner’s rule of no tap (PLAN D277) is one the product’s own user waits on, so its error is a sentence that user can read — the writer’s own, when it found the data this job lists cannot answer the ask at all — and the reason as it is goes beside it as detail.

5. How a job is worked

  1. An exact repeat — same product, same kind, same words once normalised (case, spaces and punctuation) — is answered as it was the last time, with no model at all.
  2. Otherwise the matcher reads the job against the library: the eight approved instruments for this product nearest by word overlap, with their schemas. Colours, a heading, a period, units, a goal are settings, never a reason for a new instrument. It answers from one (use_instrument, with this job’s settings, which must fit); extends one that does the job but whose settings cannot say what this ask wants (extend_instrument); looks at one’s page first (read_instrument); or says none of them does this job (none_fits). An extension is that page with something changed as a person looking at it would change it — a stop, a direction, a unit, a period; a page of its own that shows some of the same things — a risk card, a card to share, a file, a map — is none fits, however much of the data they share (PLAN D264). An empty library skips this step. The matcher is its own model in providers.toml (PLAN D229).
  3. An extension is the next version of that instrument: the writer gets its page and settings and adds the ones this ask needs, keeping the job, the look, the slug and every setting it has — the settings it was approved with must fit the new schema. It changes the page by exact edits, each finding a few whole lines as they are and putting the changed ones in their place, and what no edit finds stays character for character; a change to most of the page is written whole (PLAN D267). A product’s correction (revise) is made the same way. It waits beside the approved one as <slug>-next, and the approved one goes on answering. Approved, the new version takes the slug, and a product that upserts by slug gives it to everyone who has the old one. Not this removes the new version alone.
  4. Only when none fits does the writer write one (write_instrument): the page, its schema, and the settings for this ask, to a design brief — the answer first, one focal point, one memorable visual idea, charts that say their answer, the person’s own baseline, designed empty states, dark and light. The page that passes the checks in §3 goes back to the same model once more with a designer’s rubric, and the page it writes then is kept if it passes them too (PLAN D228). What fails the checks goes back with the reason, three times at most. The writer is the direction’s model at the tier the owner chose (§2, direction).
  5. A new instrument or version goes to the owner as the page — a file in the chat, filled with its settings and the made-up example — with what was asked, what it cost and how long it took, why the library did not answer when it had something near, and [Approve] [Not this]. Approve binds the approval to the page’s hash and publishes. Not this deletes it and fails the job.

The build turn is a turn of its own, not the chat’s. It has its own short prompt, the brief, the job and the library, and the tools above. None of them reads this server. The chat’s tools are not in it, so an ask written to make the model say something about the machine has nothing to say it with.

Cost. A hit is one call on the matcher — a few seconds, a fraction of a cent — or no call. A miss is two calls on the writer, the page and its designer’s pass, paid once; an extension is one. After the owner approves, the library answers that ask for everybody else. Every job keeps its model, tokens, cost and seconds, and the product is told the cost. The owner’s daily AI budget in /settings covers these turns as it covers questions, and is off unless they set it.

6. What the owner sees