Clocks
A clock is a date the world sets, beside the job's own deadline. It names who answers for it, rings rungs that give notice, escalate, ask for an acknowledgement and run a step through the gate, moves when an extension commits or never moves at all, can be one date per item, prices a miss, and never lapses silently.
clocks answers one question: which dates does the world set for this work, and who answers for each one?
One line on the Translation from A date the world sets. The registry takes the filing until a date the buyer names, and the buyer answers for it. Three days out, the clock asks for more time; at the date, unfiled, the buyer chooses.
file: { due: job => job.input.file_by, owner: 'buyer', rungs: ['run extend 3d'], moves: { extend: '14d after due' }, lapse: ['file', 'withdraw, in writing'] },It opens with the job, and every surface reads it:
$ npx services.dev clocks get clk_7Hq2 --json
"name": "file",
$ curl http://localhost:8787/clocks/clk_7Hq2
"name": "file",
→ tools/call get_clock {"id":"clk_7Hq2"}
"name": "file",
await lingua.clocks.get("clk_7Hq2")
"name": "file",
On the Service
| Key | Written | Left out | Does |
|---|---|---|---|
clocks | { <name>: Clock } | none | Dates the world sets, by name: when each is due, who answers for it, the rungs that notice, escalate, ask for an acknowledgement and run a step, the steps that move it, and what the owner chooses at the end. The rungs, moves, every and on run, and the clock's own calls acknowledge, satisfy, choose and name its owner. |
calendar | Calendar | the brand's | This Service's own calendar, in place of the brand's: its zone, closed days, closing minute and roll. |
Each clock
| Key | Written | Left out | Does |
|---|---|---|---|
due | ((job) => string | undefined) | '${ClockSpan} after ${string}' | required | The date: a function of the job returning a civil date ('2027-04-15', closing at the calendar's minute in its zone, rolled past closed days) or an instant with its time, or nothing until the job has it; or a span after a step, an input or output date, an event, or another clock: '365d after file', '3mo after issued'. With each, the function takes (job, item). It reads only the job, never the wall clock or chance. A function that throws, or returns what isn't a date, records clock.unopened and check fails it as clock_due_unreadable. |
each | { of: <input or output list> } | none | One occurrence per item, mirroring the step's each: clk_…#US-NY, moved and satisfied only by its own item's receipt. |
owner | RoleWord | 'brand' | 'buyer' | 'of_record' | required | Who answers for it: a role, the brand, the buyer, or of_record, the member of record on the subject. One that resolves to no one leaves owner null, and the owner console alarms. |
rungs | ('notice ${number}d' | 'escalate ${number}d' | 'ack daily ${number}d' | 'run ${string} ${number}d')[] | none | Before due, in civil days, each ringing at the calendar's rings_at: notice 30d and escalate 3d tell the owner; ack daily 7d asks for an acknowledgement on each of the last 7 local days, and records each one missed; run extend 3d proposes the routed step extend through the gate 3 days out, once per occurrence; 'run extend 3d, again on move' runs it after each move too. A proposal the gate holds or refuses stays armed, proposes again on each change to the record and each day at rings_at, and escalates to the owner at once. |
A clock is a date a registry, a regulator or a contract set, beside your own deadline. It holds whether or not anyone works, and can outlive the job on its subject.
When two dates move differently, they are two clocks. A step that moves names moves only its own clock's date, counted from the date before any roll, and a step satisfied_by names closes it with no one acting. After a move:
| Rung | The runtime |
|---|---|
| notice, escalate, ack | each one still ahead is superseded, its superseded_by naming a successor armed for the new date at @1: the extended return hears notice 30d on 15 September |
run | keeps its key, which carries no @, so it runs once per occurrence and no second extension is filed; 'run extend 3d, again on move' runs after each move |
a due function | runs again only after a commit changes what it read; a new answer records clock.moved { by: 'due' }, so a replay never calls it |
$ npx services.dev tick 6mo --trace
tc_7Hq2 · 2027-04-15 12:00:00Z → 2027-10-15 12:00:00Z · UTC · 2 fired
pass owed fired place kind on what
1 2027-04-15 23:59:00Z 2027-04-15 23:59:00Z 6 clock clk_7Hq3 pay · due 2027-04-15, unsatisfied · waits on the buyer's choice
each: { of: 'jurisdictions' } gives one occurrence per item, each moved or satisfied only by its own receipt. On 11 July the 2026 return's federal, California and New Jersey dates close; New York's waits for its repair:
$ npx services.dev clocks list --on job_4Tx3 --test-clock tc_7Hq4
job_4Tx3 · annual-return · period 2026 · at 2027-07-12 09:00:00 · 7 clocks
id name item status due moved from by
clk_7Hq9#US file US satisfied 2027-10-15 2027-04-15 file · accepted 2027-07-11
clk_7Hq9#US-CA file US-CA satisfied 2027-11-15 2027-04-15 file · accepted 2027-07-11
clk_7Hq9#US-NJ file US-NJ satisfied 2027-11-15 2027-05-17 file · accepted 2027-07-11
Some dates never move: a call that closes, a time to pay, the last day a claim can be brought. hard: true says so, and check refuses a hard clock with moves. A grant application's call closes with hard: { bars: ['lodge'] }; the organisation signs a day late, so the gate refuses to lodge, and a charge waiting on lodge's receipt never releases:
$ npx services.dev tick 6d --test-clock tc_7Hq6
tc_7Hq6 · 2026-11-26 09:00:00 → 2026-12-02 09:00:00 · 8 fired · 2 acts
2026-11-28 00:00:00 job_7Hq2 clock closes · rung escalate 2d · owner buyer · the owner console lists it
2026-11-30 23:59:00 job_7Hq2 clock closes · due 2026-11-30 · hard · choose: withdraw, in writing · owner buyer
| A hard date with | Once it passes, unsatisfied |
|---|---|
bars: ['lodge'] | the gate refuses those steps with 409 date_passed |
bars: [{ step: 'amended-return.file', when: claimsRefund }] | a step of another Service on the subject is barred too, only while when holds: an amendment that claims a refund is refused at once, nothing held, and one that owes more tax files |
no bars | it stays open: its exposure grows, and its owner chooses |
| a postponement | the calendar moves it, the one thing that can |
apis.tax's refund clock closes three years after filing, and bars an amendment only when it claims money back:
$ npx services.dev jobs again job_4Tx2 --service amended-return change="a second credit" --test-clock tc_7Hq3
× 409 date_passed · amended-return.file is barred: refund on job_4Tx2 closed 2029-04-17, and the amendment claims a refund (claimsRefund) · nothing held
| Key | apis.tax writes | The runtime |
|---|---|---|
exposure | exposure: lateCost, half a percent of the unpaid tax a month | prints "about $324 a month if it's late" in each notice, sorts the console's dates at risk, and shows beside the owner's choice |
guarantee | { covers: ['penalties', 'interest on them'], up_to: 5_000, after: 'notice-response', excludes: 'held' } | adds "or we pay the penalty and its interest, up to $5,000, after we ask the IRS to take it off" to the price sentence, and pays what the IRS keeps from your line, guarantee.claimed, AI and members' pay whole |
A clock is operable, not just an alarm. Its owner acknowledges, satisfies and chooses. Anyone else gets the owner's app as the next step, and a choice waits for the date:
$ npx services.dev clocks ack clk_7Hq3
lodge is the brand's, at its console.
next hand http://localhost:8787/console to the person it asks
docs http://localhost:8787/docs/errors/needs_the_owner
Each call names the occurrence it means by its due, so a call for a date the clock has moved past answers clock_moved_on, with the call for the one it is at.
At the date, a step the options name runs, through the gate:
$ npx services.dev clocks choose clk_7Hq2 renew_late
clk_7Hq2 · expires · due 2026-11-30 · buyer · chosen · chose renew_lateThe brand closes its own date the same way, on evidence on the record.
A clock whose owner resolves to no one has owner: null, and the owner console alarms until someone answers for it, even between jobs. assign takes only a member who holds the role's credential: Dates at risk runs one.
Rungs count back from the date, each ringing at the calendar's rings_at on its day, in the clock's place in the tick. At the date, unsatisfied, the clock waits on its owner's choice. Nothing lapses silently:
| On its day | The runtime | Event | Who hears |
|---|---|---|---|
notice 30d | tells the owner, with the date and the ways to keep it | clock.noticed | the owner, in their app |
escalate 3d | tells the owner again and lists the clock on the owner console | clock.escalated | the owner and the console |
ack daily 7d | asks for the day's acknowledgement, and records it missed at the day's close | clock.ack_due, clock.ack_missed | the owner |
run extend 3d | proposes the routed step through the gate, which commits it like any other | clock.ran | the record |
$ npx services.dev tick 7d --trace
tc_7Hq2 · 2026-11-20 12:00:00Z → 2026-11-27 12:00:00Z · UTC · 1 fired
pass owed fired place kind on what
1 2026-11-27 00:00:00Z 2026-11-27 00:00:00Z 6 clock clk_7Hq2 expires · run extend 3d → extend proposed · committed · waits on person at file
A run rung the gate refuses is held. It escalates to the owner at once, stays armed, proposes again each day, and at the date its step is the first option. Grants and decisions shows one held on a grant that ran out.
| App | Shows | Acts |
|---|---|---|
| Customer portal | each clock under its job or subject: its date, its next rung, who answers for it, and the date it moved from | on a clock the buyer answers for: acknowledge, mark done, choose |
| Delivery portal | Your dates: each clock the member answers for beside their Tasks' due dates, late first | the same three, on their own clocks only |
| Owner console | only the dates that need you: vacant, held, escalated, hard, due unsatisfied, with the next 30 days | assign an owner; the brand's own: mark done, choose |
A clock moves no money by itself: what it runs, what its owner chooses, and a guarantee you carry do. A clock on the job runs on every plan, one on the subject on Launch, and guarantee on Team. Every kind of time, by plan: Keeping time.
| Op | API | CLI | MCP | SDK | Site | Docs | App |
|---|---|---|---|---|---|---|---|
clock.list | GET /clocks, GET /owner/clocks | clocks list | list_clocks | clocks.list() | 1 error page | customer portal, owner console, delivery portal | |
clock.get | GET /clocks/:id, GET /owner/clocks/:id | clocks get <clock> | get_clock | clocks.get() | 1 error page | customer portal, owner console, delivery portal | |
clock.ack | POST /clocks/:id/ack, POST /owner/clocks/:id/ack | clocks ack <clock> | never: only its owner acts; an agent reads it with get_clock and hands on the owner's app | clocks.ack() | 4 error pages | customer portal, owner console, delivery portal |
Objects: clk_.
Events: clock.opened, clock.unopened, clock.repinned, guarantee.claimed, clock.noticed, clock.escalated, clock.ack_due, clock.ack_missed, clock.ran, clock.moved, clock.due, clock.barred, clock.satisfied, clock.recurred, clock.rolled, clock.vacant, clock.acked, clock.chosen, clock.assigned, clock.outlived.
Operations has every column of every row.
| Code | Category | HTTP | Why |
|---|---|---|---|
ineligible | blocked | 403 | The member can't take this Task: the answer names the first eligibility predicate they fail, and its cure. |
input_invalid | invalid_request | 422 | The order doesn't match the Service's input. |
needs_the_owner | blocked | 403 | Only the clock's owner can do this: acknowledge it, satisfy it, or choose at its date. |
check gate | Why |
|---|---|
clock_unowned | A clock's owner can never resolve: a role the Service doesn't have, or of_record with no member of record on its subject. It would run vacant, with no one to answer for the date. |
clock_due_unstable | A due or moves function answers differently when check runs it twice, or on a shifted clock: it reads the wall clock or chance, so a replay would move the date. |
clock_due_unreadable | A clock's due function can't give a date for the Service's own example order: it throws, or returns something that isn't a date. On a real order the clock would never open, and the owner console would alarm. |
| When the file won't load | Thrown by | Why |
|---|---|---|
clock_due_unknown | Service() | Service Renewal: clocks.expires.due is 'next spring'. A due date is a span after something, such as '30d after file' or '3mo after issued', or a function of the job that returns a date. |
clock_owner_unknown | Service() | Service Renewal: clocks.expires.lives_on is 'subject', and the Service has no subject for the clock to outlive the job on. |
apis.tax's lines that use it, as its file writes them, and check reading them aloud. api.lawyer's Formation keeps an election date and two yearly dates on its subject.
clocks: {
file: { due: fileDue, each: { of: 'jurisdictions' }, owner: 'of_record', rungs: ['notice 90d', 'notice 30d', 'run tentative 3d', 'ack daily 5d'], moves: { extend: extendedDue }, exposure: lateFiling, guarantee: { covers: ['penalties', 'interest on them'], up_to: 5_000, after: 'notice-response', excludes: 'held' } }, clocks file · a date your code decides, one for each jurisdiction · the member of record · moves on extend to a date your code decides · about 5% of the unpaid tax a month late, or $245 an owner · guarantee: penalties and their interest, up to $5,000, after notice-response, never for held time, from your lineapis.tax sells a guarantee.