How Time Matters in an Agentic OS

Kendall Clark · Pentad Labs · 8 July 2026 · PLRN-018

Life can only be understood backwards; but it must be lived forwards.

— Søren Kierkegaard, Journals (1843)

Abstract

WunderOS is bitemporal, recording every fact along two independent axes of time: when the fact was true in the world, and when the system came to know it. The first axis is lived forwards, the second understands the first backwards, and the two do not coincide. An agentic operating system in a regulated enterprise has no honest alternative to keeping both.

In what follows we give the two-axis account, state the user-benefits where they bite hardest, which is under audit. Then, in a second and more technical register, we show the query surface that reads the two axes: MTL+, the metric-temporal extension of Gleamalog, its three-valued semantics, and every bitemporal operator worked exhaustively against a single mortgage-underwriting run.

1. Why an agentic OS keeps two clocks

An agent acts on what it knows, and what it knows isn’t necessarily the same as what is so. Between the world becoming a certain way and the system recording that it has done so there is always a delay, sometimes a delay of milliseconds and sometimes of days or more, and in that gap the agent decides. A system with one clock cannot represent the gap. It holds the current value, overwrites the old one, and by overwriting destroys the only evidence of what it believed at the moment it acted. The agent decided on a picture of the world. The system no longer has that picture.

So when someone asks later why the agent decided as it did, the honest answer, that we believed X at the time and X was later corrected to Y, is unavailable, because the system threw X away when it wrote Y. You can ask people what they recall of what they knew later on, under color of audit, and that’s not a very satisfying way of doing business which, in fact, happens every day. But it’s a way of acting that is broken by design in agentic automation.

The unavoidable time gap is not a defect to be engineered out. It is a permanent feature of any system that learns about the world through observation rather than by constituting it. A customer’s income changed on the first of the month; the payroll feed reported it on the fifth; the underwriting agent read it on the sixth. Three dates, three different things, and a decision made on the sixth that was correct given what was knowable on the sixth and wrong given what was true on the first. A single-clock system collapses all three into one and can reconstruct none of them.

WunderOS keeps two clocks because the regulated enterprises it serves are accountable for the gap. The append-only lineage of PLRN-007 already refuses to overwrite; provenance-by-construction PLRN-006 already records where each fact came from. Bitemporality is the axis those commitments reach for.

2. What bitemporality is

Bitemporality is the discipline of never conflating when a thing was so with when we found out that it was. More prosaically, bitemporality is the practice of stamping every fact with two intervals and never letting one stand in for the other.

The first is valid-time: the stretch of world-time over which the fact held. A rate was three percent from March to June. The second is transaction-time, or tx-time: the stretch of system-time over which the system asserted the fact. The system came to believe the rate was three percent on the fourth of March and still believes it.

The two axes are orthogonal. A fact can be valid long before it is recorded, which is backdating, and it can remain on record after it has ceased to be valid, which is history. Neither axis is derivable from the other, and that irreducibility is the whole content of the idea.

Four combinations follow, and each is a real situation an agent meets.

  1. A fact true now and known now is the ordinary case.
  2. A fact true in the past and only now recorded is a late arrival, the payroll feed catching up.
  3. A fact once recorded and now retracted is a correction, kept rather than erased so that the record of earlier belief survives.
  4. A fact recorded as true of a future validity is a scheduled change, a rate that takes effect next quarter.

One clock can represent the first. Only two clocks can represent the other three.

In WunderOS the two axes have two carriers. Every WaxRow bears a sys_ulid, the ULID it was etched under, whose millisecond timestamp is its tx-time. Every Pentad payload carries a valid_at, the world-time the fact asserts. The row thus resolves to a pair, and a query that asks a temporal question asks it of the pair, not of either half. This is the bitemporal envelope, and the Pentad·T extension that carries it is the WunderOS premise the remainder of this note describes.

3. The record that can answer for itself

A record is truthful in two senses, and bitemporality is what lets an enterprise’s record be truthful in both. We can name the two virtues plainly: accuracy, the disposition to get things right; and sincerity, the disposition not to assert more than one has grounds for.

