@takk/gaptime - v1.0.0 - Apache-2.0

Remember when it was true.
And when you learned it.

GapTime is bi-temporal knowledge-graph memory for Massive Intelligence (IM) agents, and it never calls a provider API. Every fact carries valid time (when it was true in the world) and transaction time (when GapTime recorded it), so you can time-travel to what was known as of any instant, supersede outdated facts without losing history, detect contradictions, and keep an append-only audit history. Zero credentials, zero network, the host stays in control.

pnpm add @takk/gaptime
127tests passing
95%line coverage
0runtime deps
2time axes
The engine

Five capabilities and one invariant.

Conventional agent memory assumes truth is timeless. A user works at Anthropic in February and OpenAI in May; flat memory either forgets the old fact or keeps both as a contradiction the agent repeats back. GapTime makes time a first-class property of every fact, so memory stays correct as the world changes.

Bi-temporal fact store

Every fact carries two half-open intervals: valid time [validFrom, validTo), when it was true in the world, and transaction time [txFrom, txTo), when GapTime recorded it. An open end means still valid, or current belief. The two axes are queried independently or together.

"What was true on March 15" and "what did we believe on March 15" are different questions, and both have exact answers.

Contradiction detection

For a single-valued predicate, a different object over an overlapping valid interval is a contradiction. GapTime classifies the overlap with the thirteen Allen interval relations and closes the older fact over only the contradicted span, keeping any surviving prefix and suffix. Same-object overlaps merge instead of duplicating.

"David works at OpenAI now" supersedes "David works at Anthropic" without erasing that Anthropic was once true.

Time-travel queries

asOf(t) returns facts true at a valid-time instant; reconstructAsOf(t) reconstructs what the system believed at a transaction-time instant, regardless of any later change; query takes both coordinates; history returns every version.

reconstructAsOf answers a regulator's "reconstruct what the system knew at time T" directly, from the data model itself.

Hybrid retrieval

search fuses an in-memory Okapi BM25 lexical index over an inverted postings map, n-hop graph traversal, and an optional pluggable vector hook by reciprocal rank fusion, filtered to your time coordinates. The lexical and graph signals need no database and no model.

Relevant facts surface with no external search service; semantic recall is opt-in when you want it.

Provenance and audit

Source, ingestion timestamp, content hash, and actor are mandatory on every fact. The transaction-time axis is append-only, and auditTrail() returns it as a frozen, tamper-resistant event log, the record-keeping primitive behind EU AI Act Article 12 and ISO/IEC 42001.

Every answer traces back to the exact input that produced it, with a history a regulator can reconstruct.

The invariant

GapTime never calls a provider API. It holds zero credentials, opens zero network connections, and never sends a byte anywhere. The write path is deterministic and needs no model; turning free text into facts is a pluggable extractor you supply and control.

Nothing new to audit: no credential to store, no egress path to review, no vendor in the request loop.

The model

Two axes, thirteen relations, one query surface.

Every fact is a statement subject predicate object annotated with two half-open intervals. Valid time is when the fact was true in the world; transaction time is when GapTime recorded it. Contradiction detection classifies how two facts overlap with Allen interval relations, and a single query surface travels either axis, independently or together.

valid time

When it was true

The interval [validFrom, validTo). An open end means still valid. asOf(t) returns the facts true at any instant, so the agent never confuses "true then" with "true now".

transaction time

When you learned it

The interval [txFrom, txTo). An open end means current belief. reconstructAsOf(t) rebuilds exactly what the system believed at a past instant, regardless of any later change.

contradiction

Invalidate, never delete

A different object for a single-valued predicate over an overlapping interval supersedes the older fact, closing only the contradicted span and keeping the survivors. Retraction heals the supersession it caused. History stays reconstructable.

retrieval

Hybrid, no database

In-memory BM25 over an inverted index, n-hop graph traversal, and an optional pluggable vector hook, fused by reciprocal rank fusion and filtered to your time coordinates. Lexical and graph work out of the box.

valid asOf(t)

Facts true at valid-time t, believed now. Valid-time travel.

transaction reconstructAsOf(t)

Facts believed at transaction-time t, regardless of later change.

both query(pattern, opts)

Both axes independently, with a triple-position filter and limit.

raw history(pattern)

Every version ever written, nothing filtered, the log behind it all.

graph neighbors(id, opts)

N-hop traversal honoring valid time, predicate, and direction.

search search(text, opts)

Hybrid lexical, graph, and optional vector retrieval, time-filtered.

audit auditTrail(opts)

The transaction-time axis as a frozen, append-only event log.

retention prune(beforeTx)

Drop closed history before a cutoff, keep every current belief.

Quickstart

