Skip to content

LabsKrishna/kalairos

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

96 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Kalairos

npm version npm downloads license node codecov CI

Kalairos — the agent platform for AI that needs to act at the right moment.

Bitemporal memory at its foundation. Live observability through a control plane that traces every fact, every action, every decision. Local-first. JSONL canonical, SQLite derived. MIT.

Most systems store what is true. Kalairos stores what was true when decisions were made — and shows you why your agent acted on it.


The problem nobody talks about

Your agent stores a fact. The fact changes. Now you're stuck.

await memory.remember('Employees must submit reports by Friday');
// ...two weeks later, policy changes...
await memory.remember('Deadline changed to Wednesday');

A user gets penalized for missing Wednesday's deadline. They protest: "I followed the rule. Why was I punished?"

Your system shrugs. It only knows the latest rule. The old one is gone.

Every vector DB has this bug. They overwrite. Or they duplicate and confuse retrieval. Either way, history disappears.

This is not a niche problem. It is every:

  • Policy change that gets applied retroactively
  • Pricing dispute where a customer signed up at the old rate
  • Compliance audit asking "what was your retention rule on March 12?"
  • Code review flagging old code against new rules
  • AI assistant that contradicts itself because user preferences shifted

If your agent can't answer "what did we believe at the time?", it cannot be trusted to make decisions that outlive a single session.


The fix, in one call

await kalairos.remember('Employees must submit reports by Friday');
await kalairos.remember('Deadline changed to Wednesday');

// Today
await kalairos.query('report deadline');
// → "Deadline changed to Wednesday"

// What was true last week?
await kalairos.queryAt('report deadline', lastWeek);
// → "Employees must submit reports by Friday"

That's it. Same memory. Two answers. Both correct — for their moment in time.

Without Kalairos: the system only knows the latest rule. The user looks wrong. With Kalairos: the system knows what was true then. The user can prove they were right.


Why this matters

For a hobby agent, losing history is an annoyance. For an agent that touches legal, medical, financial, or HR decisions, it's disqualifying:

  • A compliance reviewer doesn't ask what your policy is — they ask what it was on the date of the event, and how you know.
  • An agent that acted on a fact must be able to show where that fact came from, who wrote it, and how much it was trusted at the time.
  • When a stored fact changes or contradicts an earlier one, someone has to see the conflict — silent overwrites are how agents get poisoned and how audits get failed.

Kalairos makes those properties structural, not bolted on. Every returned fact carries its event time, ingest time, version id, provenance chain, data classification, and an explainable trust score. Every mutation lands in a read-only audit trail (trail()), and any moment can be frozen as a named checkpoint. If your agent has to survive the question "why did it do that, and what did it know at the time?" — that's the platform's job, and this is the platform.


Install

npm install kalairos

Local-first. No cloud service. No API key required. Bring any embedder. JSONL on disk — human-readable, git-friendly, easy to back up.

npx kalairos demo    # interactive demo, zero config

Layer 1 — Three calls and you're done

init, remember, query. Most agents need nothing more.

const kalairos = require('kalairos');
const embed = async (t) => [...t].map(c => c.charCodeAt(0) / 255); // toy embedder

await kalairos.init({ embedFn: embed });

await kalairos.remember('User prefers concise bullet points');
await kalairos.remember('Customer is on the $10/month plan');

const { results } = await kalairos.query('what plan are they on?');
console.log(results[0].text);

Swap the toy embedder for OpenAI, Cohere, or any async (text) => number[] when you're ready.


Layer 2 — Time-aware memory

Once the basic loop makes sense, time-travel is one call away. remember() detects updates automatically and appends a new version. queryAt(text, timestamp) recalls whichever version was current at that moment. getHistory(id) returns the full trail.

const id = await kalairos.remember('Subscription price is $10/month');

// ...later, pricing changes...
await kalairos.remember('Subscription price increased to $20/month');

// Current state
const now = await kalairos.query('subscription price');
console.log(now.results[0].text);     // → "$20/month"

// What did we charge this customer when they signed up?
const signupDate = new Date('2025-03-01').getTime();
const past = await kalairos.queryAt('subscription price', signupDate);
console.log(past.results[0].text);    // → "$10/month"

