Skip to content
RoutineOS
Esc
↑↓navigate↵open⌘Jpreview
On this page

People-owned and producer-owned data

Every document in the store has exactly one kind of writer. Which paths belong to people, which to producers, and why that rule replaces locking.

What it is

Every path in the store is written either by producers (routine runs) or by people (through the Desk), never both. Views and agent columns are the one shared case: a producer may create them, and from then on only people change them.

Why it exists

Page writes in the artifact database carry no version check; they are last-writer-wins. Only the run side uses if_version. If a person and a run could both write one document, a run could silently undo a person’s decision, or a person’s save could vanish under a run’s write. Giving each document one kind of writer means they never race, with no locks.

It is also the main safety rule. Settings, decisions and merges are how people steer a routine. If a run could write them, text on a fetched page could steer the routine instead.

How it works

  • desk_contract.py holds HUMAN_PREFIXES: settings/, userstate/, decisions/, merges/, data/users/. guard() refuses any planned batch with a write under them.
  • CREATE_ONLY_PREFIXES: views/, columns/. A producer may create a document there only if it is absent; any edit or delete is refused.
  • PRIVATE_PREFIXES: userstate/, data/users/. The planner never reads them.
  • On the page side, actions.js is the only file that writes, and only to PEOPLE_COLLECTIONS (settings, userstate, decisions, merges, views, columns) plus data/users/<viewer>/.

Reference

Path Writer Notes
meta/workspace, meta/definition Seed step Written once by seed, or compile for the definition
entities/<id> Producers Each producer replaces only its own bySource slice
runs/<runId> Producers
inbox/<runId>--<n> Producers
pending/<id> Producers Gates; create-only per run id
state/learning Producers Written by learn
settings/<producerId>, settings/workspace People Seed writes defaults once, only when absent
decisions/<id> People Keep, drop, accept, reject, approve
merges/<entityId> People Hand merges
userstate/<entityId> People Stars, tags, notes
views/<id>, columns/<id> Agent creates as proposed; people own every later edit
data/users/<viewer>/state/... That viewer Read, done, ack; private even from the owner
settingsHistory/<id> People Since 0.5.0; see below

Since 0.5.0: settingsHistory/ is a new people collection (every Settings save writes {producer, before, after, at, by}), and per-viewer snooze and pin state under data/users/<viewer>/state/. New people collections are added to both actions.js and HUMAN_PREFIXES.

Per-viewer state

data/users/<id>/ is private to that viewer. The page gets <id> from user.id() after declaring the user capability. Documents sit at even segment counts, so per-viewer paths carry a state segment: data/users/<viewer>/state/read/<inboxId>. If user.id() returns null, per-viewer features are off for that view; nothing falls back to a shared path or to localStorage.

Worked example

On Hiring Signals, a person opens the dropped “Graduate Data Analyst” role and chooses Keep. The Desk writes decisions/role--https-jobs-example-au-1002 {decision: "keep", at, by}. On the next run the write contract reads that decision and writes the role as status: "active", dropReason: null, with the sighting note kept by person. The run never writes the decision, and the person never writes the entity. The learning step counts the keep as evidence that title_keywords missed something.

Common pitfalls

  • Storing a person’s edit on the entity. It will be overwritten by the next run. Store it as a decision or in userstate/.
  • A routine that “remembers” by writing settings. Refused. Memory lives in state/ or on records.
  • Reading per-viewer state in a run. The planner never reads it, by design. A run cannot know who read what.

Was this page helpful?