npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@takk/gaptime

v1.0.0

Published

GapTime: bi-temporal knowledge-graph memory for long-running agents. A zero-runtime-dependency, TypeScript-first temporal fact store: every fact carries valid time (when it was true in the world) and transaction time (when the system recorded it), so you

Readme

GapTime NPM

npm version license types tests coverage runtime deps

GapTime stores bi-temporal facts for Massive Intelligence (IM) agent workloads, and it never calls a provider API. That is the product invariant: zero credentials, zero network, the host stays in full control of its own calls. Every fact carries two independent clocks, valid time (when it was true in the world) and transaction time (when GapTime recorded it), so you can ask what was true at any instant, reconstruct what the system believed at any instant, supersede an outdated fact without losing the old one, and keep an append-only history. You assert facts you already have, then time-travel and search across them, and GapTime accounts for every version.

Why it exists: conventional agent memory assumes truth is timeless. A user says "I work at Anthropic" in February, moves to OpenAI in May, and flat memory either forgets the old fact (history lost) or keeps both (a contradiction the agent repeats back). Bi-temporal indexing resolves it: the old fact was valid February to May, the new one is valid from May, and GapTime can still reconstruct exactly what it believed at any past instant. Graphiti proved the model in Python but is database-bound; as of June 2026 we found no shipping npm package that combines a four-timestamp model, contradiction detection, hybrid retrieval, provenance, and an audit history in a zero-dependency, embeddable form, and regulated agent deployments (medical, finance, legal) increasingly need exactly this temporal traceability.

What ships: a four-timestamp bi-temporal model, contradiction detection over Allen interval relations, invalidate-never-delete history with retract that heals, deterministic prefix-free statement keys (FNV-1a 64), hybrid retrieval (in-memory BM25, n-hop graph traversal, optional pluggable vector hook), provenance and lineage on every fact, an append-only frozen audit trail, a prune retention policy, memory, file, and KV persistence, structural write-through adapters for Neo4j, Postgres and SQLite, Vercel AI SDK and OpenTelemetry GenAI and Model Context Protocol ingestion, bridges for the @takk family, and a CLI with a hardened HTTP bridge. Zero runtime dependencies. 127 tests across 16 suites on Node 22 and Node 24.


See it run

This is the literal output of the deterministic demonstration that ships in the CLI. Same flags, same bytes, every run:

$ 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).

Two assertions about the same single-valued predicate run in sequence. The May fact contradicts the February one, so GapTime closes the Anthropic fact at May 1 and records the contradiction with its Allen relation. Valid-time travel returns Anthropic in March and OpenAI in June; nothing is deleted, and the history carries all three fact versions.


Quickstart

pnpm add @takk/gaptime
# or: npm install @takk/gaptime

Assert, time-travel, search. The whole integration is this loop:

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-01T00:00:00Z'),
  provenance: { source: 'conversation' },
});

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

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

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

Every predicate is single-valued by default (one current object per subject and predicate). For genuinely set-valued relations (likes, knows, owns), register them as { cardinality: 'multi' } or pass defaultCardinality: 'multi', otherwise a second object supersedes the first.

The full engine surface is assert, ingest, retract, query, asOf, reconstructAsOf, history, neighbors, search, contradictions, entity, entities, resolveEntity, registerPredicate, facts, prune, stats, auditTrail, on, flush, and close, specified in SPEC.md.


The two time axes and the query surface

Every fact is a statement subject predicate object annotated with two half-open intervals: valid time [validFrom, validTo) (when it was true in the world, open end means still valid) and transaction time [txFrom, txTo) (when GapTime believed it, open end means current belief). Reads default to "valid now, believe now"; pass either coordinate to time-travel on that axis independently, or both for a true bi-temporal question.