A single-clock system fails both under audit. It fails accuracy because it cannot show what was so at a past moment, only what it now says was so. It fails sincerity because, having overwritten its earlier belief, it presents its current belief as though it had always held it, which is a quiet form of pretending now to have known then what it did not yet know.

A regulated enterprise is one place where this matters very much, because regulation is very largely a demand for a truthful record kept along both axes at once. Consider what the demand actually asks.

Four real examples of how this matters in regulated enterprises

First. A fair-lending examiner asks whether a denial was justified on the information the lender held at the time of decision, not on information that arrived a week later. Under the Equal Credit Opportunity Act and Regulation B the adverse action notice must state the actual reasons, and the actual reasons are the ones the agent read, which is a tx-time question. A system that has since ingested a corrected credit score cannot answer it. A bitemporal system pins the query to the tx-time of the decision and reads the world exactly as the agent read it.

Second. A model-risk reviewer under SR 11-7 asks the same question of the model rather than the officer: given the features as they stood when the model scored, does the score reproduce. Reproducibility is an as-of query on both axes, and it is unanswerable without them.

Third. A privacy request under the GDPR’s right to rectification asks that an incorrect fact be corrected going forward without erasing that the incorrect fact was once held and once acted upon, because the erasure would itself destroy the audit the same regulation elsewhere requires. Bitemporality is the one representation that satisfies both clauses: valid-time carries the correction, tx-time preserves the history, and neither overwrites the other. Retraction without amnesia is the phrase PLRN-007 used, and this is its temporal form.

Fourth. A trade-reconstruction request under MiFID II or a books-and-records request under SOX asks the enterprise to reproduce its own state as of a named instant, across every system, and to defend that the reproduction is faithful. The faithfulness is exactly the promise that the tx-time axis was never rewritten.

Across all four the shape is really just one shape. The enterprise is asked to answer for a past decision, and to answer for it means to reconstruct the epistemic position of the agent that made it: what was on record, and when it went on record, and what had not yet arrived. An agentic OS multiplies the number of such decisions by putting agents in the loop at machine speed, which multiplies the number of occasions on which the enterprise will be asked to answer. The two-clock record is what lets it answer without either guessing or lying. That is the benefit, and it is not a convenience. It is the precondition of deploying agents into a regulated process at all.

The rest of this note is for the reader who wants the glorious details

4. MTL+: metric temporal logic in Gleamalog

First, Gleamalog is the Datalog variant that is WunderOS’s primary query language, of agent state, of information-systems outside WunderOS, and of WunderOS itself. So far we’ve described how WunderOS state is bitemporal and why. Now we’ll look at how Gleamalog lets us read its bitemporal state bitemporally.

In short, keeping two clocks is worth nothing without a language to interrogate them. That language is MTL+, and it rides in the Gleamalog clause surface rather than beside it, so every consumer, whether a one-shot query, a standing lease, or a Brass Loom plan trigger, writes the same syntax and gets the same semantics.

But let’s start from LTL, Linear Temporal Logic, which reasons about a single timeline with a small family of modal operators:

LTL is qualitative. It can say P eventually holds. It cannot say P holds within five hours.

MTL is LTL’s metric extension. Each operator gains an interval that bounds the witness time, so ◇⟦0, 5h⟧ P means “P holds at some point in the next five hours”, and □⟦1s, 30s⟧ P means “P holds continuously across that window”.

DatalogMTL is this logic embedded in Datalog, decidable and rule-shaped (Brandt, Kalaycı, Kontchakov, Ryzhikov 2018; Wałęga, Kontchakov, Zakharyaschev 2019). Pollaci 2026 supplies the piece those lack, which is a fixpoint semantics for MTL under negation, grounded in approximation-fixpoint theory (AFT), and it is the temporal semantics WunderOS implements.

