Paper387 questions on real Jira projects, open results

The context layer for the agents inside your product

Keel turns your product's events into one graph per customer that remembers what was true and when. Every agent you build reads it through the same five tools, over MCP, HTTP or a typed SDK, so you stop rebuilding the pipeline for each one.

Read the docs Read the paper
tenant: spring · mcp
YouWhich issues in Spring are blocked, and was SPR-142 already blocked when we triaged on Oct 5?
→search(kind="issue", rule="blocked")
7 issues: issue:SPR-142, issue:SPR-311, issue:SPR-407, …
Agent7 issues are blocked. SPR-142 has been blocked by SPR-17 since Oct 4, but Keel only learned it on Oct 6 from a late event: the Oct 5 triage saw it as In progress.

Tool calls

3tool calls, no rows pasted into the prompt

An illustrative session over the benchmark's Spring tenant.

Agents, over MCP or HTTP
  • Claude Code
  • Codex
  • LangGraph
  • Claude Agent SDK
  • Google ADK
Events from
  • Postgres CDC
  • Kafka
  • SQS
  • Webhooks
  • Outbox

Four agents shouldn't mean four pipelines

Every new agent repeats the same work: extract the customer's data, decide what "blocked" or "at risk" means, keep its own memory. Keel does it once, from your events.

Without KeelN pipelines

Each agent has its own pipeline, its own rules and its own memory.

With Keelone layer

One deterministic ingest. Every agent reads the same graph through the same five tools.

How it works

  1. Send your events

    From Postgres CDC, an outbox, Kafka, SQS, webhooks or the SDK.

  2. Declare a cartridge

    Your entities, events and rules in YAML, plus ten-line handlers. Typed SDKs come out of it.

  3. Keel builds the graph

    One bitemporal graph per tenant. No LLM in ingest; replay gives the same graph.

  4. Agents read five tools

    context, history, search, related, annotate. Rules arrive evaluated.

rules

Define "blocked" once. Every agent gets the verdict.

Rules live in the cartridge, not in prompts. Keel evaluates them over the whole graph, so the model receives the answer instead of working it out from raw facts.

  • 26 points more right on whole-project rule questions with both GPT-6 Luna and Claude Haiku 5.5.
  • Fewer tokens: 77k against 149k a question with Luna, 60k against 78k with Haiku.
  • Rules compose: all, any, not, follow relations with via, measure durations.

"Which issues in the project are blocked right now?" The same model and the same tools, with and without the cartridge's rule.

No rulethe model applies the definition

input tokens149k
67% correct

rule: blockedKeel evaluates

input tokens77k
93% correct
1.6–2.7× cheaper per correct answer

Illustrative graph; numbers shown are GPT-6 Luna's. Source: the paper's rules ablation, 18 whole-project questions that use the blocked, stalled and urgent_unassigned rules, three repetitions.

bitemporal

Know what was true, and when you knew it

Every fact carries when it was true and when Keel learned it. Late events and corrections close facts instead of overwriting them, so an agent can audit what another agent saw when it decided.

  • as_of and as_known_at on every read.
  • Deterministic replay: the same log gives the same graph, in any arrival order.
  • Provenance on every fact: source, evidence, who corrected it.
events.jsonlarrives Oct 6
{ "id": "spr-142-st-7",
  "tenant": "spring",
  "type": "issue.status_changed",
  "subject": "issue:SPR-142",
  "occurred_at": "2026-10-04T09:12:00Z",
  "recorded_at": "2026-10-06T16:40:00Z",
  "source": "jira",
  "payload": { "status": "Blocked",
               "status_category": "indeterminate" } }
occurred_at true since Oct 4recorded_at learned on Oct 6
memory

Memory with scopes, enforced by the layer

Agents write what they learn with annotate. The cartridge says who may write which scope, how long it lives and what counts as evidence. The prompt can't talk its way past it.

  • 0% of answers repeat a poisoned write with Keel's checks (the evidence check as fixed after the run), against 91–98% without.
  • 0 leaks in 26,666 random reads across tenants, scopes and agents.
  • One graph per tenant: no tool can return another tenant's facts.
