riffdb-langgraph 0.2.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.
- riffdb_langgraph-0.2.0/.github/riffdb-revision +1 -0
- riffdb_langgraph-0.2.0/.github/workflows/release.yml +175 -0
- riffdb_langgraph-0.2.0/.github/workflows/riffdb-compatibility.yml +69 -0
- riffdb_langgraph-0.2.0/.gitignore +9 -0
- riffdb_langgraph-0.2.0/AGENTS.md +54 -0
- riffdb_langgraph-0.2.0/DESIGN.md +64 -0
- riffdb_langgraph-0.2.0/Makefile +26 -0
- riffdb_langgraph-0.2.0/PKG-INFO +72 -0
- riffdb_langgraph-0.2.0/README.md +61 -0
- riffdb_langgraph-0.2.0/generated/riffdb.application.exact.json +1 -0
- riffdb_langgraph-0.2.0/generated/riffdb.contract.bundle +0 -0
- riffdb_langgraph-0.2.0/pyproject.toml +55 -0
- riffdb_langgraph-0.2.0/riffdb/contract.riff +300 -0
- riffdb_langgraph-0.2.0/riffdb/queries/get_checkpoint_blobs.riffq +25 -0
- riffdb_langgraph-0.2.0/riffdb/queries/get_checkpoint_by_id.riffq +43 -0
- riffdb_langgraph-0.2.0/riffdb/queries/get_latest_checkpoint.riffq +44 -0
- riffdb_langgraph-0.2.0/riffdb/queries/get_parent_pending_sends.riffq +28 -0
- riffdb_langgraph-0.2.0/riffdb/queries/list_checkpoints_by_scope.riffq +42 -0
- riffdb_langgraph-0.2.0/riffdb/queries/list_checkpoints_by_scope_before.riffq +44 -0
- riffdb_langgraph-0.2.0/riffdb/queries/list_checkpoints_by_thread.riffq +44 -0
- riffdb_langgraph-0.2.0/riffdb/queries/list_checkpoints_by_thread_before.riffq +46 -0
- riffdb_langgraph-0.2.0/riffdb/queries/list_checkpoints_by_thread_ns.riffq +46 -0
- riffdb_langgraph-0.2.0/riffdb/queries/list_checkpoints_by_thread_ns_before.riffq +48 -0
- riffdb_langgraph-0.2.0/riffdb/queries/list_thread_blob_keys.riffq +22 -0
- riffdb_langgraph-0.2.0/riffdb/queries/list_thread_checkpoint_keys.riffq +21 -0
- riffdb_langgraph-0.2.0/riffdb/queries/list_thread_write_keys.riffq +23 -0
- riffdb_langgraph-0.2.0/riffdb.application.json +71 -0
- riffdb_langgraph-0.2.0/riffdb.application.lock.json +1 -0
- riffdb_langgraph-0.2.0/riffdb.toml +7 -0
- riffdb_langgraph-0.2.0/src/riffdb_langgraph/__init__.py +3 -0
- riffdb_langgraph-0.2.0/src/riffdb_langgraph/__main__.py +234 -0
- riffdb_langgraph-0.2.0/src/riffdb_langgraph/config.py +38 -0
- riffdb_langgraph-0.2.0/src/riffdb_langgraph/generated/client.py +2341 -0
- riffdb_langgraph-0.2.0/src/riffdb_langgraph/py.typed +0 -0
- riffdb_langgraph-0.2.0/src/riffdb_langgraph/saver.py +1832 -0
- riffdb_langgraph-0.2.0/tests/test_config.py +29 -0
- riffdb_langgraph-0.2.0/tests/test_saver.py +514 -0
- riffdb_langgraph-0.2.0/tests/test_surface.py +31 -0
- riffdb_langgraph-0.2.0/uv.lock +1322 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fc72da6e3d2af6ab29be72712139e9d16bcaf233
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v[0-9]+.[0-9]+.[0-9]+'
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
inputs:
|
|
9
|
+
release_tag:
|
|
10
|
+
description: 'Release tag to verify (for example v0.2.0)'
|
|
11
|
+
required: false
|
|
12
|
+
default: 'v0.2.0'
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
concurrency:
|
|
18
|
+
group: release-${{ github.workflow }}-${{ github.ref }}
|
|
19
|
+
cancel-in-progress: false
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
verify-and-build:
|
|
23
|
+
name: Verify adapter and build Python distributions
|
|
24
|
+
runs-on: ubuntu-24.04
|
|
25
|
+
timeout-minutes: 20
|
|
26
|
+
outputs:
|
|
27
|
+
release_tag: ${{ steps.meta.outputs.release_tag }}
|
|
28
|
+
version: ${{ steps.meta.outputs.version }}
|
|
29
|
+
revision: ${{ steps.meta.outputs.revision }}
|
|
30
|
+
steps:
|
|
31
|
+
- name: Check out adapter
|
|
32
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
33
|
+
with:
|
|
34
|
+
fetch-depth: 0
|
|
35
|
+
persist-credentials: false
|
|
36
|
+
- name: Install Python
|
|
37
|
+
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
38
|
+
with:
|
|
39
|
+
python-version: '3.13'
|
|
40
|
+
- name: Install uv
|
|
41
|
+
run: python3 -m pip install --quiet uv
|
|
42
|
+
- name: Resolve release metadata and download RiffDB release bundle
|
|
43
|
+
id: meta
|
|
44
|
+
run: |
|
|
45
|
+
if [[ "${{ github.event_name }}" == "push" ]]; then
|
|
46
|
+
release_tag="${GITHUB_REF_NAME}"
|
|
47
|
+
else
|
|
48
|
+
release_tag="${{ inputs.release_tag || 'v0.2.0' }}"
|
|
49
|
+
fi
|
|
50
|
+
[[ "$release_tag" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]]
|
|
51
|
+
version="${BASH_REMATCH[1]}"
|
|
52
|
+
python3 - "$version" <<'PY'
|
|
53
|
+
from pathlib import Path
|
|
54
|
+
import sys
|
|
55
|
+
import tomllib
|
|
56
|
+
expected = sys.argv[1]
|
|
57
|
+
pyproject = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8"))
|
|
58
|
+
assert pyproject["project"]["version"] == expected, pyproject["project"]["version"]
|
|
59
|
+
init_text = Path("src/riffdb_langgraph/__init__.py").read_text(encoding="utf-8")
|
|
60
|
+
assert f'__version__ = "{expected}"' in init_text, init_text
|
|
61
|
+
PY
|
|
62
|
+
riffdb_rev="$(cat .github/riffdb-revision)"
|
|
63
|
+
[[ "$riffdb_rev" =~ ^[0-9a-f]{40}$ ]]
|
|
64
|
+
|
|
65
|
+
bundle="riffdb-${version}-x86_64-unknown-linux-gnu.tar.gz"
|
|
66
|
+
base_url="https://github.com/riffdb/riffdb/releases/download/${release_tag}"
|
|
67
|
+
mkdir -p "$RUNNER_TEMP/riffdb-release"
|
|
68
|
+
curl -fsSL "$base_url/release-provenance-v1.json" -o "$RUNNER_TEMP/riffdb-release/release-provenance-v1.json"
|
|
69
|
+
curl -fsSL "$base_url/$bundle" -o "$RUNNER_TEMP/riffdb-release/$bundle"
|
|
70
|
+
expected_sha="$(jq -r '.bundle_archive.sha256' "$RUNNER_TEMP/riffdb-release/release-provenance-v1.json")"
|
|
71
|
+
echo "${expected_sha} $RUNNER_TEMP/riffdb-release/$bundle" | sha256sum -c -
|
|
72
|
+
tar -xzf "$RUNNER_TEMP/riffdb-release/$bundle" -C "$RUNNER_TEMP/riffdb-release"
|
|
73
|
+
bundle_dir="$RUNNER_TEMP/riffdb-release/riffdb-${version}-x86_64-unknown-linux-gnu"
|
|
74
|
+
[[ "$(tr -d '\n' < "$bundle_dir/REVISION")" == "$riffdb_rev" ]]
|
|
75
|
+
"$bundle_dir/bin/riffdb" application check
|
|
76
|
+
|
|
77
|
+
printf 'release_tag=%s\n' "$release_tag" >>"$GITHUB_OUTPUT"
|
|
78
|
+
printf 'version=%s\n' "$version" >>"$GITHUB_OUTPUT"
|
|
79
|
+
printf 'revision=%s\n' "$riffdb_rev" >>"$GITHUB_OUTPUT"
|
|
80
|
+
- name: Run adapter checks and build wheel and sdist
|
|
81
|
+
run: |
|
|
82
|
+
uv sync --locked
|
|
83
|
+
make check
|
|
84
|
+
mkdir -p "$RUNNER_TEMP/release-out"
|
|
85
|
+
uv build --out-dir "$RUNNER_TEMP/release-out"
|
|
86
|
+
(
|
|
87
|
+
cd "$RUNNER_TEMP/release-out"
|
|
88
|
+
sha256sum "riffdb_langgraph-${{ steps.meta.outputs.version }}"* >SHA256SUMS
|
|
89
|
+
)
|
|
90
|
+
- name: Upload verified release artifacts
|
|
91
|
+
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
92
|
+
with:
|
|
93
|
+
name: riffdb-langgraph-release
|
|
94
|
+
path: ${{ runner.temp }}/release-out/*
|
|
95
|
+
if-no-files-found: error
|
|
96
|
+
|
|
97
|
+
publish-release:
|
|
98
|
+
name: Publish GitHub Release and PyPI package
|
|
99
|
+
needs: [verify-and-build]
|
|
100
|
+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
|
|
101
|
+
runs-on: ubuntu-24.04
|
|
102
|
+
permissions:
|
|
103
|
+
contents: write
|
|
104
|
+
id-token: write
|
|
105
|
+
attestations: write
|
|
106
|
+
steps:
|
|
107
|
+
- name: Check out adapter
|
|
108
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
109
|
+
- name: Download verified release artifacts
|
|
110
|
+
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
111
|
+
with:
|
|
112
|
+
name: riffdb-langgraph-release
|
|
113
|
+
path: release-out
|
|
114
|
+
- name: Attest build provenance
|
|
115
|
+
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
|
|
116
|
+
with:
|
|
117
|
+
subject-path: |
|
|
118
|
+
release-out/riffdb_langgraph-${{ needs.verify-and-build.outputs.version }}-py3-none-any.whl
|
|
119
|
+
release-out/riffdb_langgraph-${{ needs.verify-and-build.outputs.version }}.tar.gz
|
|
120
|
+
release-out/SHA256SUMS
|
|
121
|
+
- name: Publish GitHub Release
|
|
122
|
+
env:
|
|
123
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
124
|
+
run: |
|
|
125
|
+
tag="${{ needs.verify-and-build.outputs.release_tag }}"
|
|
126
|
+
version="${{ needs.verify-and-build.outputs.version }}"
|
|
127
|
+
notes="Compiled RiffDB checkpoint-saver adapter for LangGraph v${version} (pinned to RiffDB \`${{ needs.verify-and-build.outputs.revision }}\` and \`riffdb==${version}\`)."
|
|
128
|
+
if gh release view "$tag" >/dev/null 2>&1; then
|
|
129
|
+
gh release upload "$tag" release-out/* --clobber
|
|
130
|
+
else
|
|
131
|
+
gh release create "$tag" release-out/* \
|
|
132
|
+
--title "riffdb-langgraph ${tag}" \
|
|
133
|
+
--notes "$notes" \
|
|
134
|
+
--verify-tag
|
|
135
|
+
fi
|
|
136
|
+
- name: Publish to PyPI (when token or Trusted Publishing is configured)
|
|
137
|
+
env:
|
|
138
|
+
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
|
|
139
|
+
run: |
|
|
140
|
+
python3 -m pip install --quiet twine
|
|
141
|
+
if [[ -n "${PYPI_TOKEN:-}" ]]; then
|
|
142
|
+
python3 -m twine upload --non-interactive --skip-existing -u __token__ -p "$PYPI_TOKEN" release-out/*.whl release-out/*.tar.gz
|
|
143
|
+
else
|
|
144
|
+
python3 - <<'PY' || echo "::warning::PyPI Trusted Publishing or PYPI_TOKEN is not yet configured for riffdb-langgraph; GitHub Release wheel and sdist are published."
|
|
145
|
+
import json
|
|
146
|
+
import os
|
|
147
|
+
import subprocess
|
|
148
|
+
import sys
|
|
149
|
+
import urllib.request
|
|
150
|
+
|
|
151
|
+
req_url = os.environ["ACTIONS_ID_TOKEN_REQUEST_URL"]
|
|
152
|
+
req_tok = os.environ["ACTIONS_ID_TOKEN_REQUEST_TOKEN"]
|
|
153
|
+
sep = "&" if "?" in req_url else "?"
|
|
154
|
+
req = urllib.request.Request(
|
|
155
|
+
f"{req_url}{sep}audience=pypi",
|
|
156
|
+
headers={"Authorization": f"Bearer {req_tok}", "Accept": "application/json; api-version=2.0"},
|
|
157
|
+
)
|
|
158
|
+
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
159
|
+
jwt = json.loads(resp.read().decode("utf-8"))["value"]
|
|
160
|
+
mint = urllib.request.Request(
|
|
161
|
+
"https://upload.pypi.org/_/oidc/mint-token",
|
|
162
|
+
data=json.dumps({"token": jwt}).encode("utf-8"),
|
|
163
|
+
headers={"Content-Type": "application/json", "Accept": "application/json"},
|
|
164
|
+
method="POST",
|
|
165
|
+
)
|
|
166
|
+
with urllib.request.urlopen(mint, timeout=30) as resp:
|
|
167
|
+
pypi_tok = json.loads(resp.read().decode("utf-8"))["token"]
|
|
168
|
+
import glob
|
|
169
|
+
pkgs = sorted(glob.glob("release-out/*.whl") + glob.glob("release-out/*.tar.gz"))
|
|
170
|
+
subprocess.run(
|
|
171
|
+
[sys.executable, "-m", "twine", "upload", "--non-interactive", "--skip-existing", "-u", "__token__", "-p", pypi_tok, *pkgs],
|
|
172
|
+
check=True,
|
|
173
|
+
)
|
|
174
|
+
PY
|
|
175
|
+
fi
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
name: RiffDB compatibility
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
schedule:
|
|
7
|
+
- cron: '23 4 * * *'
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
inputs:
|
|
10
|
+
riffdb_ref:
|
|
11
|
+
description: 'Full RiffDB commit or main; empty uses the adapter pin'
|
|
12
|
+
required: false
|
|
13
|
+
default: ''
|
|
14
|
+
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
|
|
18
|
+
concurrency:
|
|
19
|
+
group: riffdb-compatibility-${{ github.ref }}-${{ github.event_name }}
|
|
20
|
+
cancel-in-progress: true
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
application-compatibility:
|
|
24
|
+
name: Application compatibility
|
|
25
|
+
runs-on: ubuntu-24.04
|
|
26
|
+
timeout-minutes: 30
|
|
27
|
+
env:
|
|
28
|
+
CARGO_INCREMENTAL: '0'
|
|
29
|
+
CARGO_TARGET_DIR: ${{ github.workspace }}/riffdb/target
|
|
30
|
+
steps:
|
|
31
|
+
- name: Check out adapter
|
|
32
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
33
|
+
with:
|
|
34
|
+
path: adapter
|
|
35
|
+
persist-credentials: false
|
|
36
|
+
- name: Select RiffDB revision
|
|
37
|
+
id: riffdb
|
|
38
|
+
env:
|
|
39
|
+
EVENT_NAME: ${{ github.event_name }}
|
|
40
|
+
REQUESTED_REF: ${{ inputs.riffdb_ref }}
|
|
41
|
+
run: |
|
|
42
|
+
revision="$(cat adapter/.github/riffdb-revision)"
|
|
43
|
+
[[ "$revision" =~ ^[0-9a-f]{40}$ ]]
|
|
44
|
+
if [[ "$EVENT_NAME" == schedule ]]; then revision=main; fi
|
|
45
|
+
if [[ -n "$REQUESTED_REF" ]]; then revision="$REQUESTED_REF"; fi
|
|
46
|
+
[[ "$revision" =~ ^([0-9a-f]{40}|main)$ ]]
|
|
47
|
+
printf 'revision=%s\n' "$revision" >>"$GITHUB_OUTPUT"
|
|
48
|
+
- name: Check out RiffDB
|
|
49
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
50
|
+
with:
|
|
51
|
+
repository: riffdb/riffdb
|
|
52
|
+
ref: ${{ steps.riffdb.outputs.revision }}
|
|
53
|
+
path: riffdb
|
|
54
|
+
persist-credentials: false
|
|
55
|
+
- name: Install Rust toolchain
|
|
56
|
+
run: rustup toolchain install 1.98.1 --profile minimal
|
|
57
|
+
- name: Cache Cargo sources
|
|
58
|
+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
59
|
+
with:
|
|
60
|
+
path: |
|
|
61
|
+
~/.cargo/registry
|
|
62
|
+
~/.cargo/git
|
|
63
|
+
key: cargo-compatibility-${{ runner.os }}-${{ hashFiles('riffdb/Cargo.lock') }}
|
|
64
|
+
- name: Build selected RiffDB CLI
|
|
65
|
+
working-directory: riffdb
|
|
66
|
+
run: cargo build --locked --release -p riffdb-cli --bin riffdb
|
|
67
|
+
- name: Check adapter application
|
|
68
|
+
working-directory: adapter
|
|
69
|
+
run: ../riffdb/target/release/riffdb application check
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# AGENTS.md — RiffDB LangGraph adapter
|
|
2
|
+
|
|
3
|
+
## Mission
|
|
4
|
+
|
|
5
|
+
Implement a standalone Python checkpoint-saver adapter for LangGraph using a
|
|
6
|
+
compiled RiffDB application. All LangGraph-specific behavior belongs in this
|
|
7
|
+
repository, never in RiffDB.
|
|
8
|
+
|
|
9
|
+
## Authoritative inputs
|
|
10
|
+
|
|
11
|
+
- `langgraph` (`>=1.1.0`), `langgraph-checkpoint` (`>=4.0.0`), and
|
|
12
|
+
`langgraph-checkpoint-conformance` (`>=0.1.0`), especially
|
|
13
|
+
`langgraph.checkpoint.base.BaseCheckpointSaver` and the upstream conformance
|
|
14
|
+
suite (`langgraph.checkpoint.conformance.validate`).
|
|
15
|
+
- This repository's `DESIGN.md`.
|
|
16
|
+
- The accepted RiffDB specification and ADRs at the revision recorded in the
|
|
17
|
+
generated application lock and `.github/riffdb-revision`.
|
|
18
|
+
|
|
19
|
+
If these inputs conflict, stop and request maintainer review. Do not weaken a
|
|
20
|
+
LangGraph checkpointer semantic or a RiffDB safety guarantee to make a test
|
|
21
|
+
pass.
|
|
22
|
+
|
|
23
|
+
## Architecture boundaries
|
|
24
|
+
|
|
25
|
+
1. LangGraph checkpoint mutations use generated, compiled RiffDB commands only.
|
|
26
|
+
2. Reads use generated named RiffQL queries only. Do not add generic CRUD,
|
|
27
|
+
dynamically assembled RiffQL, or unbounded scans.
|
|
28
|
+
3. Checkpoint metadata, channel version blobs, and pending task writes belong to
|
|
29
|
+
a single `Checkpoints` aggregate partitioned by `scope` with `conflict_key
|
|
30
|
+
(scope, thread_id)`.
|
|
31
|
+
4. `put_writes` / `aput_writes` preserve upstream idempotency rules:
|
|
32
|
+
non-negative write indices (`write_idx >= 0`) are write-once per
|
|
33
|
+
`(thread_id, checkpoint_ns, checkpoint_id, task_id, write_idx)`, while
|
|
34
|
+
special negative indices from `WRITES_IDX_MAP` (`ERROR`, `SCHEDULED`,
|
|
35
|
+
`INTERRUPT`, `RESUME`) overwrite in place.
|
|
36
|
+
5. `get_tuple` and `list` hydrate channel values from `CheckpointBlob` rows
|
|
37
|
+
referenced by `channel_versions`, order `pending_writes` via upstream
|
|
38
|
+
`writes_sort_key`, and migrate `v < 4` parent `TASKS` writes into
|
|
39
|
+
`channel_values[TASKS]`.
|
|
40
|
+
6. Connection URIs (`riffdb://<database>`) carry only the database alias.
|
|
41
|
+
Credentials and TLS configuration come from protected environment variables,
|
|
42
|
+
never from the URI.
|
|
43
|
+
|
|
44
|
+
## Completion checks
|
|
45
|
+
|
|
46
|
+
Before calling the adapter usable:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
make check
|
|
50
|
+
make conformance
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Unit fakes may supplement upstream tests but never replace real RiffDB loopback
|
|
54
|
+
and `langgraph-checkpoint-conformance` coverage.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Design
|
|
2
|
+
|
|
3
|
+
## Compatibility target
|
|
4
|
+
|
|
5
|
+
`riffdb-langgraph` implements `langgraph.checkpoint.base.BaseCheckpointSaver[str]`
|
|
6
|
+
for `langgraph>=1.1.0` and `langgraph-checkpoint>=4.0.0`, validated against all
|
|
7
|
+
eight capabilities of `langgraph-checkpoint-conformance`:
|
|
8
|
+
|
|
9
|
+
| Capability | Methods | Behavior |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `put` | `put`, `aput` | Atomic checkpoint envelope + channel blob persistence, metadata normalization via `get_checkpoint_metadata` |
|
|
12
|
+
| `put_writes` | `put_writes`, `aput_writes` | Pending task writes with idempotent regular indices (`write_idx >= 0`) and overwritable special indices (`WRITES_IDX_MAP`) |
|
|
13
|
+
| `get_tuple` | `get_tuple`, `aget_tuple` | Latest or specific `checkpoint_id` lookup, channel blob hydration, `pending_writes` ordering via `writes_sort_key`, and `v < 4` `TASKS` migration |
|
|
14
|
+
| `list` | `list`, `alist` | Reverse-chronological (`checkpoint_id DESC`) checkpoint listing with `thread_id`, `checkpoint_ns`, `before`, `limit`, and metadata filtering |
|
|
15
|
+
| `delete_thread` | `delete_thread`, `adelete_thread` | Removal of all checkpoints, channel blobs, and pending writes across all namespaces for a `thread_id` |
|
|
16
|
+
| `delete_for_runs` | `delete_for_runs`, `adelete_for_runs` | Removal of checkpoints (and their pending writes) whose metadata `run_id` matches the target run IDs |
|
|
17
|
+
| `copy_thread` | `copy_thread`, `acopy_thread` | Cloning all checkpoints, channel blobs, and pending writes across all namespaces from a source `thread_id` to a target `thread_id` |
|
|
18
|
+
| `prune` | `prune`, `aprune` | Pruning checkpoints per `(thread_id, checkpoint_ns)` using `keep_latest` or `delete` strategies |
|
|
19
|
+
|
|
20
|
+
The Python floor is 3.13, matching the RiffDB Python application runtime.
|
|
21
|
+
|
|
22
|
+
## Contract and aggregate model
|
|
23
|
+
|
|
24
|
+
The contract (`riffdb/contract.riff`) defines a single aggregate `Checkpoints`
|
|
25
|
+
partitioned by `scope` with `conflict_key (scope, thread_id)`:
|
|
26
|
+
|
|
27
|
+
- **`Checkpoint`** (root): keyed by `(scope, thread_id, checkpoint_ns, checkpoint_id)`.
|
|
28
|
+
Stores `parent_checkpoint_id`, typed serde payloads for the stripped checkpoint
|
|
29
|
+
envelope (`channel_values` removed) and normalized `CheckpointMetadata`, and a
|
|
30
|
+
monotonic `revision`. Indexed with `binary_utf8_v1` text keys for reverse
|
|
31
|
+
lexicographic `checkpoint_id` range queries (`< $before_checkpoint_id`).
|
|
32
|
+
- **`CheckpointBlob`** (child): keyed by
|
|
33
|
+
`(scope, thread_id, checkpoint_ns, checkpoint_id, blob_key)` with
|
|
34
|
+
`checkpoint_id = ""` and `blob_key = "{len(channel)}:{channel}:{version}"`.
|
|
35
|
+
Deduplicates serialized channel values across checkpoints within a
|
|
36
|
+
`(thread_id, checkpoint_ns)`. Empty channel versions are recorded with
|
|
37
|
+
`serde_type = "empty"` so removed/unwritten channels are omitted on hydration.
|
|
38
|
+
- **`CheckpointWrite`** (child): keyed by
|
|
39
|
+
`(scope, thread_id, checkpoint_ns, checkpoint_id, task_id, write_idx)`.
|
|
40
|
+
Stores pending task writes with `initialized: bool` so `PutCheckpointWrites`
|
|
41
|
+
enforces write-once semantics when `write_idx >= 0` (`allow_overwrite == false`)
|
|
42
|
+
and overwrites when `write_idx < 0` (`allow_overwrite == true`).
|
|
43
|
+
|
|
44
|
+
## Configuration
|
|
45
|
+
|
|
46
|
+
`RiffDBCheckpointSaver` accepts a canonical URI `riffdb://<database>`. It
|
|
47
|
+
contains no credential or TLS material. Connection authority comes from
|
|
48
|
+
protected process configuration:
|
|
49
|
+
|
|
50
|
+
- `RIFFDB_LANGGRAPH_ENDPOINT` (or fallback `RIFFDB_ENDPOINT`)
|
|
51
|
+
- `RIFFDB_LANGGRAPH_CREDENTIAL_FILE` (or fallback `RIFFDB_CREDENTIAL_FILE`)
|
|
52
|
+
- `RIFFDB_LANGGRAPH_TRUST_ROOT` (optional, for verified TLS)
|
|
53
|
+
- `RIFFDB_LANGGRAPH_SERVER_NAME` (optional, for verified TLS)
|
|
54
|
+
|
|
55
|
+
The credential file is loaded via `BearerCredential.from_protected_file`.
|
|
56
|
+
Unknown URI schemes, userinfo, ports, paths, query parameters, and fragments
|
|
57
|
+
raise `InvalidCheckpointerURI` before any network activity.
|
|
58
|
+
|
|
59
|
+
## Session consistency
|
|
60
|
+
|
|
61
|
+
`RiffDBCheckpointSaver` tracks the highest `commit_sequence` returned by any
|
|
62
|
+
mutation command and attaches `read_after_commit` to subsequent queries on the
|
|
63
|
+
same saver instance, guaranteeing monotonic read-your-writes consistency across
|
|
64
|
+
both sync and async execution paths.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
UV ?= uv
|
|
2
|
+
PYTHON ?= $(UV) run python
|
|
3
|
+
RIFFDB ?= riffdb
|
|
4
|
+
|
|
5
|
+
.PHONY: bootstrap format lint typecheck test check conformance
|
|
6
|
+
|
|
7
|
+
bootstrap:
|
|
8
|
+
$(UV) sync
|
|
9
|
+
|
|
10
|
+
format:
|
|
11
|
+
$(UV) run ruff format --check .
|
|
12
|
+
|
|
13
|
+
lint:
|
|
14
|
+
$(UV) run ruff check .
|
|
15
|
+
|
|
16
|
+
typecheck:
|
|
17
|
+
$(UV) run mypy
|
|
18
|
+
|
|
19
|
+
test:
|
|
20
|
+
$(UV) run pytest
|
|
21
|
+
|
|
22
|
+
check: format lint typecheck test
|
|
23
|
+
git diff --exit-code
|
|
24
|
+
|
|
25
|
+
conformance:
|
|
26
|
+
$(RIFFDB) dev --reset --exec '$(PYTHON) -m riffdb_langgraph "$$RIFFDB_ENDPOINT" "$$RIFFDB_CREDENTIAL_FILE" "$$RIFFDB_DATABASE"'
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: riffdb-langgraph
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Compiled RiffDB checkpoint-saver adapter for LangGraph
|
|
5
|
+
Author: RiffDB contributors
|
|
6
|
+
License: MIT OR Apache-2.0
|
|
7
|
+
Requires-Python: >=3.13
|
|
8
|
+
Requires-Dist: langgraph-checkpoint>=3.0.0
|
|
9
|
+
Requires-Dist: riffdb==0.2.0
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# riffdb-langgraph
|
|
13
|
+
|
|
14
|
+
`riffdb-langgraph` is a compiled RiffDB checkpoint-saver adapter
|
|
15
|
+
(`RiffDBCheckpointSaver`) for LangGraph (`langgraph>=1.1.0`,
|
|
16
|
+
`langgraph-checkpoint>=4.0.0`). It is an out-of-tree integration: no
|
|
17
|
+
LangGraph-specific code or vocabulary belongs in the RiffDB repository.
|
|
18
|
+
|
|
19
|
+
The adapter implements both synchronous and asynchronous `BaseCheckpointSaver`
|
|
20
|
+
surfaces and passes all eight capability suites (81 tests) in the upstream
|
|
21
|
+
`langgraph-checkpoint-conformance` package:
|
|
22
|
+
|
|
23
|
+
- `put` (`put`, `aput`)
|
|
24
|
+
- `put_writes` (`put_writes`, `aput_writes`)
|
|
25
|
+
- `get_tuple` (`get_tuple`, `aget_tuple`)
|
|
26
|
+
- `list` (`list`, `alist`)
|
|
27
|
+
- `delete_thread` (`delete_thread`, `adelete_thread`)
|
|
28
|
+
- `delete_for_runs` (`delete_for_runs`, `adelete_for_runs`)
|
|
29
|
+
- `copy_thread` (`copy_thread`, `acopy_thread`)
|
|
30
|
+
- `prune` (`prune`, `aprune`)
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from langgraph.graph import START, END, StateGraph
|
|
36
|
+
from riffdb_langgraph import RiffDBCheckpointSaver
|
|
37
|
+
|
|
38
|
+
with RiffDBCheckpointSaver("riffdb://langgraph-development") as checkpointer:
|
|
39
|
+
graph = builder.compile(checkpointer=checkpointer)
|
|
40
|
+
result = graph.invoke(
|
|
41
|
+
{"messages": ["hello"]},
|
|
42
|
+
config={"configurable": {"thread_id": "thread-1"}},
|
|
43
|
+
)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
RiffDB TLS and credential material is supplied through protected environment
|
|
47
|
+
configuration (`RIFFDB_LANGGRAPH_ENDPOINT` and
|
|
48
|
+
`RIFFDB_LANGGRAPH_CREDENTIAL_FILE`) described in [DESIGN.md](DESIGN.md); it is
|
|
49
|
+
never embedded in the checkpointer URI.
|
|
50
|
+
|
|
51
|
+
## Development and Verification
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
make check
|
|
55
|
+
make conformance
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`make conformance` starts a disposable `riffdb dev` instance, runs sync and
|
|
59
|
+
async `StateGraph` fan-out smoke tests, and executes the full
|
|
60
|
+
`langgraph-checkpoint-conformance` validation suite against the live server.
|
|
61
|
+
|
|
62
|
+
## RiffDB compatibility CI
|
|
63
|
+
|
|
64
|
+
This adapter owns its RiffDB compatibility checks. Pull requests compile the
|
|
65
|
+
application sources and verify existing locks and generated artifacts against
|
|
66
|
+
the full commit in `.github/riffdb-revision`. Scheduled checks use RiffDB
|
|
67
|
+
`main`; manual dispatch accepts a full commit or `main`. Failures are reported
|
|
68
|
+
here and do not block RiffDB itself.
|
|
69
|
+
|
|
70
|
+
To upgrade, update the pin, migrate sources as needed, review the result of
|
|
71
|
+
`riffdb application lock --write`, and verify with `riffdb application check`.
|
|
72
|
+
Commit the pin and migration together in this repository.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# riffdb-langgraph
|
|
2
|
+
|
|
3
|
+
`riffdb-langgraph` is a compiled RiffDB checkpoint-saver adapter
|
|
4
|
+
(`RiffDBCheckpointSaver`) for LangGraph (`langgraph>=1.1.0`,
|
|
5
|
+
`langgraph-checkpoint>=4.0.0`). It is an out-of-tree integration: no
|
|
6
|
+
LangGraph-specific code or vocabulary belongs in the RiffDB repository.
|
|
7
|
+
|
|
8
|
+
The adapter implements both synchronous and asynchronous `BaseCheckpointSaver`
|
|
9
|
+
surfaces and passes all eight capability suites (81 tests) in the upstream
|
|
10
|
+
`langgraph-checkpoint-conformance` package:
|
|
11
|
+
|
|
12
|
+
- `put` (`put`, `aput`)
|
|
13
|
+
- `put_writes` (`put_writes`, `aput_writes`)
|
|
14
|
+
- `get_tuple` (`get_tuple`, `aget_tuple`)
|
|
15
|
+
- `list` (`list`, `alist`)
|
|
16
|
+
- `delete_thread` (`delete_thread`, `adelete_thread`)
|
|
17
|
+
- `delete_for_runs` (`delete_for_runs`, `adelete_for_runs`)
|
|
18
|
+
- `copy_thread` (`copy_thread`, `acopy_thread`)
|
|
19
|
+
- `prune` (`prune`, `aprune`)
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from langgraph.graph import START, END, StateGraph
|
|
25
|
+
from riffdb_langgraph import RiffDBCheckpointSaver
|
|
26
|
+
|
|
27
|
+
with RiffDBCheckpointSaver("riffdb://langgraph-development") as checkpointer:
|
|
28
|
+
graph = builder.compile(checkpointer=checkpointer)
|
|
29
|
+
result = graph.invoke(
|
|
30
|
+
{"messages": ["hello"]},
|
|
31
|
+
config={"configurable": {"thread_id": "thread-1"}},
|
|
32
|
+
)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
RiffDB TLS and credential material is supplied through protected environment
|
|
36
|
+
configuration (`RIFFDB_LANGGRAPH_ENDPOINT` and
|
|
37
|
+
`RIFFDB_LANGGRAPH_CREDENTIAL_FILE`) described in [DESIGN.md](DESIGN.md); it is
|
|
38
|
+
never embedded in the checkpointer URI.
|
|
39
|
+
|
|
40
|
+
## Development and Verification
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
make check
|
|
44
|
+
make conformance
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`make conformance` starts a disposable `riffdb dev` instance, runs sync and
|
|
48
|
+
async `StateGraph` fan-out smoke tests, and executes the full
|
|
49
|
+
`langgraph-checkpoint-conformance` validation suite against the live server.
|
|
50
|
+
|
|
51
|
+
## RiffDB compatibility CI
|
|
52
|
+
|
|
53
|
+
This adapter owns its RiffDB compatibility checks. Pull requests compile the
|
|
54
|
+
application sources and verify existing locks and generated artifacts against
|
|
55
|
+
the full commit in `.github/riffdb-revision`. Scheduled checks use RiffDB
|
|
56
|
+
`main`; manual dispatch accepts a full commit or `main`. Failures are reported
|
|
57
|
+
here and do not block RiffDB itself.
|
|
58
|
+
|
|
59
|
+
To upgrade, update the pin, migrate sources as needed, review the result of
|
|
60
|
+
`riffdb application lock --write`, and verify with `riffdb application check`.
|
|
61
|
+
Commit the pin and migration together in this repository.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"application":"riffdb-langgraph","contract":{"bundle_hash":"b2da9e908d1987b893189d9d470d553edcf05ec700ac3657860ff858b580bcfe","lineage":"RiffDBLangGraph","source":"riffdb/contract.riff","version":1},"generation":{"python":"src/riffdb_langgraph/generated/client.py"},"query_modules":[{"module_hash":"031908018e6cd7507ca0e0f81b8375673f1707c57742af8e964e2b618d185e75","name":"riffdb_langgraph","queries":[{"name":"GetCheckpointBlobs","source":"riffdb/queries/get_checkpoint_blobs.riffq"},{"name":"GetCheckpointById","source":"riffdb/queries/get_checkpoint_by_id.riffq"},{"name":"GetLatestCheckpoint","source":"riffdb/queries/get_latest_checkpoint.riffq"},{"name":"GetParentPendingSends","source":"riffdb/queries/get_parent_pending_sends.riffq"},{"name":"ListCheckpointsByScope","source":"riffdb/queries/list_checkpoints_by_scope.riffq"},{"name":"ListCheckpointsByScopeBefore","source":"riffdb/queries/list_checkpoints_by_scope_before.riffq"},{"name":"ListCheckpointsByThread","source":"riffdb/queries/list_checkpoints_by_thread.riffq"},{"name":"ListCheckpointsByThreadBefore","source":"riffdb/queries/list_checkpoints_by_thread_before.riffq"},{"name":"ListCheckpointsByThreadNs","source":"riffdb/queries/list_checkpoints_by_thread_ns.riffq"},{"name":"ListCheckpointsByThreadNsBefore","source":"riffdb/queries/list_checkpoints_by_thread_ns_before.riffq"},{"name":"ListThreadBlobKeys","source":"riffdb/queries/list_thread_blob_keys.riffq"},{"name":"ListThreadCheckpointKeys","source":"riffdb/queries/list_thread_checkpoint_keys.riffq"},{"name":"ListThreadWriteKeys","source":"riffdb/queries/list_thread_write_keys.riffq"}],"version":1}],"reactive_modules":[],"roles":[{"agent_subscriptions":[],"commands":["DeleteCheckpointBlobs","DeleteCheckpointWrites","DeleteCheckpoints","PutCheckpoint","PutCheckpointBlobs","PutCheckpointWithBlobs","PutCheckpointWrites"],"environment":"development","event_streams":[],"name":"LangGraphCheckpointer","queries":["GetCheckpointBlobs","GetCheckpointById","GetLatestCheckpoint","GetParentPendingSends","ListCheckpointsByScope","ListCheckpointsByScopeBefore","ListCheckpointsByThread","ListCheckpointsByThreadBefore","ListCheckpointsByThreadNs","ListCheckpointsByThreadNsBefore","ListThreadBlobKeys","ListThreadCheckpointKeys","ListThreadWriteKeys"],"row_policies":[],"tenant_scope":"global","watch_queries":[]}],"schema":"riffdb.application-manifest/v5","seed_inputs":[]}
|
|
Binary file
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27,<2"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "riffdb-langgraph"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Compiled RiffDB checkpoint-saver adapter for LangGraph"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.13"
|
|
11
|
+
license = { text = "MIT OR Apache-2.0" }
|
|
12
|
+
authors = [{ name = "RiffDB contributors" }]
|
|
13
|
+
dependencies = [
|
|
14
|
+
"langgraph-checkpoint>=3.0.0",
|
|
15
|
+
"riffdb==0.2.0",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[dependency-groups]
|
|
19
|
+
dev = [
|
|
20
|
+
"langgraph>=1.0.0",
|
|
21
|
+
"langgraph-checkpoint-conformance",
|
|
22
|
+
"mypy>=1.17,<2",
|
|
23
|
+
"pytest>=8.4,<9",
|
|
24
|
+
"pytest-asyncio>=1.0",
|
|
25
|
+
"ruff>=0.12,<0.13",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[tool.hatch.build.targets.wheel]
|
|
29
|
+
packages = ["src/riffdb_langgraph"]
|
|
30
|
+
|
|
31
|
+
[tool.pytest.ini_options]
|
|
32
|
+
addopts = "-ra"
|
|
33
|
+
testpaths = ["tests"]
|
|
34
|
+
asyncio_mode = "auto"
|
|
35
|
+
|
|
36
|
+
[tool.ruff]
|
|
37
|
+
line-length = 100
|
|
38
|
+
target-version = "py313"
|
|
39
|
+
extend-exclude = ["src/riffdb_langgraph/generated/client.py"]
|
|
40
|
+
|
|
41
|
+
[tool.ruff.lint]
|
|
42
|
+
select = ["E", "F", "I", "B", "UP", "RUF"]
|
|
43
|
+
|
|
44
|
+
[tool.mypy]
|
|
45
|
+
python_version = "3.13"
|
|
46
|
+
strict = true
|
|
47
|
+
packages = ["riffdb_langgraph"]
|
|
48
|
+
exclude = ["src/riffdb_langgraph/generated/client.py"]
|
|
49
|
+
|
|
50
|
+
[[tool.mypy.overrides]]
|
|
51
|
+
module = [
|
|
52
|
+
"langgraph.*",
|
|
53
|
+
"langgraph_checkpoint_conformance.*",
|
|
54
|
+
]
|
|
55
|
+
ignore_missing_imports = true
|