worklists.dev

Tasks on EPCIS 2.0

A task closes with an event,
not a checkbox.

worklists.dev assigns work to a person or an agent and closes it with the scan that proves it happened. complete takes the hash of an EPCIS event, never a boolean, so the record of work done is the evidence itself. It ships as a package, and every verb that emits is free.

import { complete } from "worklists.dev"complete(record, {
  "taskId": "T-4410",
  "tenant": "acme-dc",
  "artifact": {
    "kind": "event-hash",
    "digest": "ni:///sha-256;43af6a48762684e0703a3a435d07c6bbe458891792287088b47376ca5f856371"
  },
  "by": {
    "kind": "principal",
    "id": "badge:4471",
    "tier": "human"
  },
  "at": "2026-09-08T06:12:00Z",
  "actionId": "act-0003",
  "seq": 2,
  "ns": "acme-dc"
})

completed the closing act appended; the capture intent goes to epcis.dev.capture

{
  "record": {
    "actionId": "act-0003",
    "taskId": "T-4410",
    "tenant": "acme-dc",
    "tier": "human",
    "verb": "completed",
    "at": "2026-09-08T06:12:00Z",
    "seq": 2,
    "by": {
      "kind": "principal",
      "id": "badge:4471",
      "tier": "human"
    },
    "artifact": {
      "kind": "event-hash",
      "digest": "ni:///sha-256;43af6a4876268376ca5f856371"
    }
  },
  "captureIntent": {
    "what": {
      "verb": "gs1.org.ai:completing",
      "object": "T-4410"
    },
    "who": "badge:4471",
    "where": {
      "ns": "acme-dc"
    },
    "when": "2026-09-08T06:12:00.000Z",
    "why": {
      "reason": "worklists.dev.complete",
      "cause": "ni:///sha-256;43af6a4876268376ca5f856371"
    },
    "how": {
      "method": "worklists.dev.complete",
      "details": {
        "taskId": "T-4410",
        "actionId": "act-0003"
      }
    }
  },
  "captureToolId": "epcis.dev.capture"
}
import { complete } from "worklists.dev"complete(record, {
  "taskId": "T-4410",
  "tenant": "acme-dc",
  "artifact": true,
  "by": {
    "kind": "principal",
    "id": "badge:4471",
    "tier": "human"
  },
  "at": "2026-09-08T06:12:00Z",
  "actionId": "act-0003",
  "seq": 2,
  "ns": "acme-dc"
})

422 complete takes an event hash, not an acknowledgement

{
  "type": "https://worklists.dev/errorence-required",
  "title": "complete takes an event hasknowledgement",
  "status": 422,
  "detail": "complete takes the CBV §8.9nowledgement.",
  "instance": "/v1/tasks/T-4410/complete",
  "door": "worklists.dev",
  "verb": "complete",
  "retryable": false,
  "costed": false
}
import { query } from "worklists.dev"query(record, {
  "status": [
    "settled"
  ]
}, me)

1 row status is a fold over the task's own acts, never a column

{
  "items": [
    {
      "taskId": "T-4410",
      "tenant": "acme-dc",
      "status": "settled",
      "assignee": {
        "roleKind": "person",
        "ref": "badge:4471"
      },
      "openedAt": "2026-09-08T06:02:00Z",
      "lastActAt": "2026-09-08T06:12:00Z",
      "artifact": {
        "kind": "event-hash",
        "digest": "ni:///sha-256;43af6a4876268376ca5f856371"
      },
      "actCount": 3,
      "actionClass": "work"
    }
  ],
  "snapshot": "wl-snap-3"
}

One pallet, five stations, five closing events.

A pallet of bagged lettuce comes off the trailer at dock door 1 and a BOPIS order leaves at the curb. Under every screen is one append-only record: a demand is dispatched into tasks, a performer claims, and the scan that closes each task is the event whose hash complete takes. Status is never written; it is the fold over these acts.

01 receiving

Dock door 1

An ASN dispatches the receiving task; nobody typed it. Badge 4471 claims it at the door, and the pallet label scan is the event whose hash closes it. The dock counted 47 of 48, and a short receipt is an exception, so an exception is a demand: a recount task is dispatched from it.

06:02  dispatched  ← ASN 856 #556
06:09  claimed     badge:4471
06:12  settled     ni:…;2f6c…a91e
06:12
WorklistAcme DC

Receiving

Task

  • RefT-4410
  • Subjectsscc (00) 0 0614141 123456789 0
  • Atdock door 1
  • Dispatched byASN 856 #556 · 48 cases

Evidence

  • Closes withpallet label scan at dock door 1
  • Event hashni:///sha-256;2f6c…a91e
  • GradeAttested
Settled06:12
06:41
TaskAcme DC

Scan

Read

  • SymbologyGS1 DataBar Expanded
  • Payload0100614141107346102211
  • GTIN00614141107346
  • Lot2211

Attestation

  • Whobadge:4471
  • Captured byid.org.ai/code/acme-dc-app
  • Event hashAttestingni:///sha-256;8b21…c04d
  • GradeNoneAttested
Attesting the read
Attested06:41

02 storing

Aisle 7, bay 3

The putaway plan dispatches the shelf task. The case scan at the bay carries the GTIN and the lot; the attestation under it carries who scanned and what captured it. That scan is the closing evidence, so there is nothing else to press.

06:14  dispatched  ← putaway plan
06:39  claimed     badge:4471
06:41  settled     shelf + case scan

