keel-context 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- keel_context-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +24 -0
- keel_context-0.1.0/.github/ISSUE_TEMPLATE/cartridge_proposal.yml +23 -0
- keel_context-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +11 -0
- keel_context-0.1.0/.github/workflows/docs.yml +27 -0
- keel_context-0.1.0/.github/workflows/release.yml +26 -0
- keel_context-0.1.0/.gitignore +9 -0
- keel_context-0.1.0/CLAUDE.md +81 -0
- keel_context-0.1.0/CONTRIBUTING.md +58 -0
- keel_context-0.1.0/LICENSE +202 -0
- keel_context-0.1.0/NOTICE +4 -0
- keel_context-0.1.0/PKG-INFO +178 -0
- keel_context-0.1.0/README.md +139 -0
- keel_context-0.1.0/ROADMAP.md +66 -0
- keel_context-0.1.0/cartridges/clinics/cartridge.yaml +45 -0
- keel_context-0.1.0/cartridges/clinics/handlers.py +28 -0
- keel_context-0.1.0/cartridges/crm/cartridge.yaml +50 -0
- keel_context-0.1.0/cartridges/crm/handlers.py +35 -0
- keel_context-0.1.0/cartridges/finance/cartridge.yaml +83 -0
- keel_context-0.1.0/cartridges/finance/handlers.py +59 -0
- keel_context-0.1.0/cartridges/fleet/cartridge.yaml +101 -0
- keel_context-0.1.0/cartridges/fleet/handlers.py +83 -0
- keel_context-0.1.0/docs/SPEC.md +353 -0
- keel_context-0.1.0/docs/connect.md +170 -0
- keel_context-0.1.0/docs/contributing.md +1 -0
- keel_context-0.1.0/docs/demo.gif +0 -0
- keel_context-0.1.0/docs/first-cartridge.md +119 -0
- keel_context-0.1.0/docs/index.md +93 -0
- keel_context-0.1.0/docs/roadmap.md +1 -0
- keel_context-0.1.0/examples/agent_minimal.py +35 -0
- keel_context-0.1.0/examples/demo.tape +53 -0
- keel_context-0.1.0/examples/events/clinics.jsonl +7 -0
- keel_context-0.1.0/examples/events/crm.jsonl +4 -0
- keel_context-0.1.0/examples/events/finance.jsonl +14 -0
- keel_context-0.1.0/examples/events/fleet.jsonl +13 -0
- keel_context-0.1.0/keel/__init__.py +28 -0
- keel_context-0.1.0/keel/adapters/__init__.py +7 -0
- keel_context-0.1.0/keel/adapters/postgres.py +108 -0
- keel_context-0.1.0/keel/adapters/webhook.py +38 -0
- keel_context-0.1.0/keel/cartridge.py +151 -0
- keel_context-0.1.0/keel/cli.py +182 -0
- keel_context-0.1.0/keel/envelope.py +72 -0
- keel_context-0.1.0/keel/handlers.py +56 -0
- keel_context-0.1.0/keel/ingest.py +79 -0
- keel_context-0.1.0/keel/mcp_server.py +67 -0
- keel_context-0.1.0/keel/rules.py +85 -0
- keel_context-0.1.0/keel/scopes.py +58 -0
- keel_context-0.1.0/keel/store/__init__.py +4 -0
- keel_context-0.1.0/keel/store/base.py +86 -0
- keel_context-0.1.0/keel/store/memory.py +163 -0
- keel_context-0.1.0/keel/store/sqlite.py +196 -0
- keel_context-0.1.0/keel/tools.py +132 -0
- keel_context-0.1.0/mkdocs.yml +51 -0
- keel_context-0.1.0/pyproject.toml +52 -0
- keel_context-0.1.0/tests/test_adapters.py +110 -0
- keel_context-0.1.0/tests/test_core.py +191 -0
- keel_context-0.1.0/tests/test_store.py +131 -0
- keel_context-0.1.0/uv.lock +2549 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: Bug report
|
|
2
|
+
description: The core, the CLI or a shipped cartridge does not behave as documented.
|
|
3
|
+
labels: [bug]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: what
|
|
7
|
+
attributes:
|
|
8
|
+
label: What happened
|
|
9
|
+
description: What you did, what you expected, what you got. A failing test or a `keel` command is ideal.
|
|
10
|
+
validations:
|
|
11
|
+
required: true
|
|
12
|
+
- type: textarea
|
|
13
|
+
id: events
|
|
14
|
+
attributes:
|
|
15
|
+
label: Events to reproduce
|
|
16
|
+
description: The smallest `.jsonl` stream (or table rows) that triggers it. Strip anything confidential.
|
|
17
|
+
render: json
|
|
18
|
+
- type: input
|
|
19
|
+
id: version
|
|
20
|
+
attributes:
|
|
21
|
+
label: Keel version and Python version
|
|
22
|
+
placeholder: keel 0.0.1, Python 3.12
|
|
23
|
+
validations:
|
|
24
|
+
required: true
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: Cartridge proposal
|
|
2
|
+
description: Propose a new vertical, or report that writing one needed a change under keel/.
|
|
3
|
+
labels: [cartridge]
|
|
4
|
+
body:
|
|
5
|
+
- type: input
|
|
6
|
+
id: vertical
|
|
7
|
+
attributes:
|
|
8
|
+
label: Vertical
|
|
9
|
+
placeholder: clinics, last-mile logistics, property management
|
|
10
|
+
validations:
|
|
11
|
+
required: true
|
|
12
|
+
- type: textarea
|
|
13
|
+
id: ontology
|
|
14
|
+
attributes:
|
|
15
|
+
label: Entities, relations and the three most important event types
|
|
16
|
+
description: A sketch is enough. See cartridges/fleet/cartridge.yaml for the shape.
|
|
17
|
+
validations:
|
|
18
|
+
required: true
|
|
19
|
+
- type: textarea
|
|
20
|
+
id: core
|
|
21
|
+
attributes:
|
|
22
|
+
label: Did the core get in the way?
|
|
23
|
+
description: Anything under keel/ you had to change or wished existed. This is the most valuable part.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
## What
|
|
2
|
+
|
|
3
|
+
## Why
|
|
4
|
+
|
|
5
|
+
## Checklist
|
|
6
|
+
|
|
7
|
+
- [ ] `uv run pytest` and `uv run ruff check .` pass
|
|
8
|
+
- [ ] `cartridges/fleet` and `cartridges/crm` still validate without edits
|
|
9
|
+
- [ ] Core change: a test in `tests/` covers it. Cartridge change: events in `examples/events/` and assertions.
|
|
10
|
+
- [ ] Nothing under `keel/` names an entity kind, relation, predicate, rule or event type
|
|
11
|
+
- [ ] If `examples/agent_minimal.py` output changed, the reason is in this description
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
paths:
|
|
7
|
+
- docs/**
|
|
8
|
+
- mkdocs.yml
|
|
9
|
+
- ROADMAP.md
|
|
10
|
+
- CONTRIBUTING.md
|
|
11
|
+
- .github/workflows/docs.yml
|
|
12
|
+
workflow_dispatch:
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: write
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
deploy:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
- run: |
|
|
23
|
+
git config user.name github-actions[bot]
|
|
24
|
+
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
|
|
25
|
+
- uses: astral-sh/setup-uv@v5
|
|
26
|
+
- run: uv sync --extra docs
|
|
27
|
+
- run: uv run mkdocs gh-deploy --force
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
# Publishes to PyPI with trusted publishing (no token stored here) when a v* tag is pushed.
|
|
4
|
+
# One-time setup on PyPI: add a pending publisher for project keel-context, owner angelkurten,
|
|
5
|
+
# repository keel, workflow release.yml, environment pypi.
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
tags: ["v*"]
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
pypi:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
environment: pypi
|
|
18
|
+
permissions:
|
|
19
|
+
id-token: write
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
- uses: astral-sh/setup-uv@v5
|
|
23
|
+
- run: uv sync --extra dev
|
|
24
|
+
- run: uv run pytest -q
|
|
25
|
+
- run: uv build
|
|
26
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Keel — project spec for coding agents
|
|
2
|
+
|
|
3
|
+
Read this before changing anything. It says what Keel is, what it is not, and the rules that keep the core generic. `ROADMAP.md` says what comes next.
|
|
4
|
+
|
|
5
|
+
## What Keel is
|
|
6
|
+
|
|
7
|
+
A context layer for AI agents inside vertical SaaS products. Domain events from the SaaS go in; a bitemporal context graph **per end-customer** (the tenant of the tenant) comes out; every agent reads and writes it through the same five tools.
|
|
8
|
+
|
|
9
|
+
The domain is declared once in a **cartridge** (YAML + a few ten-line handlers). The core knows nothing about any domain.
|
|
10
|
+
|
|
11
|
+
Positioning, in one line: *stop rebuilding the pipeline for every agent.* "Shared memory for agents" is the consequence, never the headline.
|
|
12
|
+
|
|
13
|
+
## What Keel is not
|
|
14
|
+
|
|
15
|
+
- Not an agent framework. Agents live in LangGraph, ADK, the Claude SDK, wherever. Keel is what they consult.
|
|
16
|
+
- Not a document/RAG memory. We ingest structured events with time, not text. Ingestion is deterministic. No LLM in the ingest path (see Open decisions).
|
|
17
|
+
- Not a copy of the SaaS's database. The graph holds pointers, semantics and earned memory.
|
|
18
|
+
- Not a single global memory. Memory has scopes and nothing is promoted by accident.
|
|
19
|
+
|
|
20
|
+
## Architecture
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
sources (Postgres table/CDC, webhooks, Kafka later)
|
|
24
|
+
-> Event envelope (keel/envelope.py) tenant, type, subject, occurred_at, recorded_at, source, payload
|
|
25
|
+
-> Ingestor (keel/ingest.py) reads an adapter, dispatches by event.type
|
|
26
|
+
-> HandlerRegistry (keel/handlers.py) cartridge handlers map payload -> store primitives
|
|
27
|
+
-> Store (keel/store/) bitemporal: entities, relations, facts; one subgraph per tenant (memory or sqlite)
|
|
28
|
+
-> Tools (keel/tools.py) context / history / search / annotate / related
|
|
29
|
+
-> MCP server (keel/mcp_server.py) optional; exposes the same five tools
|
|
30
|
+
cartridges/<name>/cartridge.yaml + handlers.py ontology, event catalog, rules, memory policy, handlers
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Store primitives are the only write path: `upsert_entity`, `link`, `unlink`, `open_fact`, `close_fact`. `open_fact` closes the previous open fact on the same predicate; nothing is overwritten.
|
|
34
|
+
|
|
35
|
+
## Design rules (enforce these in review)
|
|
36
|
+
|
|
37
|
+
1. **Events are the truth.** The log is append-only; the graph is derived and must be rebuildable with `Ingestor.replay(tenant)`. Any state that cannot be rebuilt from events is a bug.
|
|
38
|
+
2. **Pointers, not copies.** An entity carries `ref` to the row in the system of record and the few attributes a tool needs to label it. If a PR copies a whole row or table into the graph, reject it.
|
|
39
|
+
3. **Explicit time, bitemporal.** `valid_from`/`valid_to` = world time. `recorded_at` = when Keel learned it. Facts close; they are never mutated or deleted by handlers. "The tire was critical on Oct 3" and "the tire is critical" are different queries and both must work.
|
|
40
|
+
4. **Provenance on every fact.** `source`, `evidence` (event id, row ref, photo), and `corrected_by` when a human or agent corrected it.
|
|
41
|
+
5. **Scopes, not a flat store.** `global` > `tenant` > `task` > `agent`. Policy (TTL, evidence requirement) is enforced in `Tools.annotate` via `MemoryPolicy`, never trusted to the calling agent. Promotion to a wider scope is an explicit `annotate` with evidence.
|
|
42
|
+
6. **The core is domain-agnostic.** No entity kind, relation name, predicate, rule or event type appears anywhere under `keel/`. The acceptance test for any core change: both `cartridges/fleet` and `cartridges/crm` still pass without edits. Adding a third cartridge must require zero changes under `keel/`.
|
|
43
|
+
7. **Five tools, and only five.** New capabilities go into the semantics of these five (richer `context`, filters on `history`) before anyone adds a sixth. The tool names and signatures are part of the public standard.
|
|
44
|
+
8. **One subgraph per tenant.** Every store call takes `tenant` first. Cross-tenant reads do not exist. A tool instance is bound to one tenant.
|
|
45
|
+
9. **Handlers are small.** A handler maps one payload to primitives in ~10 lines. Business logic (what "critical" means) lives in `rules`, read by tools, not in handlers and not in agents.
|
|
46
|
+
10. **Deterministic ingest.** No network calls, no LLM calls, no randomness in the ingest path. Replay must produce an identical graph.
|
|
47
|
+
|
|
48
|
+
## Conventions
|
|
49
|
+
|
|
50
|
+
- Python 3.10+, `uv`, `pytest`, `ruff` (line length 100). Type hints everywhere; pydantic for the envelope only, dataclasses in the store.
|
|
51
|
+
- Subject keys are `<kind>:<id>` strings. Event types are dotted lowercase: `inspection.created`.
|
|
52
|
+
- Datetimes are timezone-aware UTC. Naive datetimes are rejected at the envelope.
|
|
53
|
+
- Public surface: `keel/__init__.py` exports. Anything else is internal and may change.
|
|
54
|
+
- All user-facing text (README, docs, CLI help, errors) is in English. Commit messages in English.
|
|
55
|
+
- Tests: every core change adds or updates a test in `tests/`; cartridge changes add events to `examples/events/*.jsonl` and assertions in `tests/test_core.py`.
|
|
56
|
+
|
|
57
|
+
## How to verify a change
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
uv sync --extra dev
|
|
61
|
+
uv run pytest
|
|
62
|
+
uv run keel validate cartridges/fleet && uv run keel validate cartridges/crm
|
|
63
|
+
uv run python examples/agent_minimal.py
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
If `examples/agent_minimal.py` output changes, say why in the PR.
|
|
67
|
+
|
|
68
|
+
## Open decisions (do not resolve silently)
|
|
69
|
+
|
|
70
|
+
- **Durable store.** Decided 2026-10-14: pluggable by design, never a core change. `Store` protocol (`keel/store/base.py`), `MemoryStore` as reference, `SqliteStore` as the default durable one, and a conformance suite any store can run (`KEEL_STORE=pkg.module:factory uv run pytest tests/test_store.py`). Graphiti or Postgres would be third implementations; if Graphiti forces an LLM in the ingest path, use only its graph store and keep our deterministic ingest.
|
|
71
|
+
- **Consolidation/decay.** Expiry is shipped (`expires_at`, `store.expire`, `keel expire`). Summarizing old episodic facts into durable ones is deliberately left open (2026-10-14) until a pilot brings a real case; the leading candidate is a deterministic job, never an LLM in the core.
|
|
72
|
+
- **Multi-cartridge tenants.** One tenant, one cartridge for now. Composing cartridges is a later decision.
|
|
73
|
+
- **Pricing unit** (hosted): events ingested per month. Not a code concern yet, but metering must be possible from the log.
|
|
74
|
+
|
|
75
|
+
## Things to never do
|
|
76
|
+
|
|
77
|
+
- Never add a dependency to the core for one cartridge's convenience.
|
|
78
|
+
- Never let a tool return facts from another tenant.
|
|
79
|
+
- Never delete or mutate a fact from a handler. Close it.
|
|
80
|
+
- Never read `event.payload` in `keel/`. Only handlers do. One exception: `Cartridge.check_event` (behind `keel validate --events`) compares payload keys and types with the catalog; it never reads values.
|
|
81
|
+
- Never write generic placeholders (`TBD`, `n/a`) into cartridge YAML; leave the key out.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Contributing to Keel
|
|
2
|
+
|
|
3
|
+
Keel is built in the open, part-time. Small, focused pull requests land fastest.
|
|
4
|
+
|
|
5
|
+
## Set up
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv sync --extra dev
|
|
9
|
+
uv run pytest
|
|
10
|
+
uv run keel validate cartridges/fleet --events examples/events/fleet.jsonl && uv run keel validate cartridges/crm --events examples/events/crm.jsonl && uv run keel validate cartridges/finance --events examples/events/finance.jsonl && uv run keel validate cartridges/clinics --events examples/events/clinics.jsonl
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## What we are looking for
|
|
14
|
+
|
|
15
|
+
- **Cartridges.** The best contribution. A new vertical (clinics, logistics, property management, ...) in `cartridges/<name>/` with `cartridge.yaml`, `handlers.py`, a sample stream in `examples/events/<name>.jsonl` and a test in `tests/`. It must pass `keel validate` and require zero changes under `keel/`. If it does need a core change, open an issue first: that is a bug in the core, and we want to fix it generically.
|
|
16
|
+
- **Adapters.** Sources that yield `Event`s (see `keel/adapters/`). An adapter never reads a payload.
|
|
17
|
+
- **Bug reports with a failing test.** The fastest way to get something fixed.
|
|
18
|
+
|
|
19
|
+
## Rules the review enforces
|
|
20
|
+
|
|
21
|
+
`CLAUDE.md` is the spec and `docs/SPEC.md` is the normative contract. The short version:
|
|
22
|
+
|
|
23
|
+
1. No domain names under `keel/`: no entity kind, relation, predicate, rule or event type.
|
|
24
|
+
2. Pointers, not copies. Never copy a row or a table into the graph.
|
|
25
|
+
3. Facts close. Handlers never mutate or delete them.
|
|
26
|
+
4. Five tools. New capability goes into the semantics of the existing five.
|
|
27
|
+
5. Deterministic ingest: no network, no LLM, no randomness.
|
|
28
|
+
6. Every core change comes with a test. Every cartridge change comes with events and assertions.
|
|
29
|
+
|
|
30
|
+
## Process
|
|
31
|
+
|
|
32
|
+
- Open an issue before a large change. For a cartridge or a small fix, a pull request is fine.
|
|
33
|
+
- Run `uv run ruff check .` and `uv run pytest` before pushing.
|
|
34
|
+
- Commit messages and all user-facing text in English.
|
|
35
|
+
- If `examples/agent_minimal.py` output changes, say why in the pull request.
|
|
36
|
+
|
|
37
|
+
By contributing you agree that your contributions are licensed under the Apache License 2.0.
|
|
38
|
+
|
|
39
|
+
## Your own store
|
|
40
|
+
|
|
41
|
+
The core talks to a `Store` protocol (`keel/store/base.py`); `MemoryStore` is the reference and `SqliteStore` the default durable one. A third store (Postgres, Graphiti, anything) is a separate package, never a core change. Run the conformance suite against it:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
KEEL_STORE=your_pkg.module:factory uv run pytest tests/test_store.py
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`factory` is a zero-argument callable or class that returns a fresh, empty store.
|
|
48
|
+
|
|
49
|
+
## Releasing
|
|
50
|
+
|
|
51
|
+
Bump `version` in `pyproject.toml` and `__version__` in `keel/__init__.py`, update the roadmap, then:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
git tag -a vX.Y.Z -m "keel-context X.Y.Z" && git push origin vX.Y.Z
|
|
55
|
+
gh release create vX.Y.Z --title "X.Y.Z" --notes-file notes.md
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The tag triggers `.github/workflows/release.yml`, which runs the tests, builds and publishes `keel-context` to PyPI through trusted publishing (one-time setup on PyPI: pending publisher for project `keel-context`, owner `angelkurten`, repository `keel`, workflow `release.yml`, environment `pypi`).
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: keel-context
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Context layer for AI agents in vertical SaaS: domain events in, a per-tenant bitemporal context graph out, shared by every agent through five MCP tools.
|
|
5
|
+
Project-URL: Homepage, https://github.com/angelkurten/keel
|
|
6
|
+
Project-URL: Documentation, https://angelkurten.github.io/keel/
|
|
7
|
+
Author-email: Angel Kürten <angel@angelkurten.com>
|
|
8
|
+
License: Apache-2.0
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
License-File: NOTICE
|
|
11
|
+
Keywords: agents,bitemporal,context,event-driven,knowledge-graph,mcp
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Requires-Dist: pydantic>=2.6
|
|
18
|
+
Requires-Dist: pyyaml>=6.0
|
|
19
|
+
Requires-Dist: typer>=0.12
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: fastapi>=0.110; extra == 'dev'
|
|
22
|
+
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
23
|
+
Requires-Dist: mcp>=1.2; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
26
|
+
Provides-Extra: docs
|
|
27
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
28
|
+
Requires-Dist: mkdocs<2; extra == 'docs'
|
|
29
|
+
Provides-Extra: graphiti
|
|
30
|
+
Requires-Dist: graphiti-core>=0.5; extra == 'graphiti'
|
|
31
|
+
Provides-Extra: mcp
|
|
32
|
+
Requires-Dist: mcp>=1.2; extra == 'mcp'
|
|
33
|
+
Provides-Extra: postgres
|
|
34
|
+
Requires-Dist: psycopg[binary]>=3.1; extra == 'postgres'
|
|
35
|
+
Provides-Extra: webhook
|
|
36
|
+
Requires-Dist: fastapi>=0.110; extra == 'webhook'
|
|
37
|
+
Requires-Dist: uvicorn>=0.27; extra == 'webhook'
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
# Keel
|
|
41
|
+
|
|
42
|
+
**Stop rebuilding the pipeline for every agent.**
|
|
43
|
+
|
|
44
|
+
Docs: [angelkurten.github.io/keel](https://angelkurten.github.io/keel/) · Why: [We built four agents and four copies of our customer context](https://angelkurten.com/blog/keel-four-agents-four-copies-of-context)
|
|
45
|
+
|
|
46
|
+
Keel is a context layer for AI agents inside vertical SaaS products. Domain events from your product go in; a bitemporal context graph per end-customer comes out; every agent you build reads and writes it through the same five MCP tools.
|
|
47
|
+
|
|
48
|
+
You describe your vertical once, in a *cartridge* (ontology, event catalog, semantic rules, memory policies). Keel's core knows nothing about your domain. Your second agent connects in a day instead of weeks, because there is no second pipeline to write.
|
|
49
|
+
|
|
50
|
+
> Status: 0.1.0, alpha. The MCP server (`keel serve`), the Postgres table adapter and the webhook adapter run against the in-memory store or the SQLite store (`--db keel.db`, stdlib, no server to run).
|
|
51
|
+
|
|
52
|
+

|
|
53
|
+
|
|
54
|
+
## The problem
|
|
55
|
+
|
|
56
|
+
Every new agent in a SaaS product repeats the same work: extract the customer's data, normalize it, decide what "critical" means, keep its own memory, expose its own tools to the model. Four agents, four copies of the customer's context, four definitions of "critical", four memories that never talk.
|
|
57
|
+
|
|
58
|
+
Existing memory layers (Zep, Mem0, AgentCore Memory, Neo4j's MKG) ingest text and documents and model memory per user or per agent. None of them start from **domain events**, and none model the **tenant of the tenant**: the end-customer of a B2B SaaS.
|
|
59
|
+
|
|
60
|
+
## How Keel is different
|
|
61
|
+
|
|
62
|
+
1. **Cartridges.** A vertical is declared in YAML plus a few ten-line handlers. The core stays generic. Write a cartridge for fleets, clinics, logistics, or your own product.
|
|
63
|
+
2. **Events in, not documents.** Keel ingests structured facts with time (Postgres tables, webhooks, Kafka later). Ingestion is deterministic and cheap. The graph is exact because it is born from events, not inferred from text.
|
|
64
|
+
3. **Every fact carries its provenance.** Source, `valid_from`, `valid_to`, `recorded_at`, and who corrected it. Audit-ready by construction.
|
|
65
|
+
4. **Tenant of the tenant.** One subgraph per end-customer, with its own permissions and expiry.
|
|
66
|
+
|
|
67
|
+
## Five tools, any agent
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
context(subject, as_of?) -> what is true now, or at as_of: facts, relations, rules and their verdicts
|
|
71
|
+
history(subject, since?, until?, predicate?, scope?) -> what was true, and when we learned it
|
|
72
|
+
search(text) -> entities and facts matching text
|
|
73
|
+
annotate(subject, predicate, value, scope, evidence) -> an agent (or a human) writes what it learned
|
|
74
|
+
related(subject, relation?) -> neighbors by relation type
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Expose them over MCP (`keel serve`) and point any agent framework at them.
|
|
78
|
+
|
|
79
|
+
## Quick start
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
uv sync --extra dev
|
|
83
|
+
uv run keel validate cartridges/fleet --events examples/events/fleet.jsonl
|
|
84
|
+
uv run keel ingest cartridges/fleet examples/events/fleet.jsonl
|
|
85
|
+
uv run keel context cartridges/fleet examples/events/fleet.jsonl unit:MXK-4821
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Then write your own cartridge: see [docs/first-cartridge.md](docs/first-cartridge.md) (20 minutes).
|
|
89
|
+
|
|
90
|
+
In your own product it is a library: `uv add keel-context` (or `pip install keel-context`); the import is `import keel`.
|
|
91
|
+
|
|
92
|
+
## Connect a real event stream
|
|
93
|
+
|
|
94
|
+
Point an MCP client at the five tools:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
uv sync --extra mcp
|
|
98
|
+
uv run keel serve cartridges/fleet --tenant acme-logistics --events examples/events/fleet.jsonl
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
With Claude Code, one command registers it and the agent sees exactly five tools:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
claude mcp add keel -- uv run --directory $PWD keel serve cartridges/fleet --tenant acme-logistics --events examples/events/fleet.jsonl
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Codex CLI (OpenAI) works the same way from `~/.codex/config.toml`; the four read tools are annotated read-only, so it runs them without prompting. Both clients answered the same questions through the same five tools: see [connect](docs/connect.md#serve-the-tools-over-mcp).
|
|
108
|
+
|
|
109
|
+
Poll an outbox table in your Postgres (a cursor file makes restarts resume, not re-ingest):
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
import psycopg
|
|
113
|
+
from keel.adapters.postgres import PostgresTableAdapter
|
|
114
|
+
|
|
115
|
+
adapter = PostgresTableAdapter(lambda: psycopg.connect(DSN), table="keel_events", cursor_path=".keel/cursor")
|
|
116
|
+
ingestor = Ingestor(cartridge, SqliteStore("keel.db")) # durable; MemoryStore() for a throwaway graph
|
|
117
|
+
ingestor.ingest(adapter.follow(interval=5))
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Or receive envelopes over HTTP (`uv sync --extra webhook`):
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from keel.adapters.webhook import build_app
|
|
124
|
+
|
|
125
|
+
app = build_app(ingestor, token="shared-secret") # POST /events, Authorization: Bearer <token>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The table shape and the envelope fields are in `keel/adapters/postgres.py` and `keel/envelope.py`. `keel export <tenant> --db keel.db` writes one tenant's whole audit trail as JSONL, closed facts and provenance included.
|
|
129
|
+
|
|
130
|
+
## Architecture
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
your SaaS (Postgres) webhooks Kafka / SQS (later)
|
|
134
|
+
\ | /
|
|
135
|
+
v v v
|
|
136
|
+
+-------------------------------------------------+ +------------------+
|
|
137
|
+
| event envelope: tenant, type, subject, time | | cartridge |
|
|
138
|
+
| ingestor + handler registry | <--- | ontology |
|
|
139
|
+
| bitemporal context graph, one subgraph/tenant | | event catalog |
|
|
140
|
+
| memory scopes: global / tenant / task / agent | | handlers |
|
|
141
|
+
| five MCP tools | | semantic rules |
|
|
142
|
+
+-------------------------------------------------+ | memory policies |
|
|
143
|
+
| | | +------------------+
|
|
144
|
+
agent A agent B agent N (no pipeline)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Design rules the core enforces:
|
|
148
|
+
|
|
149
|
+
- **Events are the truth.** An append-only log; the graph is derived and can be replayed.
|
|
150
|
+
- **Pointers, not copies.** The graph holds references to your rows, the semantics, and earned memory. Never a copy of your tables.
|
|
151
|
+
- **Explicit time.** Facts close, they are not overwritten.
|
|
152
|
+
- **Scopes, not a flat store.** Nothing is promoted to a wider scope by accident.
|
|
153
|
+
|
|
154
|
+
## Repository layout
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
keel/ core: envelope, handlers, store, scopes, cartridge loader, ingest, tools, cli
|
|
158
|
+
cartridges/ fleet (reference), crm, finance and clinics: four domains, one core, no core change between them
|
|
159
|
+
examples/ sample event streams and a minimal agent
|
|
160
|
+
docs/ site sources (mkdocs.yml at the root): first-cartridge, connect, SPEC
|
|
161
|
+
tests/
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Roadmap
|
|
165
|
+
|
|
166
|
+
- [x] Event envelope and handler registry
|
|
167
|
+
- [x] In-memory bitemporal store
|
|
168
|
+
- [x] Memory scopes with expiry
|
|
169
|
+
- [x] Five tools as plain functions
|
|
170
|
+
- [x] MCP server (`keel serve`)
|
|
171
|
+
- [x] Durable store: SQLite (Graphiti still planned for graph-native queries)
|
|
172
|
+
- [x] Postgres (table + polling) and webhook adapters
|
|
173
|
+
- [ ] Consolidation and decay jobs
|
|
174
|
+
- [ ] Cartridge catalog
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
Apache 2.0. The event envelope, the cartridge format and the five tools are the standard; they will always be free.
|