Model
- A definition stores a versioned JSON state machine under a stable slug.
- A run binds one definition version to a document and current state.
- A task asks one user or resolved role to choose from declared options.
- A transition records the previous state, next state, trigger, actor, and time.
running, done, failed, or cancelled. Tasks are open,
claimed, resolved, or expired.
Definition
user:<id>, document_owner, or a registered role:<name> resolver.
document_owner resolves when a document-bound run creates its task. The
shipped role:admin resolver assigns a shared task visible to administrators.
Built-in state kinds are:
system: emits a routing event without creating a taskapprove: creates a human task and waits for a choice or timeoutend: completes the run and expires remaining tasks
Engine.RegisterHandler.
Handlers can return a transactional effect with their routing event. Suchi
runs that effect in the same transaction as the transition, which keeps a
human-approved write retry-safe when the durable job is redelivered.
API flow
Register the example under
invoice-approval from an administrator’s browser
or OIDC session:
$TOKEN needs documents:write and change access to document 17.
The assignee resolves the resulting task from the Approvals screen or API. A
resolution enqueues the next advancement; the HTTP request does not run the
whole state machine inline.
Timeouts and recovery
Entering a state withtimeout_sec records a deadline. A recurring durable
timeout sweep enqueues a timeout advancement for due runs and expires their
open tasks. The definition must map timeout when timeout behavior is desired.
Interrupted jobs are reclaimed on startup. Each queued advancement and task is
bound to one visit to a state; stale retries cannot replay a choice on a later
visit or recreate a resolved task. Effects and task creation recheck that revision
inside the writer transaction. Cancellation records a transition and expires tasks;
accepted decisions clear their deadline before a later timeout sweep can act.
Accepting a decision or timeout increments the run revision immediately, so delayed
entry work cannot create another task or apply an effect while advancement waits.
The beta.2 upgrade preserves pending decisions only when retained entry jobs and
the resolved task prove their state. Initial-state timeouts can resume; later-state
timeouts have ambiguous origins in the old implementation. Missing entry history
or ambiguous events leave affected jobs dead with a recovery message, preserving
the current review. Inspect the run and resolve a current task, or cancel and
restart a stalled run. Retrying an unbound old job cannot authorize it.
Run and task IDs are retained and no longer reused after deletion. The run allocator
also skips IDs retained in old jobs. Purged task IDs without pre-upgrade allocation
history cannot be reconstructed.
Approval jobs appear in /api/tasks/; filter by kind=approval: when
investigating a stalled run. Application logs use component=approvals.
Document suggestions
Archive matching and low-confidence LLM title classification use the shippeddocument-change definition. Each suggestion becomes an approval assigned to
the document owner with its field, proposed value, confidence, source, and
supporting document IDs stored in the run variables. Applying a suggestion
changes the document through the approval transition; rejecting it only closes
the run. Repeated classifiers do not open a duplicate active review for the
same document, field, and value.
Classification decisions are independent per field. A confident category can
file a document immediately while lower-confidence correspondent or tag
suggestions remain in Approvals. The Approvals screen shows the current filing
category alongside the exact proposed action so those reviews are not mistaken
for a second filing decision.
If a pending suggestion becomes true before it is reviewed, the existing
approval sweep closes it as system:satisfied and records
document.suggestion_satisfied in the audit log. It does not linger as an
actionable approval or disappear without history.
If a document has instead been filed into a different non-Inbox category, the
sweep dismisses its obsolete filing proposal as system:superseded, with a
document.suggestion_superseded audit event. Its existing filing is unchanged.
Inbox documents keep their pending filing proposals. The sweep runs every 30
seconds through the job queue; an ingestion backlog can delay it.
This remains the extension pattern for future action workflows: a producer
starts an approval in its existing transaction, and a typed handler performs
the approved write. Reviewable extracted facts use the deliberately separate
document_intelligence ledger because they are bulk metadata decisions, not
state-machine runs.
Review dates
Start with Document dates for the complete user workflow. The Approvals screen hosts pendingdocument_intelligence candidates. Selection
works per date, source document, or visible result set.
Dates and workflow approvals share the same card grid. More than two groups use
two desktop columns and one mobile column; one or two groups stay in a compact
single column. The selected-date action bar stays above the list while
scrolling. One bounded API call adds or rejects the selected IDs.
This review does not create approval definitions, runs, transitions, or one
task row per date. See Document dates: Automatic and reviewed
dates for the threshold, review,
and rescan rules.
Rescan proposals
Suchi ships arescan-proposal definition. When a code, extraction schema, or
prompt version makes stored processing results stale, Suchi creates one admin
review task per affected pipeline kind. The choices are:
- rescan every stale document
- rescan a random sample of 20 when more than 20 documents match
- dismiss the proposal
Current limits
- A run has one current state; parallel branches are not supported.
- Definitions are managed as JSON through the API rather than a visual editor or watched directory.
- A
role:admintask is shared by all administrators until one resolves it; assignment is not round-robin.