03 picking

Pick cart 12

A BOPIS order dispatches five picks and a pack. The worklist on the cart is a query over those tasks, and the state on every row is a fold over that task's own acts. There is no status column anywhere to update.

09:58  dispatched  ← BOPIS-88214
       5 lines → 5 picks + 1 pack
10:01  claimed     badge:4471
10:07  settled     T-5102 · lot 2211
10:07
Acme DC

Worklist

3pending at pick cart 12Mandated
  • T-5102Picking · aisle 7, bay 3 Settled
  • T-5103Picking · aisle 7, bay 5 Settled
  • T-5104Picking · aisle 9, bay 1 Claimed
  • T-5105Picking · aisle 12, bay 2 Pending
  • T-5106Picking · cooler 2 Pending
  • T-5107Packing · pack station 2 Pending
Claim next
10:26
WorklistAcme DC

Packing

Task

  • RefT-5107
  • Subjectorder BOPIS-88214 · tote 0419
  • Atpack station 2
  • Dispatched byorder BOPIS-88214

Evidence

  • Closes withtote label scan after the last line
  • Event hashConfirmingni:///sha-256;5d0a…e3f7
  • GradeNoneAttested
Confirming the scan
Settled10:26

04 packing

Pack station 2

The tote label scan after the last line closes the pack task. Until the event's hash lands, the evidence reads as confirming and the task stays open. A pressed button would have closed it; a hash is what closes it.

10:24  claimed     badge:4471
10:26  scanned     tote 0419
       presented = unconfirmed
       until the hash lands

05 staging outbound

Stall 4 · curbside

Check-in at stall 4 dispatches the handoff. The order code scan at the car settles it, and the record of work done and the evidence it happened are the same object. Trace the pallet back through all five and every hop names its task.

11:46  dispatched  ← check-in, stall 4
11:47  claimed     badge:4471
11:48  settled     ni:…;c77e…1b30
11:48
WorklistAcme DC

Staging outbound

Task

  • RefT-5108
  • Subjectorder BOPIS-88214 · tote 0419
  • Atstall 4
  • Dispatched byorder BOPIS-88214 · customer arrived

Evidence

  • Closes withorder code scan at the car
  • Event hashni:///sha-256;c77e…1b30
  • GradeAttested
Settled11:48

The verbs.

Five verbs in the package, one append-only record. Every verb appends an act; nothing edits one. The status you read back is the fold over those acts.

groupverbswhat it does
Assignclaimclaim is the pull, and a pull is not management. The performer takes the next task from the station's pool, and the record shows who did.
Performcomplete, timeoutcomplete takes an event hash, not a boolean: a task closes with the evidence it happened. A timeout is a state the record enters by an alarm, never a silence.
Readquery, traceA worklist is a query, never a store: status is a fold over a task's own acts. trace walks one task's lineage down to the events that satisfied it.

Seven laws, and the door refuses the rest.

  1. 01

    A task is the causal parent of its events.

    The record reads this was scanned because this task said to. With the action verb supplying the step and the badge supplying the performer, a complete event composes from scans with no application in the loop.

  2. 02

    complete takes an event hash, not a boolean.

    Closing a task means presenting an attested capture — a real who, at a real place, inside the window — so the record of work done is the same object as the evidence it happened.

  3. 03

    A worklist is a query, never a store.

    Status is a fold over a task's own acts. There is no status column to update, no reopen, no update; the record is append-only and the fold is the truth.

  4. 04

    Worklists never invents demand; it converts it.

    Every task descends from a demand this door can name — a document, an exception, a plan, a schedule. A task with no parent demand is refused.

  5. 05

    Object to performer is the product. Performer to objects is refused.

    Trace a bag of lettuce back to the hands that moved it, always. List what one person did across the floor — never, on an employer's instance. A scope with no cited authority does not get the metric, and a rate is never computed.

  6. 06

    The same task goes to a human or to an agent.

    The door does not change shape. An approval is a task; a pick is a task; the agent that drafts the count and the supervisor who attests it append to the same record.

  7. 07

    Emitting is never paywalled.

    You can always record what you did. The free/paid line, when it publishes, sits on dispatch volume and on the managed instance — never on the act of closing a task with evidence.

Two operators. Two records. Non-fungible.

The package enforces the line the product is named for. query answers who touched this object on any instance. It refuses what did this person touch on an employer's instance, with a typed problem a caller can branch on. A regime called discretionary is refused outright; the two that exist are the two below.

  • An employer's instance

    Its record answers the employer's questions: object to performer, always. Who touched this pallet, at which station, inside which window. Never what one person did across the floor, and a rate is never computed.

  • A worker-held instance

    A union, a trade school, a trade association runs its own. That record belongs to the worker: it counts toward no employer's metric and is visible to no employer. Which regime you are in is legible on the device.

Who this is for.

  • Platform teams

    You own the WMS or ERP integration and want the task to be the reason on the event. The verbs take your demand as it arrives, and the closing event lands in your own event layer.

  • Software vendors

    Your approvals screen is faking a status column. Here a task's status is a fold over its acts and complete takes a hash, so the screen stops guessing.

  • Agents

    You arrive with a worklist to work and a badge to present. The same task goes to you or to a person, and the door does not change shape. An approval is a task.

Get started.

Say what you will dispatch. Access opens the verbs against your own demand, the closing event validated at epcis.dev, and the frontline app under your own name.