// Full version history with deltas and provenance
const history = await kalairos.getHistory(id);
history.versions.forEach(v => console.log(`v${v.version}: ${v.text}`));

One entity, many versions. Every query picks the version current at its chosen moment.

flowchart LR
    subgraph Entity["Entity #42 — 'Subscription price'"]
        direction LR
        v1["v1<br/>'$10/month'<br/>Jan 15"]
        v2["v2<br/>'$15/month'<br/>Feb 20"]
        v3["v3<br/>'$20/month'<br/>Mar 10 &larr; current"]
        v1 --> v2 --> v3
    end

    Q1["query('price')<br/><i>today</i>"]              -.->|returns| v3
    Q2["queryAt('price', Feb 25)"]                     -.->|returns| v2
    Q3["queryAt('price', Jan 20)"]                     -.->|returns| v1
    Q4["getHistory(42)"]                               -.->|returns| Entity

    classDef current fill:#d4edda,stroke:#28a745,color:#155724
    classDef past    fill:#fff3cd,stroke:#ffc107,color:#856404
    classDef old     fill:#f8d7da,stroke:#dc3545,color:#721c24
    class v3 current
    class v2 past
    class v1 old
Loading

Versions are linear per entity — each new version supersedes the previous; there is no branching. The "fork" effect happens across entities: when a remember() falls below the similarity threshold, it creates a new entity instead of a new version. Linearity is what keeps queryAt(t) well-defined: there is always one unambiguous answer to "what did we believe about entity X at time T."


Every change leaves a breadcrumb. Important moments become checkpoints.

Every remember() writes a trail event with who did it, why, when it became effective, and when it was ingested. Nothing extra to call. The trail is read-only and pure — trail() projects it from data already on disk.

const policy = kalairos.scope({ source: { type: "agent", actor: "policy-bot" } });

await policy.remember("Employees must submit reports by Friday");
await policy.remember("Deadline changed to Wednesday", {
  why:         "Policy update from HR memo",
  effectiveAt: "2026-04-15",
});

const events = await kalairos.trail({ action: ["remembered", "superseded"] });
// → [{ action: "remembered", who: { agent: "policy-bot" }, ingestAt, effectiveAt, ... },
//    { action: "superseded", who: { agent: "policy-bot" }, why: "Policy update from HR memo", ... }]

Each event is one of a closed set: remembered, superseded, corrected, contested, reaffirmed, forgotten, restored, imported, annotated. Switch on it without guessing.

When a moment matters — quarter close, audit cut, model ship — name it:

await kalairos.checkpoint("q1-close", {
  during: ["2026-01-01", "2026-04-01"],
  why:    "Q1 audit reference",
});

const q1 = await kalairos.trail({ checkpoint: "q1-close" });
const lastWeek = Date.now() - 7 * 86_400_000;
const past     = await kalairos.queryAt("report deadline", lastWeek);

Checkpoints are frozen by default — backdated writes do not silently join them. Pass live: true if you want the filter to re-evaluate on every read.


Where this earns its keep

Scenario The pain What queryAt proves
Policy change "I was penalized for breaking a rule that didn't exist when I acted." The rule that applied on the date of the action.
Pricing dispute "I signed up at $10. Why am I being charged $20?" The price at the moment of signup.
Compliance audit "What was your data retention policy on March 12?" The policy as it stood on March 12.
Engineering review Old code flagged against rules that didn't exist when it was written. The rule as it was when the code was committed.
Drifting AI agent Assistant flips between contradictory user preferences with no record of why. The preference at any past turn — and the full trail of changes.

If your product makes promises that outlive the moment, you need memory that does too.


A compliance audit, end to end

The scenarios above, as one runnable script. A policy pipeline records a regulated retention rule; the regulation changes; an auditor asks about a date in between. Provenance, classification, contradiction flagging, human review, audit trail, and a frozen checkpoint — all against the same store the 3-call quickstart uses.

const kalairos = require('kalairos');

