← Chitragupta The work

Most of it happens
while you are away.

Triggers, conditions, and runs that survive the things that kill a run. Mail arrives, a time comes round, another automation finishes — and the engine knows the difference between failing and correctly declining to act.

The shape

What a run actually passes through

An automation is not a scheduled prompt. A prompt runner is one function call: a crash anywhere inside it loses everything and there is nothing to resume. This is a pipeline with durable state at every joint.

 connector / scheduler / agent / api
            │  emits
            ▼
      core/events.py          Event(id, kind, source, subject, data, trust)
            │                 ── deduplicated on (source, external_id)
            ▼
   automation/router.py       which automations care?
            │
            ▼
  automation/triggers.py      a registry, not an if/elif chain
            │
            ▼
 automation/conditions.py     deterministic first, semantic only if needed
            │
            ▼
  automation/executor.py      the driver, resumable from any state
            │
   ┌────────┴──────────────────────────────────────┐
   │ plan → approve → act → verify → retry → done  │
   └───────────────────────────────────────────────┘
Connectors emit events; they never import the engine, and the engine never imports a connector. Adding a source is a new event, not a new branch.
The state machine

Every transition is written before it is acted on

            ┌──────────────────────────── CANCELLED (user)
            │
 PENDING ──► RUNNING ──► WAITING_FOR_APPROVAL ──► EXECUTING ──► VERIFYING ──► COMPLETED
               │  │                                   │             │
               │  └────────────► BLOCKED              │             │
               │     (condition false, permission denied)           │
               │                                      ▼             ▼
               └──────────────────────────────► RETRYING ──► ESCALATED / FAILED
A process that dies between two states restarts in the earlier one, and the step ledger says which steps already completed. Recovery is resumption, not replay.
A morning brief that ran at 07:00The trigger, the condition it checked, and the run log beneath
Invariants

The rules that make a crash survivable

01

A side effect is claimed before it is caused

The idempotency claim is written and committed before the handler runs. A claim written afterwards proves nothing about a process that died in between — which is the exact case it exists for.

02

A handler that raised leaves the claim open

That means “we tried and do not know”. The retry verifies what happened rather than repeating it.

03

Success means verified

An action with a verifier that did not pass is not complete. An action with no verifier must say why — “nobody wrote a checker” and “there is no way to check this” are different answers, and reporting them identically lets the first become permanent.

04

Every run is bounded

Wall clock, actions, retries and model iterations all have ceilings. Exceeding one is BLOCKED, not an exception.

05

A run carries its lineage

An event produced by automation A's action carries A's run id. A depth limit and a self-trigger check make the A → event → A loop terminate.

06

The deadline bounds work, not waiting

An approval can take hours. Counting that against the run killed every run that waited.

Untrusted input

An email cannot redefine the job

The trigger context used to be pasted into the prompt unfenced — "NEW EMAIL(S) that just arrived:\n" + …. That is the injection surface, and the fence that closes it already existed one directory away, written for the browser.

and one morebecause
There is one permission system, and it is not in hereAutomation calls the same gate an interactive agent calls. It may never widen what you already allow.
A semantic verdict is data, never authorizationA condition returns a bool and a confidence. The gate reads neither.
Unknown actions fail closedAn automation is the caller most likely to meet an action name a model invented.
Nothing accepts an unsigned delivery from a provider that signsA provider whose secret is unset is refused with 503 rather than degrading to unauthenticated.

Want it on your Mac?

Free while it is in beta. Bring your own model — or run a local one and never leave the machine.

Join the waitlist→