agent.pyPython
tools.annotate("issue:SPR-142", "root_cause", "vendor patch",
               scope="tenant")   # no evidence
raisesPermissionError
writing to scope 'tenant' requires evidence
(an event id, a row ref, or a human correction)
agent.pyPython
tools.annotate("issue:SPR-142", "root_cause", "vendor patch",
               scope="tenant", evidence=late_event.id)
# stored; every agent of this tenant can read it
sdk

Your cartridge, as a typed client in your language

keel sdk writes one file for your product's repo, the way protoc does: a type per event payload, a constructor per event, subject helpers, the rules, and a client for the five tools. Your compiler catches a payload that drifts from the catalog before Keel ever sees it.

  • TypeScript, Python, Go and PHP, each with a small published runtime and tested end to end against keel serve.
  • --check in CI fails the build when the cartridge moves on and the generated file is stale.
  • One contract: the same cartridge drives ingest, the tools and every client.
$ keel sdk typescript cartridges/jira -o src/keel-jira.ts
import { client, emit, subjects } from "./keel-jira";

const keel = client({ baseUrl, tenant: "spring", agent: "triage" });

await keel.events([emit.issueStatusChanged({
  tenant: "spring", subject: subjects.issue("SPR-142"),
  occurredAt: new Date(), source: "jira",
  payload: { status: "Blocked", status_category: "blocked" },
})]);   // ✗ Type '"blocked"' is not assignable to
        //   '"new" | "indeterminate" | "done" | null'

const ctx = await keel.context(subjects.issue("SPR-142"));
ctx.evaluation.blocked;   // true

Five tools. Only five.

The names and signatures are part of the public standard. New capabilities go into these five, never into a sixth, so your agents' prompts don't change when Keel does.

context

What Keel knows about a subject now, or at a past moment, with every rule's verdict.

callMCP · HTTP · SDK
context("issue:SPR-142")

Measured, with the results public

387 questions over three Public Jira Dataset projects, with late arrivals and corrections injected, graded against an independent oracle. Three agents.

Accuracy on whole-project questions

GPT-6 Luna

Keel
93%
API
61%
RAG
21%

Claude Haiku 5.5

Keel
93%
API
65%
RAG
14%

Claude Sonnet 5.5

Keel
99%
API
78%
RAG
27%

Sonnet ran once, and so did Luna's RAG; the rest are three repetitions. Sonnet's RAG run is partial (34 of 72 questions).

+21 to +32 points

over the product's own API on whole-project questions, with all three agents (p < 0.001), at the lowest cost per correct answer.

19%

for TGMS on the same questions with GPT-6 Luna, reading 741k tokens a question.

+3 points

over the API on single-issue questions with Luna and Haiku (p < 0.001), a tie with Sonnet; every system that reads the log scores 0.95 to 0.99 there.

Keel: one deterministic context layer for product agents

Angel Kürten, 2026 · Zenodo · CC BY 4.0

Where Keel does not win is in the paper too: a few small tables that fit in a prompt do as well pasted in.

BibTeX
@misc{kurten2026keel,
  author    = {K{\"u}rten, Angel},
  title     = {Keel: one deterministic context layer
               for product agents},
  year      = {2026},
  publisher = {Zenodo},
  doi       = {10.5281/zenodo.23571090}
}

Running in three commands

An example cartridge and its events ship with the repository. Point Claude Code, Codex or any MCP client at it.

  1. Install

    Python 3.10+, or run the container.

    git clone https://github.com/angelkurten/keel
    cd keel && pip install keel-context
  2. Validate and load

    Check the events against the cartridge, then build the graph.

    keel validate examples/fleet \
      --events examples/fleet/events.jsonl
    keel ingest examples/fleet \
      examples/fleet/events.jsonl
  3. Serve the five tools

    Over MCP for your agent, or HTTP for your backend.

    claude mcp add keel -- keel serve \
      examples/fleet --tenant acme-logistics \
      --events examples/fleet/events.jsonl

Pilot it on your own events

Your events, your agents, one day to the first answer. We help you through the cartridge and fix what blocks you in the core.