| Query | What it answers | |---|---| | asOf(t) | Facts true at valid-time t, believed now. The "what was true then" axis. | | reconstructAsOf(t) | Facts believed at transaction-time t, regardless of later change. The "what did the system know then" axis. | | query(pattern, { asOfValidTime, asOfTransactionTime }) | Both axes independently, with a triple-position filter. | | history(pattern) | Every version ever written, nothing filtered. The raw log behind time-travel. | | neighbors(id, { depth, asOfValidTime }) | Graph traversal outward from an entity, honoring valid time so only live edges are walked. | | search(text, { asOfValidTime, expandHops }) | Hybrid lexical, graph, and optional vector retrieval, filtered to the chosen time. | | auditTrail({ since, until }) | The transaction-time axis as a frozen, append-only event log. |


Contradiction detection

A single knob, predicate cardinality, drives contradiction detection. 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, closes the older fact over only the contradicted span, and keeps any surviving prefix and suffix. The relation is recorded so you can see exactly how two facts collided.

| Relation group | Members | Meaning for two valid intervals | |---|---|---| | disjoint | before, after | No overlap; never a contradiction | | touching | meets, metBy | Adjacent at a single instant; half-open, so no overlap | | partial overlap | overlaps, overlappedBy | Genuine overlap; a contradiction for a single-valued predicate | | containment | during, contains | One interval inside the other; the surviving prefix and suffix are kept | | boundary-shared | starts, startedBy, finishes, finishedBy | Share one endpoint and overlap | | identical | equals | Same interval; the newer fact fully supersedes the older |

Same-object overlapping assertions are restatements, not contradictions: they merge into the union interval so a subject never carries two current versions of the same object at one instant. Disjoint same-object spans (true Monday, true Friday, not in between) stay separate.


The bi-temporal model

GapTime never deletes. Two operations close intervals, and both stay reconstructable, which is what keeps time-travel exact rather than approximate.

supersession  closes the older fact's VALID interval over the contradicted span,
              writes clipped current-belief versions for the surviving prefix and
              suffix, and ends the older belief on the transaction axis.

retraction    closes a fact's TRANSACTION interval ("we recorded this in error"),
              and heals the supersession it caused: the overridden beliefs are
              restored, the clips are closed. reconstructAsOf at a past instant is
              unaffected, because every prior version is still in history.

The worked example, David moving from Anthropic to OpenAI, shows the rule: asserting OpenAI valid from May 1 over Anthropic valid from February closes Anthropic at May 1 (the finishedBy relation when both ran open), so asOf(March) is Anthropic, asOf(June) is OpenAI, and reconstructAsOf(before the May write) still returns Anthropic with an open interval, exactly what the system believed then.

Retrieval

search fuses three signals by reciprocal rank fusion, filtered to the requested time coordinates: an in-memory Okapi BM25 lexical index over an inverted postings map, n-hop graph traversal around the lexical hits, and an optional pluggable vector hook. The lexical and graph signals work with no database and no model; semantic recall is opt-in through a vector: VectorIndex you supply.

Ingesting MCP tool output

An MCP tool result is an ideal fact source: it carries structuredContent, a real-world timestamp, and a provenance hint. GapTime maps the result timestamp to valid time and the ingestion instant to transaction time, keeping the tool as the provenance source so any answer traces back to the exact call that produced it:

import { ingestMcpResult } from '@takk/gaptime/mcp';

await ingestMcpResult(memory, 'web_search', toolResult, {
  facts: (result) => toFacts(result.structuredContent),
});

Keep the fact mapping deterministic, or let the configured Extractor produce facts; either way GapTime never calls the model itself. The runnable version is examples/mcp-tools-ingest.ts.


Persistence

State backends persist entities, every fact version, episodes, contradictions, the audit trail, and the supersession ledger, so a reload restores the whole store. Three ship in the box: memoryState (default), fileState (Node, atomic unique-temp writes with corrupt-snapshot quarantine), and kvState, which wraps any string key-value client in one line:

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

// Any Redis client (ioredis, node-redis), one line:
const state = kvState({
  get: (k) => redis.get(k),
  set: (k, v) => redis.set(k, v),
});