async function main() {
  const embed = async (t) => [...t].map(c => c.charCodeAt(0) / 255).slice(0, 64); // toy embedder
  await kalairos.init({ embedFn: embed, dataFile: ':memory:' });

  // Every write from this pipeline carries provenance + data classification.
  const policy = kalairos.scope({
    source: { type: 'agent', actor: 'policy-sync' },
    classification: 'regulated',
    tags: ['retention-policy'],
  });

  // January: the policy on the books.
  const id = await policy.remember('Customer records are retained for 5 years', {
    timestamp: new Date('2026-01-05').getTime(),
  });

  // April: the regulation changes. Same entity, new version — the old rule is not gone.
  await policy.remember('Customer records are retained for 7 years', {
    timestamp: new Date('2026-04-01').getTime(),
    why: 'Reg update 2026-113',
    effectiveAt: '2026-04-01',
  });

  // The auditor asks: "What was your retention rule on March 12?"
  const march12 = new Date('2026-03-12').getTime();
  const audit = await kalairos.queryAt('customer record retention rule', march12);
  console.log(audit.results[0].text);
  // → "Customer records are retained for 5 years"

  // Today's answer comes with explainable trust — the change was flagged, not silently absorbed.
  const today = await kalairos.query('customer record retention rule');
  console.log(today.results[0].text);
  console.log(today.results[0].trustBreakdown.formula);
  // → "Customer records are retained for 7 years"
  // → "Trust 0.30 = base 0.75 -0.16 contradictions ×0.50 recency"

  // A human reviews the flagged change and confirms it — on the record.
  await kalairos.annotate(id, {
    trustScore: 0.9,
    verified: true,
    notes: 'Confirmed against Reg update 2026-113 by compliance review',
  });

  // Evidence, not assertion: who changed what, when, and why.
  const events = await kalairos.trail({ entity: id });
  events.forEach(e => console.log(e.action, e.who, e.why ?? ''));
  // → remembered { agent: 'policy-sync' }
  // → superseded { agent: 'policy-sync' } Reg update 2026-113
  // → contested  { agent: 'policy-sync' } Reg update 2026-113
  // → annotated  ...

  // Freeze the audit window: reviewers see the same slice forever.
  await kalairos.checkpoint('fy26-q1-audit', {
    during: ['2026-01-01', '2026-04-01'],
    why: 'Q1 retention audit reference',
  });
  const q1 = await kalairos.trail({ checkpoint: 'fy26-q1-audit' });
  console.log(`${q1.length} events in the frozen Q1 window`);

  await kalairos.shutdown();
}

main().catch(e => { console.error(e); process.exit(1); });

Note the trust formula: the 5-year → 7-year change contradicts the prior version, so Kalairos flags it and cuts trust until a human confirms it. That's deliberate — a policy that changes without anyone noticing is exactly the failure mode a regulated deployment cannot have.


Layer 3 — Advanced maintenance

Reach for these only when you need them. The 3-function path covers most agents.

Trust and annotations

Every query result includes a trust score and a source provenance chain. Annotate an entity with trust signals without creating a new content version:

await kalairos.annotate(id, { trustScore: 0.9, verified: true, notes: 'confirmed by finance' });

Consolidation

Merge near-duplicate memories into a single entity. Useful at the end of long sessions.

const { merged, totalMerged } = await kalairos.consolidate({ threshold: 0.9 });

Contradiction inspection

When a new version contradicts a prior one, the delta is flagged. Surface the conflict instead of silently overwriting:

const { contradictions } = await kalairos.getContradictions(id);
contradictions.forEach(v => console.log(`v${v.versionId}: ${v.delta.summary}`));

Provenance defaults with scope()

If every write from one part of your agent shares the same source / classification / tags, scope() pre-fills them so you don't pass them every call. Reads behave exactly like the flat API.

const support = kalairos.scope({
  source: { type: 'agent', actor: 'support-bot' },
  classification: 'confidential',
  tags: ['support'],
});

await support.remember('Customer reports checkout is broken on mobile');
const { results } = await support.query('checkout');

scope() is optional sugar. Everything it does is expressible with the flat API.


What Kalairos gives you

