langchain-agentkit 0.4.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.
Files changed (49) hide show
  1. langchain_agentkit-0.4.0/.github/workflows/ci.yml +50 -0
  2. langchain_agentkit-0.4.0/.github/workflows/publish.yml +31 -0
  3. langchain_agentkit-0.4.0/.gitignore +11 -0
  4. langchain_agentkit-0.4.0/LICENSE +21 -0
  5. langchain_agentkit-0.4.0/PKG-INFO +300 -0
  6. langchain_agentkit-0.4.0/README.md +281 -0
  7. langchain_agentkit-0.4.0/examples/custom_state_type.py +103 -0
  8. langchain_agentkit-0.4.0/examples/manual_wiring.py +60 -0
  9. langchain_agentkit-0.4.0/examples/multi_agent.py +70 -0
  10. langchain_agentkit-0.4.0/examples/root_with_checkpointer.py +59 -0
  11. langchain_agentkit-0.4.0/examples/standalone_node.py +37 -0
  12. langchain_agentkit-0.4.0/examples/subgraph_with_checkpointer.py +64 -0
  13. langchain_agentkit-0.4.0/pyproject-skillkit.toml +28 -0
  14. langchain_agentkit-0.4.0/pyproject.toml +47 -0
  15. langchain_agentkit-0.4.0/src/langchain_agentkit/__init__.py +62 -0
  16. langchain_agentkit-0.4.0/src/langchain_agentkit/_handler_validation.py +81 -0
  17. langchain_agentkit-0.4.0/src/langchain_agentkit/agent.py +285 -0
  18. langchain_agentkit-0.4.0/src/langchain_agentkit/agent_kit.py +125 -0
  19. langchain_agentkit-0.4.0/src/langchain_agentkit/frontmatter.py +38 -0
  20. langchain_agentkit-0.4.0/src/langchain_agentkit/middleware.py +49 -0
  21. langchain_agentkit-0.4.0/src/langchain_agentkit/node.py +271 -0
  22. langchain_agentkit-0.4.0/src/langchain_agentkit/prompts/base_agent.md +12 -0
  23. langchain_agentkit-0.4.0/src/langchain_agentkit/prompts/skills_system.md +21 -0
  24. langchain_agentkit-0.4.0/src/langchain_agentkit/prompts/task_management.md +21 -0
  25. langchain_agentkit-0.4.0/src/langchain_agentkit/skill_kit.py +226 -0
  26. langchain_agentkit-0.4.0/src/langchain_agentkit/skills_middleware.py +70 -0
  27. langchain_agentkit-0.4.0/src/langchain_agentkit/state.py +26 -0
  28. langchain_agentkit-0.4.0/src/langchain_agentkit/task_tools.py +237 -0
  29. langchain_agentkit-0.4.0/src/langchain_agentkit/tasks_middleware.py +137 -0
  30. langchain_agentkit-0.4.0/src/langchain_agentkit/types.py +49 -0
  31. langchain_agentkit-0.4.0/src/langchain_agentkit/validate.py +46 -0
  32. langchain_agentkit-0.4.0/src/langchain_skillkit/__init__.py +35 -0
  33. langchain_agentkit-0.4.0/tests/__init__.py +0 -0
  34. langchain_agentkit-0.4.0/tests/fixtures/prompts/nodes/analyst.md +8 -0
  35. langchain_agentkit-0.4.0/tests/fixtures/prompts/nodes/researcher.md +9 -0
  36. langchain_agentkit-0.4.0/tests/fixtures/skills/market-sizing/SKILL.md +17 -0
  37. langchain_agentkit-0.4.0/tests/fixtures/skills/market-sizing/calculator.py +16 -0
  38. langchain_agentkit-0.4.0/tests/fixtures/skills_extra/competitive-analysis/SKILL.md +9 -0
  39. langchain_agentkit-0.4.0/tests/test_agent.py +353 -0
  40. langchain_agentkit-0.4.0/tests/test_agent_kit.py +207 -0
  41. langchain_agentkit-0.4.0/tests/test_frontmatter.py +66 -0
  42. langchain_agentkit-0.4.0/tests/test_node.py +278 -0
  43. langchain_agentkit-0.4.0/tests/test_skill_kit.py +160 -0
  44. langchain_agentkit-0.4.0/tests/test_skills_middleware.py +103 -0
  45. langchain_agentkit-0.4.0/tests/test_task_tools.py +253 -0
  46. langchain_agentkit-0.4.0/tests/test_tasks_middleware.py +211 -0
  47. langchain_agentkit-0.4.0/tests/test_types.py +67 -0
  48. langchain_agentkit-0.4.0/tests/test_validate.py +122 -0
  49. langchain_agentkit-0.4.0/uv.lock +1402 -0