MTL+ is the binding of that ceiling to a concrete clause and evaluator surface, plus one extension the literature does not have. The extension is bitemporality. Every interval binds both axes at once, and a metric operator is satisfied only where its witness lies inside the window on the tx-axis and on the valid-axis simultaneously. Single-axis MTL is a special case; the bare operator, carrying no interval, keeps its pre-metric LTL meaning unchanged, which is what lets the eight shipped operators remain backward compatible.

The interval enclosure is a pair of mathematical white square brackets, (U+27E6) and (U+27E7), chosen so the metric payload is visually and syntactically distinct from Gleam’s [] list literals and so the parser never has to disambiguate the two. Plain ASCII [ ] is accepted as a fallback for shell pipes, CI logs, and Slack snippets where the codepoints are inconvenient.

Interval literals are an integer and a unit suffix, one of ms, s, m, h, d, with 0 the literal tick-zero and −7d seven days into the past.

The eight operators and their glyphs, each now carrying an optional interval:

Glyph Operator Reading Metric form
eventually P holds at some future point window bounds the witness
always P holds at every future point window bounds the coverage
once P held at some past point window bounds the witness
historically P held throughout the past window bounds the coverage
prev P held one tick ago ⟦k, k⟧ narrows to a stride
next P holds one tick ahead ⟦k, k⟧ narrows to a stride
since A has held since B window bounds the B-to-A gap
now P holds at the instant interval rejected at compile

Time enters as integer ticks. A ULID packs a 48-bit Unix-millisecond timestamp in its high bits, and the mapping to a signed 64-bit tick from the project epoch is a shift and a subtraction, pure and zero-cost; in Zig,

pub fn ulid_to_tick(ulid: u128) i64 {
    const epoch_ms: i64 = 1_774_915_200_000;   // 2026-03-31T00:00:00Z, project start
    return @as(i64, @intCast(ulid >> 80)) - epoch_ms;
}

An i64 tick from that epoch spans some 290 million years to either side, so overflow is not a practical concern, and the 80 bits of ULID randomness are discarded for interval arithmetic and recovered only as a deterministic tiebreaker at the sort-merge join. A WaxRow runs this mapping twice, once over sys_ulid for the tx-tick and once over the payload’s valid_at for the valid-tick, and the both-axes rule runs over the resulting pair:

\[\Diamond_{[a,b]}\,P \;\equiv\; \exists t:\; P\text{-witness at } (t_{\text{tx}}, t_{\text{val}}) \;\text{ with }\; t_{\text{tx}} \in [a,b] \;\wedge\; t_{\text{val}} \in [a,b].\]

At the AST the whole extension is one optional field. The eight constructors gain an Option(Interval); the None case is the bare LTL+ operator, semantics untouched:

pub type Interval {
  Bounded(lo: Int, hi: Int)     // ⟦a, b⟧ in millisecond ticks
  LowerBounded(lo: Int)         // ⟦a, ∞)
  UpperBounded(hi: Int)         // (−∞, b]
  Unbounded                     // (−∞, ∞) ≡ None at the AST level
}

pub type TemporalOp {
  Once(Pentad, Option(Interval))
  Historically(Pentad, Option(Interval))
  Prev(Pentad, Option(Interval))
  Since(Pentad, Pentad, Option(Interval))
  Next(Pentad, Option(Interval))
  Eventually(Pentad, Option(Interval))
  Always(Pentad, Option(Interval))
  Now(Pentad)
}

5. Why three values: true, false, and undef

A metric operator does not return a boolean. It returns one of three values, true, false, or undef, packed one to a byte as 10, 00, 01. The third value is not hedging and it is not probability. It is the substrate saying, in a principled way, that it cannot yet decide, and it exists because a system that never overwrites and learns about the world through, say, a CDC feed has three genuinely distinct epistemic states, not two.

Three conditions produce undef, and each is a real gap rather than a soft answer.

The first is a pending tx-axis: the world may already have made P true, but the CDC feed carrying the fact from a federated source has not yet landed locally, so the tx-tick does not yet exist.