Assert, time-travel, reconstruct.

1. Add the package

pnpm add @takk/gaptime
npm install @takk/gaptime
yarn add @takk/gaptime
bun add @takk/gaptime

Zero runtime dependencies. No peer dependencies. The tarball is 58 files; the core entry is 6.18 kB brotli.

2. Assert two facts

import { createGapTime } from '@takk/gaptime';

const memory = createGapTime();

// February: David joins Anthropic.
memory.assert({
  subject: { type: 'person', name: 'David' },
  predicate: 'works_at',
  object: { type: 'organization', name: 'Anthropic' },
  validFrom: Date.parse('2026-02-01'),
  provenance: { source: 'conversation' },
});

// May: works_at is single-valued, so this supersedes the February fact.
// GapTime closes the Anthropic fact at May 1 and records a contradiction.
memory.assert({
  subject: { type: 'person', name: 'David' },
  predicate: 'works_at',
  object: { type: 'organization', name: 'OpenAI' },
  validFrom: Date.parse('2026-05-01'),
  provenance: { source: 'conversation' },
});

3. Time-travel and reconstruct

// Valid-time travel: where did David work at each instant?
memory.asOf(Date.parse('2026-03-15')); // -> Anthropic
memory.asOf(Date.parse('2026-06-15')); // -> OpenAI

// Transaction-time travel: what did we BELIEVE in March, before the May update?
memory.reconstructAsOf(Date.parse('2026-03-15')); // -> Anthropic, open interval

// The contradiction the May assertion produced, with its Allen relation.
memory.contradictions(); // [{ predicate: 'works_at', relation: 'finishedBy', ... }]

// The append-only audit history, frozen and tamper-resistant.
memory.auditTrail();
CLI

The proof runs in your terminal.

The gaptime binary ships in the box: help, version, assert, query, asof, reconstruct, contradictions, audit, search, demo for the deterministic walkthrough below, inspect --watch for live state, and serve, a hardened local HTTP bridge for non-JavaScript stacks.

$ gaptime demo
gaptime 1.0.0 demo: "where does David work?"

Two facts asserted: works_at Anthropic (from Feb), works_at OpenAI (from May).
The May assertion contradicts the Feb one (single-valued predicate), so GapTime
closes the Anthropic fact at May 1 instead of deleting it.

asOf 2026-03-15 (valid-time travel):
  f3vroh52o4m96n  David works_at Anthropic  valid[2026-02-01T00:00:00.000Z, 2026-05-01T00:00:00.000Z) tx[2026-05-01T00:00:00.000Z, now) src=conversation

asOf 2026-06-15 (valid-time travel):
  f3vrf22a4mdmfq  David works_at OpenAI  valid[2026-05-01T00:00:00.000Z, open) tx[2026-05-01T00:00:00.000Z, now) src=conversation

Contradictions:
  works_at: f3vqw7wp1lwcxw superseded by f3vrf22a4mdmfq (finishedBy)

History preserved: 3 fact versions, 2 current, 1 contradiction(s).

Output of gaptime demo, the shipped bi-temporal walkthrough. Same input, same output, every time: the engine is fully deterministic, with seeded ids and an injectable clock.

Surfaces

Eight entries, one engine underneath.

Sizes are brotli-compressed, measured on the published build and enforced in CI by size-limit.

@takk/gaptime

10.72 kB ESM / 10.86 kB CJS

The full engine: assert, ingest, query, asOf, reconstructAsOf, history, neighbors, search, contradictions, prune, auditTrail, plus the memory, file, and KV-style state backends.

@takk/gaptime/otel

404 B

GenAI span ingestion: maps OpenTelemetry GenAI spans into facts, the span start time to valid time and ingestion to transaction time, so traces you already collect become memory.

@takk/gaptime/vercel

372 B

Ingestion for the Vercel AI SDK: turns onStepFinish and onFinish tool results into facts, the response timestamp to valid time, with a host-supplied fact mapper.

@takk/gaptime/integrations

575 B

Bridges to the five sibling packages: observe the memory behaviorally, retract facts on credential rotation, invalidate a racs prefix cache on belief change, tune retrieval, and route the extraction model.

@takk/gaptime/web

6.18 kB

The same engine without the Node file backend, so browser bundles never advertise a backend they cannot run. Pair with kvState for persistence.

@takk/gaptime/edge

6.18 kB

Edge-runtime entry with the identical surface. kvState wraps any Redis, Upstash, or Cloudflare KV style client in one line for cross-isolate state.

CLI: gaptime

assert / query / asof / reconstruct / demo / serve

demo runs the deterministic walkthrough, inspect --watch redraws live state, and serve is a hardened, token-authenticated HTTP bridge for non-JavaScript stacks.