// Upstash Redis:   get: (k) => upstash.get<string>(k), set: (k, v) => upstash.set(k, v)
// Cloudflare KV:   get: (k) => env.GAPTIME_KV.get(k),  set: (k, v) => env.GAPTIME_KV.put(k, v)

const memory = createGapTime({ state });
// ... assert and query as usual ...
await memory.flush(); // snapshot saved; a new engine with the same backend restores it

GapTime keeps its working set in memory: the graph lives in the heap (roughly 2 kB per fact version), nothing is freed automatically, and maxFacts (default 100000) is a hard ceiling, not an eviction policy. For long-running agents, apply a retention cutoff with prune(beforeTransactionTime), which drops fully-closed history and audit events before the cutoff while keeping every current belief; choose a cutoff that respects your retention obligations. For datasets beyond a bounded working set, mirror writes into a real database with the @takk/gaptime/store adapters. GapTime is a fast embeddable temporal layer for the working set, not a database replacement at large scale.


Vercel AI SDK ingestion

The AI SDK surfaces each agent step through onStepFinish and onFinish. GapTime turns the tool results into facts, mapping the response timestamp to valid time and the ingestion instant to transaction time:

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

const memory = createGapTime();

const result = await generateText({
  model,
  prompt,
  onStepFinish: gaptimeOnStepFinish(memory, { facts: stepToFacts }),
});

The adapter is structural: it matches the AI SDK step shape without importing the ai package, so the zero-dependency invariant survives. A host-supplied facts mapper declares which facts each step yields.


OpenTelemetry ingestion

If your stack already emits GenAI spans, GapTime ingests them directly. ingestSpan reads span attributes and timing, maps the span start time to valid time and the ingestion instant to transaction time, and uses the span's tool or operation as the provenance source:

import { createGapTime } from '@takk/gaptime';
import { ingestSpan, type GenAiSpan } from '@takk/gaptime/otel';

const memory = createGapTime();

// Inside a span processor's onEnd, an OTLP collector hook, or wherever
// finished spans surface in your host:
async function onSpanEnd(span: GenAiSpan): Promise<void> {
  await ingestSpan(memory, span, { facts: spanToFacts });
}

It tolerates the HrTime tuple, epoch-millisecond, and Date start-time spellings in circulation, and works with the spans the Vercel AI SDK emits under experimental_telemetry.


CLI and the serve bridge

The gaptime binary ships these commands: help, version, assert, ingest, query, asof, reconstruct, contradictions, audit, stats, search, demo (the deterministic demonstration above), inspect (print a saved snapshot, --watch for a live redraw), and serve.

gaptime serve wraps one engine in a hardened local HTTP bridge so non-JavaScript hosts can assert, query, time-travel, and read statistics. It binds loopback by default, refuses non-loopback hosts without a bearer token, rejects non-loopback Host headers in tokenless mode with 403 (the DNS-rebinding defense, /healthz included), compares tokens in constant time, and gates bodies with 415 and 413 responses.

gaptime serve --port 4378 --token "$GAPTIME_TOKEN" --state .gaptime/state.json

curl -s -X POST http://127.0.0.1:4378/assert \
  -H "authorization: Bearer $GAPTIME_TOKEN" \
  -H "content-type: application/json" \
  -d '{
    "subject": { "type": "person", "name": "David" },
    "predicate": "works_at",
    "object": { "type": "organization", "name": "OpenAI" },
    "validFrom": 1777593600000,
    "provenance": { "source": "conversation" }
  }'

Hermes Agent note: a Hermes deployment can call /ingest after each tool result and /asof or /reconstruct when it needs context, getting bi-temporal memory and an audit history without touching its own runtime. The full recipe, including the shell hook and the honest limits of an out-of-process bridge, is in examples/hermes-bridge.md.


Package surface