Plain vector store Kalairos
Updates Overwrite or duplicate Automatic versioning
History None Full version trail with deltas
"What was true on Jan 15?" Can't answer queryAt any timestamp
Contradictions Invisible Auto-detected between versions
Provenance Not tracked Who stored it, when, from where
Retrieval Cosine similarity Semantic + graph + keyword + recency
Deployment Cloud SDK Local-first, zero cloud dependency
Embedding model Bundled or locked in BYO — any provider, any model

API stability

Kalairos follows semver. Within the 1.x line, the signatures of init, remember, query, queryAt, getHistory are frozen. Additive fields land in minor releases; any breaking change bumps the major version. Deprecated APIs emit warnings for at least two minor versions before removal.


Benchmarks

All numbers reproducible on any machine — deterministic bag-of-words embedder, no API key needed. Last full run: 2026-07-02 (M-series macOS, Node v23.7.0); the dated snapshot lives in bench/RESULTS.md.

Metric Score What it measures
Recall@5 75% (finance), 50% (engineering) Fraction of relevant items in top-5 results
Precision@3 100% (health) Fraction of top-3 results that are relevant
MRR 1.0 First relevant result appears at rank 1
Temporal accuracy 100% queryAt time-travel returns correct historical version
Contradiction detection 100% Conflicting facts flagged; metric drift correctly exempted
Cross-session recall 100% Memories persist across agent sessions and agents
Poisoning defense 5/5 attacks defended Injected facts flagged, trust-penalized, asOf-recoverable
Query latency (p95) 5.2 ms @ 1k · 64.8 ms @ 10k entities End-to-end query() on the JSONL + SQLite store
Observability completeness 1.000 Fraction of agent actions visible in the control-plane ledger
Cross-agent trace coverage 1.000 Handoffs reconstructible end-to-end (caller, callee, payload, outcome)

These numbers use a bag-of-words embedder (no neural model). With OpenAI text-embedding-3-small or Cohere embeddings, expect recall@5 > 90%.

npm run bench            # memory suite (75 assertions, 10 constitution goals)
npm run bench:poisoning  # adversarial ingest fixtures
npm run bench:latency    # latency at 1k / 10k entities
npm run bench:real       # real embeddings (requires OPENAI_API_KEY)

See BENCH.md for method, budgets, and CI gates — including the one number we publish while missing it (10k p95 is 64.8 ms against a 50 ms budget, down from 321 ms pre-v1.7).


API reference

Lifecycle

await kalairos.init({ embedFn, llmFn?, embeddingDim?, dataFile?, ...overrides })
await kalairos.shutdown()

Write

await kalairos.remember(text, opts?)
await kalairos.ingest(text, opts?)                  // power-user / batch path
await kalairos.ingestBatch(items)
await kalairos.ingestFile(filePath, opts?)
await kalairos.ingestTimeSeries(label, points, opts?)

Options: { type, timestamp, metadata, tags, source, classification, retention, memoryType, workspaceId, useLLM, importance, forceNew }.

Read

await kalairos.query(text, { limit?, maxTokens?, filter? })
await kalairos.queryAt(text, timestamp, { limit?, maxTokens?, filter? })
await kalairos.queryRange(text, since, until, { limit?, maxTokens?, filter? })
await kalairos.get(id)
await kalairos.getMany(ids)
await kalairos.getHistory(id)
await kalairos.listEntities({ page?, limit?, type?, since?, until?, tags?, memoryType?, workspaceId? })
await kalairos.getGraph()
await kalairos.traverse(id, depth?)
await kalairos.getStatus()

Delete

await kalairos.remove(id, { deletedBy? })           // soft delete
await kalairos.forget(id, { reason?, who? })        // first-class verb, audited
await kalairos.restore(id, { reason?, who? })       // reverse a forget
await kalairos.purge(id)                            // permanent hard delete

Audit trail and checkpoints

await kalairos.trail({ entity?, since?, until?, action?, who?, checkpoint?, limit? })
await kalairos.checkpoint(name, { during?, entity?, tags?, workspace?, why?, live? })
await kalairos.getCheckpoint(name)
await kalairos.listCheckpoints({ workspace? })