@takk/gaptime/mcp and /store

362 B and under 1 kB

Model Context Protocol ingestion turns tool structuredContent into provenanced facts; the store adapters mirror writes into Neo4j, Postgres, or SQLite. GapTime imports no driver.

Telemetry

typed event union

fact.asserted, fact.invalidated, fact.retracted, contradiction.detected, episode.ingested, query.executed: synchronous, deterministic under an injected clock, and free of any telemetry vendor.

Family stack

Six packages, one operational layer for agents.

Each package stands alone with zero runtime dependencies; together they cover credentials, routing, behavior, tuning, caching, and memory. The /integrations entry bridges them in one line each.

@takk/keymesh

Credential rotation, failover, and circuit breaking for provider API keys; keymeshBridge retracts facts from a source when its key rotates.

davccavalcante/keymesh
@takk/modelchain

Model routing and fallback chains across providers; modelchainExtractor routes which model turns free text into facts on the optional extraction path.

davccavalcante/modelchain
@takk/behavioralai

Behavioral observation for agent systems; behavioralaiBridge turns the memory into a behaviorally observed agent, contradiction rate included.

davccavalcante/behavioralai
@takk/noeticos

Adaptive runtime parameter tuning with canary rollouts and rollback; noeticosBridge returns tuned search parameters for the memory-search task.

davccavalcante/noeticos
@takk/racs

Prefix-cache management for long-context agents; racsBridge invalidates a racs prefix cache holding a rendered memory segment whenever a belief changes.

davccavalcante/racs
@takk/gaptime this package

Bi-temporal knowledge-graph memory: valid and transaction time, contradiction detection, time-travel queries, hybrid retrieval, audit history.

davccavalcante/gaptime
FAQ

Common questions.

Is GapTime production-ready at 1.0.0?

Yes. 127 tests across 16 suites pass on Node 22 and Node 24, with line coverage around 95 percent. TypeScript strict mode, lint, and package checks are clean, and every release ships with SLSA provenance.

What is bi-temporal memory?

Every fact carries two independent clocks: valid time, when it was true in the world, and transaction time, when GapTime recorded it. That pair lets you ask what was true at an instant and what the system believed at an instant, independently.

Does GapTime ever call my provider's API?

Never. GapTime stores and queries facts entirely in-process. It holds zero credentials and opens zero network connections; the host application makes every provider call with its own credentials and transport.

How is GapTime different from a vector memory like mem0 or Letta?

Those model time as ordering or recency. GapTime models two independent validity axes, so it supersedes outdated facts without losing history and can reconstruct what was believed at any past transaction time. It is structured temporal memory, not a similarity index, though you can plug a vector hook in for semantic recall.

Is GapTime the TypeScript answer to Graphiti?

Graphiti is the Python state of the art but is database-bound. GapTime is a zero-dependency, embeddable, TypeScript-first temporal knowledge graph that runs in Node, edge runtimes, and the browser, with write-through adapters for Neo4j, Postgres, and SQLite when you need a database behind it.

How does contradiction detection work?

For a single-valued predicate, asserting a different object over an overlapping valid interval is a contradiction. GapTime classifies the overlap with the thirteen Allen interval relations and closes the older fact over only the contradicted span, keeping any surviving prefix and suffix.

Are facts ever deleted?

No. Supersession closes a valid interval, retraction closes a transaction interval, and retracting a fact heals the supersession it caused. Both axes stay reconstructable, which is what makes the audit history precise.

Can GapTime support compliance record-keeping?

The transaction-time axis is append-only and the audit trail is frozen and tamper-resistant, providing the record-keeping primitive behind EU AI Act Article 12 and ISO/IEC 42001. Conformance remains your organization's responsibility; GapTime is the substrate, not the certification.

What about scale and memory growth?

GapTime keeps the working set in the process heap and never frees automatically. For long-running agents, apply a retention policy with prune, and mirror writes into Neo4j, Postgres, or SQLite with the store adapters. It is a fast embeddable temporal layer, not a database replacement at large scale.

Author

Built and maintained by David C Cavalcante.

David C Cavalcante

Founder, Takk Innovate Studio

Builder of the @takk family of NPM packages: zero-dependency operational infrastructure for Massive Intelligence (IM) agent systems.

GapTime exists because agents that run for months accumulate facts that change, and flat memory either forgets the old truth or repeats a contradiction. Graphiti proved the bi-temporal model in Python; this package brings it to TypeScript as a zero-dependency library any host can embed.

If GapTime mishandles a temporal edge case the test suite missed, the most useful thing you can do is open a GitHub issue with a deterministic reproduction. The release runbook, the threat model, and the contributor agreement live in the repository.