@@ -0,0 +1,50 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python: ["3.11", "3.13"]
14
+ name: Test (Python ${{ matrix.python }})
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - uses: astral-sh/setup-uv@v5
19
+
20
+ - name: Install Python
21
+ run: uv python install ${{ matrix.python }}
22
+
23
+ - name: Install dependencies
24
+ run: uv sync --extra dev
25
+
26
+ - name: Run tests
27
+ run: uv run pytest --tb=short -q
28
+
29
+ lint:
30
+ runs-on: ubuntu-latest
31
+ name: Lint & Types
32
+ steps:
33
+ - uses: actions/checkout@v4
34
+
35
+ - uses: astral-sh/setup-uv@v5
36
+
37
+ - name: Install Python
38
+ run: uv python install 3.13
39
+
40
+ - name: Install dependencies
41
+ run: uv sync --extra dev
42
+
43
+ - name: Ruff check
44
+ run: uv run ruff check src/ tests/
45
+
46
+ - name: Ruff format
47
+ run: uv run ruff format --check src/ tests/
48
+
49
+ - name: Mypy
50
+ run: uv run mypy src/
@@ -0,0 +1,31 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ name: Build & Publish
11
+ permissions:
12
+ id-token: write
13
+ contents: write
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - uses: astral-sh/setup-uv@v5
18
+
19
+ - name: Install Python
20
+ run: uv python install 3.13
21
+
22
+ - name: Build package
23
+ run: uv build
24
+
25
+ - name: Publish to PyPI
26
+ run: uv publish --trusted-publishing always
27
+
28
+ - name: Create GitHub Release
29
+ run: gh release create ${{ github.ref_name }} dist/* --generate-notes
30
+ env:
31
+ GH_TOKEN: ${{ github.token }}
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .venv/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ *.egg
10
+ .env
11
+ .start/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 rsmdt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,300 @@
1
+ Metadata-Version: 2.4
2
+ Name: langchain-agentkit
3
+ Version: 0.4.0
4
+ Summary: Composable middleware framework for LangGraph agents
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.11
8
+ Requires-Dist: langchain-core>=0.3
9
+ Requires-Dist: langgraph>=0.4
10
+ Requires-Dist: pyyaml>=6.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: langchain-openai>=0.3; extra == 'dev'
13
+ Requires-Dist: mypy>=1.10; extra == 'dev'
14
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
15
+ Requires-Dist: pytest>=8.0; extra == 'dev'
16
+ Requires-Dist: ruff>=0.8; extra == 'dev'
17
+ Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
18
+ Description-Content-Type: text/markdown
19
+
20
+ # langchain-agentkit
21
+
22
+ Composable middleware framework for LangGraph agents.
23
+
24
+ [![Python](https://img.shields.io/pypi/pyversions/langchain-agentkit.svg)](https://pypi.org/project/langchain-agentkit/)
25
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
26
+
27
+ Build LangGraph agents with reusable middleware that composes tools and prompts. Two layers to choose from:
28
+
29
+ - **`agent` metaclass** — declare a class, get a complete ReAct agent with middleware-composed tools and prompts
30
+ - **`AgentKit`** — primitive composition engine for full control over graph topology
31
+
32
+ > **Migrating from `langchain-skillkit`?** See [Migration](#migrating-from-langchain-skillkit) below. The old import path still works with a deprecation warning.
33
+
34
+ ## Installation
35
+
36
+ Requires **Python 3.11+**, `langchain-core>=0.3`, `langgraph>=0.4`.
37
+
38
+ ```bash
39
+ pip install langchain-agentkit
40
+ ```
41
+
42
+ ## Quick Start
43
+
44
+ ### The `agent` metaclass (recommended)
45
+
46
+ Declare a class that inherits from `agent` to get a `StateGraph` with an automatic ReAct loop:
47
+
48
+ ```python
49
+ from langchain_core.messages import HumanMessage, SystemMessage
50
+ from langchain_openai import ChatOpenAI
51
+ from langchain_agentkit import agent, SkillsMiddleware, TasksMiddleware
52
+
53
+ class researcher(agent):
54
+ llm = ChatOpenAI(model="gpt-4o")
55
+ tools = [web_search]
56
+ middleware = [SkillsMiddleware("skills/"), TasksMiddleware()]
57
+ prompt = "You are a research assistant."
58
+
59
+ async def handler(state, *, llm, prompt):
60
+ messages = [SystemMessage(content=prompt)] + state["messages"]
61
+ response = await llm.ainvoke(messages)
62
+ return {"messages": [response], "sender": "researcher"}
63
+
64
+ # Compile and use
65
+ graph = researcher.compile()
66
+ result = graph.invoke({"messages": [HumanMessage("Size the B2B SaaS market")]})
67
+
68
+ # With checkpointer for interrupt() support
69
+ from langgraph.checkpoint.memory import InMemorySaver
70
+ graph = researcher.compile(checkpointer=InMemorySaver())
71
+ ```
72
+
73
+ ### `AgentKit` for manual graph wiring
74
+
75
+ Use `AgentKit` when you need full control over graph topology — multi-node graphs, shared `ToolNode`, custom routing:
76
+
77
+ ```python
78
+ from langchain_agentkit import AgentKit, SkillsMiddleware, TasksMiddleware
79
+
80
+ kit = AgentKit([
81
+ SkillsMiddleware("skills/"),
82
+ TasksMiddleware(),
83
+ ])
84
+
85
+ # In any graph node:
86
+ all_tools = my_tools + kit.tools
87
+ system_prompt = kit.prompt(state, config)
88
+ ```
89
+
90
+ ### Standalone `SkillKit`
91
+
92
+ Use `SkillKit` directly for skill discovery without the middleware layer:
93
+
94
+ ```python
95
+ from langchain_agentkit import SkillKit
96
+
97
+ kit = SkillKit("skills/")
98
+ tools = kit.tools # [Skill, SkillRead]
99
+ ```
100
+
101
+ ## Examples
102
+
103
+ See [`examples/`](examples/) for complete working code:
104
+
105
+ - **[`standalone_node.py`](examples/standalone_node.py)** — Simplest usage: declare a node class, compile, invoke
106
+ - **[`manual_wiring.py`](examples/manual_wiring.py)** — Use `SkillKit` as a standalone toolkit with full graph control
107
+ - **[`multi_agent.py`](examples/multi_agent.py)** — Compose multiple agents in a parent graph
108
+ - **[`root_with_checkpointer.py`](examples/root_with_checkpointer.py)** — Multi-turn conversations with `interrupt()` and `Command(resume=...)`
109
+ - **[`subgraph_with_checkpointer.py`](examples/subgraph_with_checkpointer.py)** — Subgraph inherits parent's checkpointer automatically
110
+ - **[`custom_state_type.py`](examples/custom_state_type.py)** — Custom state shape via handler annotation + subgraph schema translation
111
+
112
+ ## API Reference
113
+
114
+ ### `agent`
115
+
116
+ Declarative agent builder. Subclassing produces a `StateGraph`. Call `.compile()` to get a runnable graph.
117
+
118
+ **Class attributes:**
119
+
120
+ | Attribute | Required | Description |
121
+ |-----------|----------|-------------|
122
+ | `llm` | Yes | Language model instance |
123
+ | `tools` | No | List of LangChain tools |
124
+ | `middleware` | No | Ordered list of `Middleware` instances |
125
+ | `prompt` | No | System prompt — inline string, file path, or list of either |
126
+
127
+ **Handler signature:**
128
+
129
+ ```python
130
+ async def handler(state, *, llm, tools, prompt, config, runtime): ...
131
+ ```
132
+
133
+ `state` is positional. Everything after `*` is keyword-only and injected by name — declare only what you need:
134
+
135
+ | Parameter | Type | Description |
136
+ |-----------|------|-------------|
137
+ | `state` | `dict` | LangGraph state (positional, required) |
138
+ | `llm` | `BaseChatModel` | LLM pre-bound with all tools via `bind_tools()` |
139
+ | `tools` | `list[BaseTool]` | All tools (user tools + middleware tools) |
140
+ | `prompt` | `str` | Fully composed prompt (template + middleware sections) |
141
+ | `config` | `RunnableConfig` | LangGraph config for the current invocation |
142
+ | `runtime` | `Any` | LangGraph runtime context (passed through from graph kwargs) |
143
+
144
+ Both sync and async handlers are supported — sync handlers are detected via `inspect.isawaitable` and awaited automatically.
145
+
146
+ **Custom state types** — annotate the handler's `state` parameter:
147
+
148
+ ```python
149
+ class MyState(TypedDict, total=False):
150
+ messages: Annotated[list, add_messages]
151
+ draft: dict | None
152
+
153
+ class my_agent(agent):
154
+ llm = ChatOpenAI(model="gpt-4o")
155
+
156
+ async def handler(state: MyState, *, llm):
157
+ ...
158
+ ```
159
+
160
+ Without an annotation, `AgentState` is used by default.
161
+
162
+ ### `Middleware` protocol
163
+
164
+ Any class with `tools` (property) and `prompt(state, config)` (method) satisfies the protocol via structural subtyping — no base class needed:
165
+
166
+ ```python
167
+ class MyMiddleware:
168
+ @property
169
+ def tools(self) -> list[BaseTool]:
170
+ return [my_tool]
171
+
172
+ def prompt(self, state: dict, config: RunnableConfig) -> str | None:
173
+ return "You have access to my_tool."
174
+ ```
175
+
176
+ **Built-in middleware:**
177
+
178
+ | Middleware | Tools | Prompt |
179
+ |-----------|-------|--------|
180
+ | `SkillsMiddleware(skills_dirs)` | `Skill`, `SkillRead` | Progressive disclosure skill list with load instructions |
181
+ | `TasksMiddleware()` | `TaskCreate`, `TaskUpdate`, `TaskList`, `TaskGet` | Base agent behavior + task context with status icons |
182
+
183
+ ### `TasksMiddleware`
184
+
185
+ Task management middleware with `Command`-based tools that update graph state via LangGraph's `ToolNode`.
186
+
187
+ ```python
188
+ # Default — auto-creates task tools
189
+ mw = TasksMiddleware()
190
+
191
+ # Custom tools
192
+ mw = TasksMiddleware(task_tools=[my_create, my_update])
193
+
194
+ # Custom task formatter
195
+ mw = TasksMiddleware(formatter=my_format_function)
196
+ ```
197
+
198
+ Task tools use `InjectedState` to read tasks from state and return `Command(update={"tasks": [...]})` to apply changes. Task state is updated locally within the agent's graph, visible to the prompt on every ReAct loop iteration. When used as a subgraph, state flows back to the parent graph on completion.
199
+
200
+ You can also create task tools directly:
201
+
202
+ ```python
203
+ from langchain_agentkit import create_task_tools
204
+
205
+ tools = create_task_tools() # [TaskCreate, TaskUpdate, TaskList, TaskGet]
206
+ ```
207
+
208
+ ### `AgentKit(middleware, prompt=None)`
209
+
210
+ Composition engine that merges tools and prompts from middleware.
211
+
212
+ - **`tools`** — All tools from all middleware, deduplicated by name (first middleware wins, cached)
213
+ - **`prompt(state, config)`** — Template + middleware sections, joined with double newline (dynamic per call)
214
+
215
+ Prompt templates can be inline strings, file paths, or a list of either:
216
+
217
+ ```python
218
+ kit = AgentKit(middleware, prompt="You are helpful.")
219
+ kit = AgentKit(middleware, prompt=Path("prompts/system.txt"))
220
+ kit = AgentKit(middleware, prompt=["prompts/base.txt", "Extra instructions"])
221
+ ```
222
+
223
+ ### `node`
224
+
225
+ The original skill-aware metaclass. Uses `skills` attribute instead of `middleware`. Consider migrating to `agent` for the full middleware composition model — `agent` adds `middleware`, `prompt`, and `config` injection.
226
+
227
+ ```python
228
+ class my_agent(node):
229
+ llm = ChatOpenAI(model="gpt-4o")
230
+ tools = [web_search]
231
+ skills = "skills/" # str, list[str], or SkillKit instance
232
+
233
+ async def handler(state, *, llm, tools, runtime): ...
234
+ ```
235
+
236
+ ### `AgentState`
237
+
238
+ Minimal LangGraph state type with task support:
239
+
240
+ | Field | Type | Description |
241
+ |-------|------|-------------|
242
+ | `messages` | `Annotated[list, add_messages]` | Conversation history with LangGraph message reducer |
243
+ | `sender` | `str` | Name of the last node that produced output |
244
+ | `tasks` | `list[dict[str, Any]]` | Task list managed by `TasksMiddleware` tools |
245
+
246
+ Extend with your own fields:
247
+
248
+ ```python
249
+ class MyState(AgentState):
250
+ current_project: str
251
+ iteration_count: int
252
+ ```
253
+
254
+ ## Security
255
+
256
+ - **Path traversal prevention**: Skill file paths resolved to absolute and checked against skill directories. Reference file names reject `.` and `..` patterns.
257
+ - **Name validation**: Skill names validated per [AgentSkills.io spec](https://agentskills.io/specification) — lowercase alphanumeric + hyphens, 1-64 chars.
258
+ - **Tool scoping**: Each agent only has access to the tools declared in its `tools` attribute plus middleware-provided tools.
259
+ - **Prompt trust boundary**: Prompt templates and middleware prompt sections are set by the developer at construction time, not by end-user input.
260
+
261
+ ## Migrating from `langchain-skillkit`
262
+
263
+ `langchain-agentkit` v0.4.0 is the successor to `langchain-skillkit`. The old import path works with a deprecation warning:
264
+
265
+ ```python
266
+ # Still works — emits DeprecationWarning
267
+ from langchain_skillkit import node, SkillKit, AgentState
268
+
269
+ # Update to:
270
+ from langchain_agentkit import node, SkillKit, AgentState
271
+ ```
272
+
273
+ **What changed:**
274
+
275
+ | Before (`langchain-skillkit`) | After (`langchain-agentkit`) |
276
+ |------------------------------|------------------------------|
277
+ | `from langchain_skillkit import ...` | `from langchain_agentkit import ...` |
278
+ | `node` with `skills` attribute | `node` (unchanged) + new `agent` with `middleware` |
279
+ | `SkillKit` only | `SkillKit` + `SkillsMiddleware` + `TasksMiddleware` |
280
+ | No middleware system | `Middleware` protocol + `AgentKit` composition |
281
+ | Handler injectables: `llm`, `tools`, `runtime` | `agent` adds: `prompt`, `config` |
282
+
283
+ **Migration steps:**
284
+
285
+ 1. Update imports from `langchain_skillkit` to `langchain_agentkit`
286
+ 2. Existing `node` subclasses work unchanged
287
+ 3. Optionally migrate `node` to `agent` for middleware support:
288
+ - Replace `skills = "skills/"` with `middleware = [SkillsMiddleware("skills/")]`
289
+ - Add `prompt` and `config` injectables as needed
290
+
291
+ ## Contributing
292
+
293
+ ```bash
294
+ git clone https://github.com/rsmdt/langchain-agentkit.git
295
+ cd langchain-agentkit
296
+ uv sync --extra dev
297
+ uv run pytest --tb=short -q
298
+ uv run ruff check src/ tests/
299
+ uv run mypy src/
300
+ ```
@@ -0,0 +1,281 @@
1
+ # langchain-agentkit
2
+
3
+ Composable middleware framework for LangGraph agents.
4
+
5
+ [![Python](https://img.shields.io/pypi/pyversions/langchain-agentkit.svg)](https://pypi.org/project/langchain-agentkit/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ Build LangGraph agents with reusable middleware that composes tools and prompts. Two layers to choose from:
9
+
10
+ - **`agent` metaclass** — declare a class, get a complete ReAct agent with middleware-composed tools and prompts
11
+ - **`AgentKit`** — primitive composition engine for full control over graph topology
12
+
13
+ > **Migrating from `langchain-skillkit`?** See [Migration](#migrating-from-langchain-skillkit) below. The old import path still works with a deprecation warning.
14
+
15
+ ## Installation
16
+
17
+ Requires **Python 3.11+**, `langchain-core>=0.3`, `langgraph>=0.4`.
18
+
19
+ ```bash
20
+ pip install langchain-agentkit
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ### The `agent` metaclass (recommended)
26
+
27
+ Declare a class that inherits from `agent` to get a `StateGraph` with an automatic ReAct loop:
28
+
29
+ ```python
30
+ from langchain_core.messages import HumanMessage, SystemMessage
31
+ from langchain_openai import ChatOpenAI
32
+ from langchain_agentkit import agent, SkillsMiddleware, TasksMiddleware
33
+
34
+ class researcher(agent):
35
+ llm = ChatOpenAI(model="gpt-4o")
36
+ tools = [web_search]
37
+ middleware = [SkillsMiddleware("skills/"), TasksMiddleware()]
38
+ prompt = "You are a research assistant."
39
+
40
+ async def handler(state, *, llm, prompt):
41
+ messages = [SystemMessage(content=prompt)] + state["messages"]
42
+ response = await llm.ainvoke(messages)
43
+ return {"messages": [response], "sender": "researcher"}
44
+
45
+ # Compile and use
46
+ graph = researcher.compile()
47
+ result = graph.invoke({"messages": [HumanMessage("Size the B2B SaaS market")]})
48
+
49
+ # With checkpointer for interrupt() support
50
+ from langgraph.checkpoint.memory import InMemorySaver
51
+ graph = researcher.compile(checkpointer=InMemorySaver())
52
+ ```
53
+
54
+ ### `AgentKit` for manual graph wiring
55
+
56
+ Use `AgentKit` when you need full control over graph topology — multi-node graphs, shared `ToolNode`, custom routing:
57
+
58
+ ```python
59
+ from langchain_agentkit import AgentKit, SkillsMiddleware, TasksMiddleware
60
+
61
+ kit = AgentKit([
62
+ SkillsMiddleware("skills/"),
63
+ TasksMiddleware(),
64
+ ])
65
+
66
+ # In any graph node:
67
+ all_tools = my_tools + kit.tools
68
+ system_prompt = kit.prompt(state, config)
69
+ ```
70
+
71
+ ### Standalone `SkillKit`
72
+
73
+ Use `SkillKit` directly for skill discovery without the middleware layer:
74
+
75
+ ```python
76
+ from langchain_agentkit import SkillKit
77
+
78
+ kit = SkillKit("skills/")
79
+ tools = kit.tools # [Skill, SkillRead]
80
+ ```
81
+
82
+ ## Examples
83
+
84
+ See [`examples/`](examples/) for complete working code:
85
+
86
+ - **[`standalone_node.py`](examples/standalone_node.py)** — Simplest usage: declare a node class, compile, invoke
87
+ - **[`manual_wiring.py`](examples/manual_wiring.py)** — Use `SkillKit` as a standalone toolkit with full graph control
88
+ - **[`multi_agent.py`](examples/multi_agent.py)** — Compose multiple agents in a parent graph
89
+ - **[`root_with_checkpointer.py`](examples/root_with_checkpointer.py)** — Multi-turn conversations with `interrupt()` and `Command(resume=...)`
90
+ - **[`subgraph_with_checkpointer.py`](examples/subgraph_with_checkpointer.py)** — Subgraph inherits parent's checkpointer automatically
91
+ - **[`custom_state_type.py`](examples/custom_state_type.py)** — Custom state shape via handler annotation + subgraph schema translation
92
+
93
+ ## API Reference
94
+
95
+ ### `agent`
96
+
97
+ Declarative agent builder. Subclassing produces a `StateGraph`. Call `.compile()` to get a runnable graph.
98
+
99
+ **Class attributes:**
100
+
101
+ | Attribute | Required | Description |
102
+ |-----------|----------|-------------|
103
+ | `llm` | Yes | Language model instance |
104
+ | `tools` | No | List of LangChain tools |
105
+ | `middleware` | No | Ordered list of `Middleware` instances |
106
+ | `prompt` | No | System prompt — inline string, file path, or list of either |
107
+
108
+ **Handler signature:**
109
+
110
+ ```python
111
+ async def handler(state, *, llm, tools, prompt, config, runtime): ...
112
+ ```
113
+
114
+ `state` is positional. Everything after `*` is keyword-only and injected by name — declare only what you need:
115
+
116
+ | Parameter | Type | Description |
117
+ |-----------|------|-------------|
118
+ | `state` | `dict` | LangGraph state (positional, required) |
119
+ | `llm` | `BaseChatModel` | LLM pre-bound with all tools via `bind_tools()` |
120
+ | `tools` | `list[BaseTool]` | All tools (user tools + middleware tools) |
121
+ | `prompt` | `str` | Fully composed prompt (template + middleware sections) |
122
+ | `config` | `RunnableConfig` | LangGraph config for the current invocation |
123
+ | `runtime` | `Any` | LangGraph runtime context (passed through from graph kwargs) |
124
+
125
+ Both sync and async handlers are supported — sync handlers are detected via `inspect.isawaitable` and awaited automatically.
126
+
127
+ **Custom state types** — annotate the handler's `state` parameter:
128
+
129
+ ```python
130
+ class MyState(TypedDict, total=False):
131
+ messages: Annotated[list, add_messages]
132
+ draft: dict | None
133
+
134
+ class my_agent(agent):
135
+ llm = ChatOpenAI(model="gpt-4o")
136
+
137
+ async def handler(state: MyState, *, llm):
138
+ ...
139
+ ```
140
+
141
+ Without an annotation, `AgentState` is used by default.
142
+
143
+ ### `Middleware` protocol
144
+
145
+ Any class with `tools` (property) and `prompt(state, config)` (method) satisfies the protocol via structural subtyping — no base class needed:
146
+
147
+ ```python
148
+ class MyMiddleware:
149
+ @property
150
+ def tools(self) -> list[BaseTool]:
151
+ return [my_tool]
152
+
153
+ def prompt(self, state: dict, config: RunnableConfig) -> str | None:
154
+ return "You have access to my_tool."
155
+ ```
156
+
157
+ **Built-in middleware:**
158
+
159
+ | Middleware | Tools | Prompt |
160
+ |-----------|-------|--------|
161
+ | `SkillsMiddleware(skills_dirs)` | `Skill`, `SkillRead` | Progressive disclosure skill list with load instructions |
162
+ | `TasksMiddleware()` | `TaskCreate`, `TaskUpdate`, `TaskList`, `TaskGet` | Base agent behavior + task context with status icons |
163
+
164
+ ### `TasksMiddleware`
165
+
166
+ Task management middleware with `Command`-based tools that update graph state via LangGraph's `ToolNode`.
167
+
168
+ ```python
169
+ # Default — auto-creates task tools
170
+ mw = TasksMiddleware()
171
+
172
+ # Custom tools
173
+ mw = TasksMiddleware(task_tools=[my_create, my_update])
174
+
175
+ # Custom task formatter
176
+ mw = TasksMiddleware(formatter=my_format_function)
177
+ ```
178
+
179
+ Task tools use `InjectedState` to read tasks from state and return `Command(update={"tasks": [...]})` to apply changes. Task state is updated locally within the agent's graph, visible to the prompt on every ReAct loop iteration. When used as a subgraph, state flows back to the parent graph on completion.
180
+
181
+ You can also create task tools directly:
182
+
183
+ ```python
184
+ from langchain_agentkit import create_task_tools
185
+
186
+ tools = create_task_tools() # [TaskCreate, TaskUpdate, TaskList, TaskGet]
187
+ ```
188
+
189
+ ### `AgentKit(middleware, prompt=None)`
190
+
191
+ Composition engine that merges tools and prompts from middleware.
192
+
193
+ - **`tools`** — All tools from all middleware, deduplicated by name (first middleware wins, cached)
194
+ - **`prompt(state, config)`** — Template + middleware sections, joined with double newline (dynamic per call)
195
+
196
+ Prompt templates can be inline strings, file paths, or a list of either:
197
+
198
+ ```python
199
+ kit = AgentKit(middleware, prompt="You are helpful.")
200
+ kit = AgentKit(middleware, prompt=Path("prompts/system.txt"))
201
+ kit = AgentKit(middleware, prompt=["prompts/base.txt", "Extra instructions"])
202
+ ```
203
+
204
+ ### `node`
205
+
206
+ The original skill-aware metaclass. Uses `skills` attribute instead of `middleware`. Consider migrating to `agent` for the full middleware composition model — `agent` adds `middleware`, `prompt`, and `config` injection.
207
+
208
+ ```python
209
+ class my_agent(node):
210
+ llm = ChatOpenAI(model="gpt-4o")
211
+ tools = [web_search]
212
+ skills = "skills/" # str, list[str], or SkillKit instance
213
+
214
+ async def handler(state, *, llm, tools, runtime): ...
215
+ ```
216
+
217
+ ### `AgentState`
218
+
219
+ Minimal LangGraph state type with task support:
220
+
221
+ | Field | Type | Description |
222
+ |-------|------|-------------|
223
+ | `messages` | `Annotated[list, add_messages]` | Conversation history with LangGraph message reducer |
224
+ | `sender` | `str` | Name of the last node that produced output |
225
+ | `tasks` | `list[dict[str, Any]]` | Task list managed by `TasksMiddleware` tools |
226
+
227
+ Extend with your own fields:
228
+
229
+ ```python
230
+ class MyState(AgentState):
231
+ current_project: str
232
+ iteration_count: int
233
+ ```
234
+
235
+ ## Security
236
+
237
+ - **Path traversal prevention**: Skill file paths resolved to absolute and checked against skill directories. Reference file names reject `.` and `..` patterns.
238
+ - **Name validation**: Skill names validated per [AgentSkills.io spec](https://agentskills.io/specification) — lowercase alphanumeric + hyphens, 1-64 chars.
239
+ - **Tool scoping**: Each agent only has access to the tools declared in its `tools` attribute plus middleware-provided tools.
240
+ - **Prompt trust boundary**: Prompt templates and middleware prompt sections are set by the developer at construction time, not by end-user input.
241
+
242
+ ## Migrating from `langchain-skillkit`
243
+
244
+ `langchain-agentkit` v0.4.0 is the successor to `langchain-skillkit`. The old import path works with a deprecation warning:
245
+
246
+ ```python
247
+ # Still works — emits DeprecationWarning
248
+ from langchain_skillkit import node, SkillKit, AgentState
249
+
250
+ # Update to:
251
+ from langchain_agentkit import node, SkillKit, AgentState
252
+ ```
253
+
254
+ **What changed:**
255
+
256
+ | Before (`langchain-skillkit`) | After (`langchain-agentkit`) |
257
+ |------------------------------|------------------------------|
258
+ | `from langchain_skillkit import ...` | `from langchain_agentkit import ...` |
259
+ | `node` with `skills` attribute | `node` (unchanged) + new `agent` with `middleware` |
260
+ | `SkillKit` only | `SkillKit` + `SkillsMiddleware` + `TasksMiddleware` |
261
+ | No middleware system | `Middleware` protocol + `AgentKit` composition |
262
+ | Handler injectables: `llm`, `tools`, `runtime` | `agent` adds: `prompt`, `config` |
263
+
264
+ **Migration steps:**
265
+
266
+ 1. Update imports from `langchain_skillkit` to `langchain_agentkit`
267
+ 2. Existing `node` subclasses work unchanged
268
+ 3. Optionally migrate `node` to `agent` for middleware support:
269
+ - Replace `skills = "skills/"` with `middleware = [SkillsMiddleware("skills/")]`
270
+ - Add `prompt` and `config` injectables as needed
271
+
272
+ ## Contributing
273
+
274
+ ```bash
275
+ git clone https://github.com/rsmdt/langchain-agentkit.git
276
+ cd langchain-agentkit
277
+ uv sync --extra dev
278
+ uv run pytest --tb=short -q
279
+ uv run ruff check src/ tests/
280
+ uv run mypy src/
281
+ ```