The second is a coverage gap under a universal operator: □ and ■ ask whether P held throughout a window, and a sub-interval covered by a witness that carries no world-time scope makes no claim that can cover the tick, so the gap is unknown rather than violated.

The third is the general case of the first two under the fixpoint, where the AFT operator has not yet reached a verdict.

Because both axes are three-valued, their intersection is a three-valued product, as the table below describes. It composes exactly with the packed-byte algebra, so a metric verdict flows into WunderOS’s provenance semiring and the join layer without special-casing.

tx-axis valid-axis result reading
true true true seen, and true in the world
true false false seen, but the world says no
false any false not seen in the tx-window
undef true undef world says yes, CDC pending on tx
undef false false world said no; tx irrelevant
true undef undef system saw it, valid-time pending
undef undef undef undecidable on both axes

The undef rows are precisely the conditions under which a two-valued system would have to guess. A boolean forces the substrate to answer false when it means not yet known, and false and not yet known are the two states a regulated audit must never confuse. The third value is what keeps the packed byte honest.

Under push evaluation the three values are not read once but emitted as transitions across a clause’s lifetime, which is the mechanism Brass Loom (WunderOS’s deterministic agent executor) plan triggers subscribe to:

t = window_open    emit (clause, witness = ∅,   state = undef)
t = first_witness  emit (clause, witness = {W},  state = true)    -- eager fire
t = window_close   emit (clause, witness = ∅,   state = false)   -- only if none arrived

The eager fire is the early answer a trigger needs. The window-close emission is the negative verdict a liveness check needs. The three-transition contract carries both, and it is the strict superset of eager-only and conservative-only, which is why it is the contract.

6. The whole surface, on one loan

In what follows we provide plenty of examples of the bitemporal parts of Gleamalog, a full description of which will appear later. The examples run against one agentic mortgage-underwriting use case, the kind now common in the field: a customer agent takes an application, a KYC agent verifies identity, a credit agent pulls a bureau score, a compliance agent checks the loan-to-value and debt-to-income ratios and document completeness against lending regulation, and a core-banking agent funds the approved loan. The agents talk agent-to-agent and call tools over MCP. Every step each of them takes lands in WunderOS as a Pentad on both time axes. The queries below read that record. One application, call it app_88, carries the run.

Each block is a clause and its answer. The features fold into the operators rather than standing apart, so the eighth example is not a new operator but a new axis rule, and so on.

◇ eventually, with the push lifecycle. Under ECOA a denied applicant must receive an adverse-action notice within thirty days of the decision. The clause is a bounded future-existential, and under push it is armed at the denial and watched:

◇⟦0, 30d⟧(:adverse_action_sent(app_88))

Answer:

The verdict requires the notice to fall inside [decision, decision+30d] on the tx-axis and the valid-axis both, so a notice backdated in the world but recorded late still satisfies only if both ticks land in the window.

□ always, with ∀-coverage. The applicant’s KYC clearance must hold continuously across the fourteen days of underwriting, not merely at some instant. This is the universal operator, and universals ask a coverage question a point-witness cannot answer:

□⟦0, 14d⟧(:kyc_cleared(app_88))

Answer:

On this operator the tx-axis is a visibility gate only, tx ≤ as_of, and not a second coverage dimension, because a clearance recorded before the window but valid during it must count toward coverage.

♦ once, over the episodic memory tier. Did the applicant capture consent at any point in the last seven days. A past-existential with a lower-bounded window:

♦⟦−7d, 0⟧(:consent_captured(app_88))

Answer:

■ historically, resolving to undef. Has the debt-to-income ratio stayed within the regulatory limit throughout the last thirty days. The past universal, the coverage rule of □ run backward:

■⟦−30d, 0⟧(:dti_within_limit(app_88))

Answer:

● prev, and the degenerate interval. Immediately before the loan was decisioned, was the credit score already pulled. The bare operator asks the prior tick:

●(:credit_pulled(app_88))

Answer:

○ next, forward over the thick path. Given approval, is funding predicted within the next five ticks. The forward existential, stride-generalized:

○⟦1, 5⟧(:funded(app_88))

Answer:

↤ since, with A-break and aging. Have the funds stayed disbursed since approval, with the approval itself inside the last ten minutes. The binary operator, its interval bounding the gap between the two witnesses:

↤⟦0, 10m⟧(:funds_disbursed(app_88), :approved(app_88))

Answer:

The aging-driven true → undef is what makes since non-monotone where eventually and always latch, and the two exits are kept distinct on purpose.

The bare operator keeps its old meaning. Dropping the interval is not a window of infinity with the bitemporal rule attached. It is the pre-metric operator, single-axis:

:eventually(:decisioned(app_88))          -- bare LTL+, single-axis, unchanged
:eventually[0, 30d](:decisioned(app_88))  -- ASCII fallback, parser-accepted, ≡ ⟦0, 30d⟧

Answer: the first asks only whether the loan is ever decisioned, on the valid-axis alone, exactly as it did before MTL+ existed. The bitemporal intersection is a property of the interval, not of the operator name, and this is the seam that keeps every shipped call site correct. The second line is the same metric clause as the white-bracket form, written in ASCII for a Unicode-less environment.

Per-object triggers. A push trigger defaults to an existential fold: the clause turns true when any object satisfies it, one state across every application sharing the predicate. Keying per object gives one lifecycle per loan:

◇⟦0, 30d⟧(:decisioned(App))     -- one lifecycle per bound App

Answer: instead of a single state that fires when some application is decisioned, the evaluator carries N states behind one clause id, one per distinct bound App, so the compliance agent gets a per-loan thirty-day clock. The object travels on the transition as a fingerprint, a sufficient discriminator for routing, with the full identity a witness-ULID chase away.

As-of replay. The audit question of section 3, made a clause. Pin the query to the record as it stood at a past transaction instant:

:as_of(tx_ulid): ■⟦−30d, 0⟧(:dti_within_limit(app_88))

Answer: the same coverage question, but evaluated against only the facts whose tx-tick is at or before tx_ulid, so the answer is the one the system would have given at that instant, before any later correction landed.

This is the mechanism that reconstructs the agent’s epistemic position at decision time, and it is why the tx-axis must never be rewritten. Read the loan as the underwriter read it, not as the record was later corrected to read.

That is the surface. Eight operators, one optional interval each, two axes, three values, and a handful of composition rules that fold into the operators rather than multiplying them.

In Gleamalog, as it is today, the same eight clauses run as a

because the mode is a lowering choice and the semantics, the AFT fixed point on the bitemporally extended fact space, is the same in all three.

The metric surface inherits from canonical DatalogMTL (Brandt, Kalaycı, Kontchakov, Ryzhikov, Querying Log Data with Metric Temporal Logic, JAIR 2018; Wałęga, Kontchakov, Zakharyaschev, DatalogMTL: Computational Complexity and Expressive Power, IJCAI 2019). The three-valued fixpoint under negation follows Pollaci et al., Fixpoint Semantics for DatalogMTL with Negation (arXiv:2601.03841, 2026), read through approximation-fixpoint theory. The truthfulness framing of section 3 is Bernard Williams, Truth and Truthfulness (2002), on accuracy and sincerity as the two virtues of a record. Within the PLRN series this note stands on the append-only retraction of PLRN-007, which bitemporality gives its temporal form; on the provenance-by-construction of PLRN-006; and on the deterministic arithmetic of PLRN-002, which is what makes the ULID-to-tick mapping and the sort-merge tiebreak replayable. The substrate-events that surface as first-class temporal witnesses are the substrate-recursion pattern of PLRN-005.

A note on method

Written in conversation with Claude Opus 4.8 (Anthropic) as structured interlocutor. The research backstop was assembled in part with Paper Lantern. The motives, ideas, claims, framing, architectural commitments and taste, or lack thereof, are mine.

Kendall Clark · k@pentad.ai
Great Falls, Virginia
July 2026