Maintenance

await kalairos.annotate(id, { trustScore?, verified?, notes?, memoryType? })
await kalairos.consolidate({ threshold?, dryRun? })
await kalairos.getContradictions(id)
await kalairos.getDrift(id)

Scoped handle

const scope = kalairos.scope({ source?, classification?, tags?, memoryType?, workspaceId? })
await scope.remember(text, opts?)
await scope.query(text, opts?)
await scope.queryAt(text, timestamp, opts?)
await scope.getHistory(id)
await scope.getContradictions(id)
await scope.annotate(id, opts?)
await scope.remove(id, { reason?, deletedBy? })

Signals

kalairos.onSignal(code, callback)
kalairos.getSignals(code?)

Grouped namespaces (advanced)

Every API above is also reachable through topic namespaces. The flat calls remain canonical for the 1.x line — namespaces are an organizational aid for codebases that prefer grouped imports.

kalairos.history    // getHistory, getChangeSince, getContradictions, getDrift,
                    // buildChangelog, trail, checkpoint, getCheckpoint, listCheckpoints
kalairos.trust      // annotate
kalairos.graph      // getGraph, traverse, consolidate
kalairos.io         // ingestBatch, ingestFile, ingestTimeSeries,
                    // exportMarkdown, importMarkdown
kalairos.signals    // onSignal, getSignals
kalairos.auth       // workspace ACL
import { history, io } from "kalairos";
await io.ingestFile("./notes.md");
const trail = await history.trail({ entity: id });

Configuration

Variable Default Description
KALAIROS_LINK_THRESHOLD 0.72 Similarity threshold for graph linking
KALAIROS_VERSION_THRESHOLD 0.82 Similarity threshold for version detection
KALAIROS_GRAPH_BOOST 0.01 Graph relationship boost weight
KALAIROS_LLM_BOOST 0.08 LLM keyword boost weight
KALAIROS_IMPORTANCE_WEIGHT 0.05 Importance boost weight in query scoring
KALAIROS_RECENCY_WEIGHT 0.10 Recency boost weight
KALAIROS_RECENCY_HALFLIFE_DAYS 30 Recency half-life in days
KALAIROS_MIN_SCORE 0.45 Minimum final score for results
KALAIROS_MIN_SEMANTIC 0.35 Minimum semantic similarity
KALAIROS_MAX_VERSIONS 0 Max versions per entity (0 = unlimited)
KALAIROS_STRICT_EMBEDDINGS 1 Require embedder (0 to disable)
KALAIROS_PORT 3000 HTTP server port

Storage

  • Persisted locally to data.kalairos (configurable via dataFile)
  • Atomic writes to reduce corruption risk
  • Pass dataFile: ":memory:" for in-memory-only mode

HTTP server

npx kalairos          # starts on localhost:3000

Core endpoints mirror the flat API (/remember, /query, /history/:id, etc.). See server.js for the full route list.


Markdown export / import

Memory is portable and diffable.

npx kalairos export --out memory.md --include-history   # dump to markdown
npx kalairos import memory.md                           # ingest back

Checkpoint it into git, share it across agents, hand-edit it when debugging.


Migration note — createAgent()scope()

createAgent() is deprecated. It still works, and emits a one-time deprecation warning on first call. Removal is targeted for 2.0.

// before
const agent = kalairos.createAgent({
  name: 'budget-planner',
  defaultClassification: 'confidential',
  defaultTags: ['finance'],
});
await agent.remember('Q2 budget is 2.4M');
await agent.recall('Q2 budget');

// after
const scope = kalairos.scope({
  source: { type: 'agent', actor: 'budget-planner' },
  classification: 'confidential',
  tags: ['finance'],
});
await scope.remember('Q2 budget is 2.4M');
await scope.query('Q2 budget');

Old method names on the handle (recall, recallAt, update, forget, boot, learnFrom, consolidateSession) remain as silent aliases on scope() for back-compat.


Feedback

We'd love to hear how you're using Kalairos — what works, what's missing, what you'd build on top of it.

Reach us at main@krishnalabs.ai


License

MIT — KrishnaLabs