| Entry | What it is | Brotli size | |---|---|---| | @takk/gaptime | The engine, intervals, retrieval, state backends, hashing | 6.18 kB ESM / 6.37 kB CJS | | @takk/gaptime/otel | OpenTelemetry GenAI span ingestion | 404 B | | @takk/gaptime/vercel | Vercel AI SDK ingestion | 372 B | | @takk/gaptime/mcp | Model Context Protocol ingestion | 362 B | | @takk/gaptime/integrations | The five family bridges | 575 B | | @takk/gaptime/store | Structural Neo4j, Postgres, SQLite adapters | under 1 kB | | @takk/gaptime/web | Browser surface, no Node imports | 6.18 kB | | @takk/gaptime/edge | Edge-runtime surface, no Node imports | 6.18 kB |

Zero runtime dependencies on every entry.


The family stack

GapTime is one layer of a five-package stack for production agents, each independent, each one line to bridge:

  • Route the extraction model with @takk/modelchain: modelchainExtractor routes which model turns free text into facts on the optional extraction path.
  • Rotate credentials with @takk/keymesh: keymeshBridge retracts facts from a source on key.rotated and circuit.open, since a compromised or stale provider should no longer feed beliefs into memory.
  • Observe behavior with @takk/behavioralai: behavioralaiBridge turns the memory into a behaviorally observed agent, so a spike in the contradiction rate surfaces as behavioral drift.
  • Tune retrieval with @takk/noeticos: noeticosBridge returns tuned search parameters (graph depth, result limit) for the memory-search task.
  • Cache the prefix with @takk/racs: racsBridge invalidates the racs cache holding a rendered memory segment whenever a belief changes, and memorySegment renders an as-of snapshot as a cacheable prefix.

All five bridges live in @takk/gaptime/integrations, are structural (no sibling package is imported at runtime), and the siblings stay optional peers.


FAQ

Does GapTime ever make a network call? No, never. GapTime stores and queries facts; the host makes every provider call with its own credentials and transport. This is the product invariant, and it is why the package has zero runtime dependencies, never sees an API key, and runs identically in Node, browsers, and edge runtimes.

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 index 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.

Does GapTime call a model on the write path? Never. The write path is deterministic and takes pre-structured facts. Turning free text into facts is a pluggable Extractor you supply (LLM-backed or rule-based), and it stays entirely under your control. The @takk/modelchain bridge can route which model that extractor uses.

What does cardinality do, and why did my second fact disappear? Every predicate is single-valued by default: one current object per subject and predicate. Asserting a second object supersedes the first (the old one stays in history). For set-valued relations such as likes or knows, register them as { cardinality: 'multi' } or set defaultCardinality: 'multi', and distinct objects coexist instead of contradicting.

What is the difference between a contradiction and expected change? A contradiction is a different object asserted for a single-valued predicate over an overlapping valid interval. Re-asserting the same object, or asserting a multi-valued relation, is not a contradiction. Same-object overlapping spans merge into one interval; disjoint spans stay separate.

Can GapTime support compliance record-keeping? The transaction-time axis is append-only and auditTrail() returns it as a frozen, tamper-resistant event log; reconstructAsOf(t) answers "reconstruct what the system knew at time T". That is the record-keeping primitive behind EU AI Act Article 12 and ISO/IEC 42001 A.6.2.8. Those are obligations on your organization, not properties a library can confer; 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 @takk/gaptime/store adapters. It is a fast embeddable temporal layer, not a database replacement at large scale.

Does it work on edge runtimes and in the browser? Yes. @takk/gaptime/web and @takk/gaptime/edge export the full surface minus the Node-only file state backend. Nothing in the engine touches sockets, the filesystem, or platform globals; persist through kvState over Cloudflare KV or Upstash.


Author

GapTime is built and maintained by David C Cavalcante, Takk Innovate Studio, who researches Massive Intelligence (IM) and non-human entities at takk.ag.

Sponsors

If GapTime gives your agent fleet memory it can trust, consider sponsoring continued maintenance through the channels in .github/FUNDING.yml. Sponsorship never buys roadmap priority; it buys maintenance time.

License

Apache-2.0. Copyright 2026 David C Cavalcante, Takk Innovate Studio. See NOTICE.