Skip to content
Rigel docs
Open Rigel

Jobs

Work that needs the network or a model runs after a command commits, and its results arrive as ordinary changes.

Why a command never waits for the network#

A command is a short change to the household's database. It does no network calls, no model calls and no file reads, so it gives the same result on a device and on the household, and it answers at once. Some work can't be done that way: reading a recipe from a web page, asking a model, drawing an image, sending an email. That work is a job.

A command that needs a job records it in the same transaction as its own change. The household runs it a moment later, with retries. If the command is rejected, no job is recorded. When the job has something to say, it says it by running ordinary commands.

So the response to a command tells you the command is applied, never that the work is finished.

How a client sees the result#

A job's results are changes to records, so they reach a client the way every change does: they show up in queries, in a pull, and as a poke on the socket. What people see about the work is kept on the record itself.

Importing a recipe is the clearest case. recipes.recipe.import_url makes a recipe at once, in the importing state and titled after the site. A job then reads the page and fills in the real recipe.

A page with www in its address

Request, curl
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/recipes.recipe.import_url' \
  -H "Cookie: rigel_session=$RIGEL_SESSION" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
  -d '{
  "url": "https://www.seriouseats.com/recipes/chicken-tikka-masala"
}'
Request, TypeScript
const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/recipes.recipe.import_url", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
    Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
  },
  body: JSON.stringify({
    url: "https://www.seriouseats.com/recipes/chicken-tikka-masala",
  }),
});
const data = await response.json();
Request, HTTP
POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/recipes.recipe.import_url HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA

{
  "url": "https://www.seriouseats.com/recipes/chicken-tikka-masala"
}
Response, 200
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req_01K9D9S2R05JFVR2787F5K7Y7E

{
  "result": {
    "recipe_id": "rcp_01JC4N0000MBGE5RJ6Q7V6V6T8"
  },
  "version": 1
}

The recipe's import_state moves from importing to imported, or to failed with the reason in import_error. Read it with recipes.recipe.get, or watch the recipe row arrive over sync. Pictures come after the import and fill icon_file_id and card_file_id when they are done.

TypeScript
const base = "https://rigel.house/api/v1/households/hh_01JC8Q4W2N7XK5R3VYTZ9D6HBE";
const headers = { Cookie: `rigel_session=${process.env.RIGEL_SESSION}`, "Content-Type": "application/json" };

const started = await fetch(`${base}/commands/recipes.recipe.import_url`, {
  method: "POST",
  headers: { ...headers, "Idempotency-Key": "cmd_01JC9D1K4M7QWT2XN8VBZ5RHEA" },
  body: JSON.stringify({ url: "https://example.com/tomato-white-bean-orzo" }),
});
const { result } = await started.json();

for (;;) {
  const response = await fetch(`${base}/queries/recipes.recipe.get?recipe_id=${result.recipe_id}`, { headers });
  const { recipe } = await response.json();
  if (recipe.import_state !== "importing") {
    console.log(recipe.import_state, recipe.import_error ?? recipe.title);
    break;
  }
  await new Promise((resolve) => setTimeout(resolve, 3000));
}

Commands that start jobs#

A command's reference page says when it starts a job. These are the ones that do, by app.

CommandThe workWhat changes when it is done
recipes.recipe.import_urlReads a recipe from a web page, then draws its picturesimport_state becomes imported or failed. Title, ingredients and steps fill in, then the pictures.
recipes.recipe.create, recipes.recipe.regenerate_imagesDraws a recipe's icon and card imageicon_file_id and card_file_id are set.
meals.dish.create, meals.dish.regenerate_iconDraws a dish's icon, and drafts what to buy when the dish needs a listThe dish's icon_file_id is set, and its ingredients are added.
meals.meal.plan, meals.week.fillDraws the plate icon for dishes eaten togetherA plate icon is added for that combination of dishes.
chat.message.sendRigel's reply, when a message mentions Rigel or carries on a conversation with itA new message from Rigel appears in the same topic or thread.
inventory.product.redo_icon, and any command that makes a new productDraws the product's iconThe product's icon_file_id is set. Inventory, shopping, recipes and meals all show it.
dashboard.app_picture.redoDraws the picture behind an app's icon, or behind TodayThe place's picture is replaced when the new one is ready, in a minute or so.
shopping.item.add, shopping.item.add_many, shopping.aisle.setPicks an aisle for each new product at every store that has aislesThe product has an aisle at that store, so the list groups by aisle.
shopping.trip.finish, shopping.trip.recordPuts what was bought away in inventoryInventory items are added in their usual places, for tracked products that have one, and linked to the trip's items. An order waits until it has arrived.
maintenance.asset.add, maintenance.asset.researchLooks an item up: reads its label and unit photos and researches its model onlineThe item shows as researching until it finishes, then has what Rigel found.
documents.google.importCopies chosen Google Drive folders and files in (see outside accounts)Documents appear, and the import's document_import row shows its progress.
email.draft.sendSends the emailThe draft becomes sent, or failed with a problem.
email.message.mark with new, and every email that arrivesRigel reads the email and acts on itThe email's state moves from new to handled, no_action, suspicious or failed, with a summary.
email.domain.check, and adding a custom domainChecks the domain's DNS records and its sending verification, again every 15 minutes for 3 daysThe domain's state and each record's state update.
email.connection.set_filterReads the Gmail mail that matches the filter, again every 5 minutesNew emails appear in the inbox.
agent.routine.create, agent.trigger.create, and the commands that change themRigel does the task, at the routine's time or after the trigger's eventWhatever Rigel did: dashboard cards, notifications and changes. last_run_at is set.

Some jobs start without a command. Products with no icon and apps with no picture are queued by the household itself when it starts and after sample data is added, as long as its model is set up, so a new household fills in its pictures over the first minutes. Old versions of a document that people edited live are thinned in the background too.

Retries and failures#

A job that fails is tried again, unless trying again can't help.

  • Tries. Up to 5 by default, and some jobs allow 3. The waits between tries are 30 seconds, 2 minutes, 10 minutes and 30 minutes. A try that doesn't finish in its time limit, 10 minutes by default, counts as a failed try.
  • Nothing happens twice. A job saves each finished step, such as a fetched page or a drawn image, so a retry carries on from where it stopped. It doesn't pay for the same image twice or post the same message twice.
  • Failures that retrying can't fix, such as a page with no recipe on it or a request the model refuses, fail at once.
  • The monthly allowance. Model and image calls count against the household's monthly allowance. A job that reaches it waits until the allowance renews, at the start of the next month in UTC, and keeps its tries. A job whose call hits the model provider's own rate limit waits until that limit resets, usually seconds, and keeps its tries too.
  • One run for many requests. While a job for the same thing is waiting, asking again doesn't queue another. The job reads the current state when it runs, so one run covers all the asks.

When a job fails for good, the app says so on the record, in words for people. A failed recipe import sets import_state to failed and import_error to something like "Rigel couldn't reach that page. Check the link and try again." When Rigel can't answer in chat, it posts one short message in the same place saying so. Pictures and icons that can't be made are left empty. Ask again with the command that starts them.

Who made the changes#

The commands a job runs are recorded as made by Rigel, acting for the person whose command started the job. The household's history keeps that, along with the job that ran them. A job that syncs an outside account, such as reading Gmail, is recorded as that account instead. Queries that read the history, such as meals.week.changes and inventory.item.history, show who or what made each change.

That is why a recipe's fields can change after your command returned: Rigel's job filled them in, acting for you. The agent guide covers how Rigel's own changes behave.

Rigel is in early access, and sign-up is by invitation. Open Rigel