Skip to content

Commit 3855142

Browse files
authored
Merge pull request #9 from runcycles/fix/v0.1.4-readme-and-type-alias
fix(v0.1.4): README dep line + IdempotencyNamespaceResolver type + docs example
2 parents b2d0f6d + 8985127 commit 3855142

6 files changed

Lines changed: 35 additions & 15 deletions

File tree

AUDIT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# langchain-runcycles — Middleware API Conformance Audit
22

33
**Date:** 2026-05-10
4-
**Package:** `langchain-runcycles` v0.1.3
4+
**Package:** `langchain-runcycles` v0.1.4
55
**LangChain target:** `langchain >= 1.0, < 2.0`, `langchain-core >= 1.0, < 2.0` (tested against `langchain==1.2.18`, `langchain-core==1.3.3`, `langgraph==1.1.10`)
66
**Cycles SDK target:** `runcycles >= 0.4.1` (tested against `runcycles==0.4.1`, Python 3.10+)
77
**Server audit:** Cycles protocol conformance is owned by [`cycles-client-python/AUDIT.md`](https://github.com/runcycles/cycles-client-python/blob/main/AUDIT.md). This document audits this package's contract with the LangChain agent middleware API only.

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.1.4] - 2026-05-10
9+
10+
External review of v0.1.3 caught two real defects (stale README dep line, type alias narrower than the documented per-call opt-out) plus a docs-example issue (undefined helper in a runnable-looking snippet). All fixed; no functional change.
11+
12+
### Fixed
13+
14+
- **Stale `langchain-core >= 0.3` line in README's Requirements section.** `pyproject.toml` was correctly tightened to `>=1.0,<2.0` in v0.1.2 but the README wasn't updated at the time. Now consistent.
15+
- **`IdempotencyNamespaceResolver` type alias widened from `Callable[[Any], str]` to `Callable[[Any], str | None]`.** Runtime already supported a callable returning `None` (per-call opt-out, documented since v0.1.3 and tested by `test_callable_namespace_returning_none_opts_out_per_call`). The type signature was narrower than the documented contract; strict-mypy users would have hit a false negative when writing the documented opt-out callable. No behavior change.
16+
- **Docs idempotency-namespace example now uses `request.state["run_id"]`** instead of an undefined `current_run_id()` / `current_run_id_contextvar.get(...)` helper. The new form is runnable against a real LangChain `ToolCallRequest` and matches the LangChain idiom for accessing per-call state. README, `docs/runcycles.mdx`, and the langchain-ai/docs PR mirror all updated together.
17+
18+
### Behavior change
19+
20+
None. Type widening is permissive (the old narrower type was a strict subset of the new one), README and docs are user-facing prose. v0.1.3 callers' code keeps working unchanged.
21+
822
## [0.1.3] - 2026-05-10
923

1024
Adds run / workflow / tenant scoping to Cycles idempotency keys, addressing the v0.1.2 review concern about cross-run collision when frameworks reuse short tool call ids like `tc_1`. Backward-compatible — keys without a configured namespace keep the v0.1.2 shape exactly. Closes #6.
@@ -71,6 +85,7 @@ Initial public release. First-class LangChain agent middleware integration for C
7185
- Examples: `tenant_budget_agent.py` (tenant cap + risky-tool denial) and `multi_agent_fanout.py` (multi-agent / HITL flow).
7286
- `AUDIT.md` documenting LangChain middleware API conformance (hooks, ToolMessage shape, jump_to semantics, SDK methods consumed).
7387

88+
[0.1.4]: https://github.com/runcycles/langchain-runcycles/compare/v0.1.3...v0.1.4
7489
[0.1.3]: https://github.com/runcycles/langchain-runcycles/compare/v0.1.2...v0.1.3
7590
[0.1.2]: https://github.com/runcycles/langchain-runcycles/compare/v0.1.1...v0.1.2
7691
[0.1.1]: https://github.com/runcycles/langchain-runcycles/compare/v0.1.0...v0.1.1

README.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -151,12 +151,11 @@ gate = CyclesToolGate(
151151
idempotency_namespace="run_2026_05_10_abc",
152152
)
153153

154-
# Callable — receives the LangChain ToolCallRequest. Pull the namespace from
155-
# wherever your runtime exposes the run id (a contextvar, your own middleware,
156-
# request metadata, etc.). The exact accessor depends on your LangChain version.
157-
def my_run_id(_request):
158-
# In production: return your_runtime.current_run_id()
159-
return current_run_id_contextvar.get("default")
154+
# Callable — receives the LangChain ToolCallRequest. Pull the run id from
155+
# wherever your runtime carries it: request state, a contextvar, your own
156+
# middleware, etc.
157+
def my_run_id(request):
158+
return request.state["run_id"]
160159

161160
gate = CyclesToolGate(
162161
client,
@@ -262,7 +261,7 @@ mypy langchain_runcycles
262261
- Python 3.10+
263262
- `runcycles >= 0.4.1`
264263
- `langchain >= 1.0, < 2.0`
265-
- `langchain-core >= 0.3`
264+
- `langchain-core >= 1.0, < 2.0`
266265

267266
## License
268267

docs/runcycles.mdx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -273,9 +273,10 @@ gate = CyclesToolGate(
273273
idempotency_namespace="run_2026_05_10_abc",
274274
)
275275

276-
# Callable: extract per call from your runtime's run-id source
277-
def my_run_id(_request):
278-
return current_run_id() # whatever your runtime exposes
276+
# Callable: receives the LangChain ToolCallRequest; pull the run id from
277+
# request state, a contextvar, your own middleware, etc.
278+
def my_run_id(request):
279+
return request.state["run_id"]
279280

280281
gate = CyclesToolGate(
281282
client,

langchain_runcycles/_config.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,15 @@
2929
TurnCounter: TypeAlias = Callable[[Any], int]
3030
"""(state) -> turn count. Default counts AIMessages; override for custom semantics."""
3131

32-
IdempotencyNamespaceResolver: TypeAlias = Callable[[Any], str]
32+
IdempotencyNamespaceResolver: TypeAlias = Callable[[Any], str | None]
3333
IdempotencyNamespace: TypeAlias = str | IdempotencyNamespaceResolver
3434
"""Optional run/workflow/tenant scope woven into Cycles idempotency keys.
35+
3536
Static string or a callable receiving the request (tool gate) or state
36-
(fan-out gate). When supplied, keys take the shape
37-
``{prefix}-{namespace}-{tool_call_id}``; without it, the v0.1.2 shape
37+
(fan-out gate). The callable may return ``None`` to disable namespacing
38+
for that particular call (per-call opt-out — useful when some calls
39+
should be globally scoped while others are run-scoped).
40+
41+
When a non-empty namespace is in effect, keys take the shape
42+
``{prefix}-{namespace}-{tool_call_id}``; otherwise the v0.1.2 shape
3843
``{prefix}-{tool_call_id}`` is preserved (back-compat)."""

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "langchain-runcycles"
7-
version = "0.1.3"
7+
version = "0.1.4"
88
description = "LangChain agent middleware for Cycles — pre-tool-call authorization, fan-out caps, and per-tenant budget enforcement for Python agents using create_agent."
99
readme = "README.md"
1010
license = "Apache-2.0"

0 commit comments

Comments
 (0)