Look up clock modes, duration grammar, public transition results, idle scope, and committed-failure recovery.
Every instance owns its clock. This checked public test shows the complete status(), advance(),
idle(), and cleanup shape:
import { createTestRuntime } from "localhost2137/testing";
import { expect, it } from "vitest";
import config from "../localhost.config.js";
it("reports one exact instance-clock transition", async () => {
const runtime = await createTestRuntime({ config, port: 0, storage: "temporary" });
try {
const instance = await runtime.createInstance();
try {
expect(await instance.clock.status()).toEqual({
mode: "pinned",
now: "2026-01-01T00:00:00.000Z",
});
const advanced = await instance.clock.advance("2h");
expect(advanced).toMatchObject({
from: "2026-01-01T00:00:00.000Z",
mode: "pinned",
to: "2026-01-01T02:00:00.000Z",
});
expect(advanced.advanceId.length).toBeGreaterThan(0);
await instance.idle();
expect(await instance.clock.status()).toEqual({
mode: "pinned",
now: "2026-01-01T02:00:00.000Z",
});
} finally {
await instance.destroy();
}
} finally {
await runtime.close();
}
});pnpm --filter @localhost2137/example-getting-started testThe clock moves only because the test calls advance("2h"). idle() does not move it.
The checked config imported above sets
clock: { mode: "pinned", startAt: "2026-01-01T00:00:00.000Z" }.
| Claim | Mode | Consequence |
|---|---|---|
| Plugin work at an exact emulator instant | { mode: "pinned", startAt } | Time stays at the RFC 3339 startAt value until explicit advancement. |
| Plugin timestamps must follow machine time | { mode: "real" } | Time follows wall time plus a persisted offset added by advances. |
Application timers or Date.now() | Neither controls them | Give the application its own clock seam or completion boundary. |
| Provider-hosted scheduling | Neither executes it | Keep a provider-facing check. |
Plugin code sees instance time only through its context clock. Code that reads Date.now(), an SDK
clock, or another process still sees that source. Task tracking is a separate concern: it controls
which work the runtime waits for and which failures it surfaces, not which clock that work reads.
A pinned clock stays fixed across daemon restarts. A real clock keeps moving and retains its explicit offset. New and reset worlds start from config; an existing persistent world retains its current clock after restart.
| Accepted | Milliseconds |
|---|---|
1ms | 1 |
2s | 2000 |
3m | 180000 |
4h | 14400000 |
30d | 2592000000 |
2w | 1209600000 |
Durations are positive whole values without leading zeroes. 01s, 1.5h, 0s, -1d, and
1month are rejected. The parsed result must fit a safe integer of milliseconds, and the resulting
clock must fit the JavaScript Date range. Month and year units do not exist.
Advance adds a duration; it never sets an absolute timestamp. Two successful advance("30d") calls
move pinned time by exactly 60 days. In real mode, they add 60 days to the persisted offset while
wall time continues to pass. Each successful window has a new advanceId.
| Awaited call | Established | Not established |
|---|---|---|
clock.status() | Current instance mode and RFC 3339 time | Any other clock source |
clock.advance(duration) | Window committed; each configured service reconciled and acknowledged it; tracked reconciliation work settled | Application queues, SDK timers, external systems |
idle() | Currently registered work for that instance settled or failed | Future scheduled retries, application jobs, untracked promises |
An advance commits { advanceId, from, to }, presents it to configured services in stable order,
waits for tracked work started during reconciliation, and then records acknowledgements. One 30d
call is one window; a plugin may produce zero, one, or many effects in it. Two 15d calls are two
windows and are equivalent only when that plugin documents and tests the invariant.
A plugin without onTimeAdvanced still observes the new clock, but the runtime invents no service
behavior. A retry stored for a future virtual instant is not currently tracked work; idle() can
settle the failed attempt while the retry remains scheduled. Advance to the plugin's documented
deadline to run it.
For callback-driven application effects, first await the operation or clock transition, then await
the application's own database, queue, or job boundary. A second idle() does not extend runtime
tracking into application code. Callbacks and webhooks shows an
actual receiver loop.
This is the exact tested control envelope when the clock committed but reconciliation remains
pending. Runtime-generated correlationId and advanceId values differ in a real process.
{
"error": {
"code": "INSTANCE_MUTATION_COMMITTED",
"correlationId": "adapter-correlation",
"details": {
"advanceId": "advance_safe",
"from": "2026-01-01T00:00:00.000Z",
"mode": "pinned",
"reconciliationPending": true,
"to": "2026-01-01T00:00:01.000Z"
},
"message": "The clock moved, but time reconciliation remains pending."
}
}| Error state | Meaning | Next action |
|---|---|---|
reconciliationPending: true | At least one service has not acknowledged the committed window. | Correct the service/config cause. Restart resumes persistent pending work; the same duration in the running daemon resumes the original window. |
reconciliationPending: false | Reconciliation finished, but a durability check failed after commit. | Inspect clock and service state. Do not repeat the duration as repair. |
| Another error code | The committed-mutation contract does not apply. | Read current status before deciding whether anything moved. |
A different duration is rejected while reconciliation is pending. If a service owing an acknowledgement was removed, restore its prior config and restart. Do not reset or destroy the world to hide the pending transition.
The public error does not expose the underlying cause. The CLI prints the generic message and
boundary correlation ID; ControlApiError also exposes the safe window details. Inspect available
delivery/plugin evidence, config, and state rather than retrying from memory.
Diagnose a failing scenario covers the first broken boundary. What determinism means separates clock control from repeatability; Provider compatibility limits what the observed transition can claim.