Skip to main content
Automations are the deterministic “when X, do Y” layer of suchi. They are configuration, not code — one row in workflows, one or more workflow_triggers, one or more workflow_actions. Every operator can add or edit them via /api/automations/ (or the admin UI in a follow-up); no Go change needed. Automations sit next to two other engines with distinct jobs, and the three are easy to mix up if you skim: If you’re just tagging documents by keyword, use rules — they were built for that and they run on every post-ingest. If you need to trigger multiple metadata changes together (assign owner + assign tags + set storage_path when a doc with correspondent “Landlord” lands), use automations. If you need a person to click Approve, use approvals.

1. Concepts

Three tables (see core/db/migrations/0020_automations.sql):
  • workflows(id, name, order_index, enabled, ...) — one row per automation. enabled=0 skips evaluation entirely.
  • workflow_triggers(workflow_id, type, filter_*) — one or more per automation. Every non-null filter must match for the trigger to fire.
  • workflow_actions(workflow_id, order_index, kind, params_json) — one or more per automation. Actions run in order_index order inside a single write transaction.
An automation is a set of triggers × actions: when any matching trigger fires, every action runs. Actions that fail log a warning and are skipped; the rest still run.

2. Triggers

Three trigger types. Each has an integer wire code (matches the mobile-app shape) and a semantic name: Every trigger row supports these optional filters. Non-null fields are AND-combined; a null field means “match anything”.

3. Actions

Every action carries a kind and a small JSON params bag. Actions are idempotent — re-running the same automation on the same doc converges rather than duplicating.

Built-in: “Auto-file from archive”

suchi ships one system automation on first boot: Auto-file from archive (system_slug auto_file_from_archive). It’s an apply_from_similar action on document_added — the archive-based counterpart to the LLM classifier.
  • Toggleable — flip enabled off in /automations if you prefer manual filing.
  • Editable — tune every threshold and the field set through the visual builder or the JSON view. The action’s own defaults are the source of truth; leaving a param blank falls back to them.
  • Undeletable — the SPA hides the trash button; DELETE /api/automations/{id} returns 409 system_automation. Toggling it off is the substitute.
The “system” concept is data-driven (a system=1 column on workflows) — future built-ins ship the same way. The seeder is idempotent by system_slug, so re-runs and upgrades are no-ops.

4. REST surface

Admin-only writes; any authed user can list/get.

4.1 Example — auto-tag Landlord docs and route to a folder

4.2 Example — title template on any invoice from Acme

4.3 Trigger type as string

The wire accepts either the integer code (mobile-compat) or the enum string form. The following are equivalent:

5. Evaluation model

  • document_added — called from postingest.postContentSteps after the rules classifier runs and content is loaded. Each matching automation runs its actions in one write tx per automation. A failure in an action logs and moves on.
  • document_updated — called from PATCH /api/documents/{id} after the doc’s fields land. document_updated triggers only fire on successful writes. Automations do NOT re-trigger on their own writes (see §7).
  • consumption — fires at the start of postingest.Handle, before any content extraction runs. Producers plumb filter context onto the job payload:
    • POST /api/documents/filename (multipart Filename)
    • fs-watch → filename + source_path (absolute path)
    • mail-intake → filename (subject line as proxy; mail_rule_id stays zero until Phase 6 adds mail-rules)
Automations do not chain. An automation that runs assign_document_type does not re-trigger document_updated on that same doc — otherwise you’d need a cycle detector. If you need one automation to depend on another, put both actions in the same automation.

6. Interaction with rules

Rules run first inside postingest. Automations see the post-rules metadata. This ordering is intentional: rules do the light classification (add_tag, set_correspondent by name-match), then automations run richer multi-step orchestration that can rely on those classifications being in place.

7. Audit and observability

Every action logs at INFO under automations.action.{ok|error} with the doc_id and action kind. Errors surface at WARN. See privacy — automation runs are visible in the audit log.

8. Not (yet) covered

  • No condition DSL / expression language. filter_content_matching is a case-insensitive regex; nothing more sophisticated. If you need arbitrary boolean logic, write two automations.
  • No user notifications on match — surface via the audit log or wire through a Subscriber plugin.
  • No scheduled/time-based triggers. Everything is event-driven.
Roadmap is in the plan doc under Phase 5. See approvals for the human-in-the-loop counterpart.