Skip to main content

Composable middleware framework for LangGraph agents

Project description

langchain-agentkit

Composable middleware framework for LangGraph agents.

Python License: MIT

Build LangGraph agents with reusable middleware that composes tools and prompts. Two layers to choose from:

  • agent metaclass — declare a class, get a complete ReAct agent with middleware-composed tools and prompts
  • AgentKit — primitive composition engine for full control over graph topology

Migrating from langchain-skillkit? See Migration below. The old import path still works with a deprecation warning.

Installation

Requires Python 3.11+, langchain-core>=0.3, langgraph>=0.4.

pip install langchain-agentkit

Quick Start

The agent metaclass (recommended)

Declare a class that inherits from agent to get a StateGraph with an automatic ReAct loop:

from langchain_core.messages import HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI
from langchain_agentkit import agent, SkillsMiddleware, TasksMiddleware

class researcher(agent):
    llm = ChatOpenAI(model="gpt-4o")
    tools = [web_search]
    middleware = [SkillsMiddleware("skills/"), TasksMiddleware()]
    prompt = "You are a research assistant."

    async def handler(state, *, llm, prompt):
        messages = [SystemMessage(content=prompt)] + state["messages"]
        response = await llm.ainvoke(messages)
        return {"messages": [response], "sender": "researcher"}

# Compile and use
graph = researcher.compile()
result = graph.invoke({"messages": [HumanMessage("Size the B2B SaaS market")]})

# With checkpointer for interrupt() support
from langgraph.checkpoint.memory import InMemorySaver
graph = researcher.compile(checkpointer=InMemorySaver())

AgentKit for manual graph wiring

Use AgentKit when you need full control over graph topology — multi-node graphs, shared ToolNode, custom routing:

from langchain_agentkit import AgentKit, SkillsMiddleware, TasksMiddleware

kit = AgentKit([
    SkillsMiddleware("skills/"),
    TasksMiddleware(),
])

# In any graph node:
all_tools = my_tools + kit.tools
system_prompt = kit.prompt(state, config)

Standalone SkillKit

Use SkillKit directly for skill discovery without the middleware layer:

from langchain_agentkit import SkillKit

kit = SkillKit("skills/")
tools = kit.tools  # [Skill, SkillRead]

Examples

See examples/ for complete working code:

API Reference

agent

Declarative agent builder. Subclassing produces a StateGraph. Call .compile() to get a runnable graph.

Class attributes:

Attribute Required Description
llm Yes Language model instance
tools No List of LangChain tools
middleware No Ordered list of Middleware instances
prompt No System prompt — inline string, file path, or list of either

Handler signature:

async def handler(state, *, llm, tools, prompt, config, runtime): ...

state is positional. Everything after * is keyword-only and injected by name — declare only what you need:

Parameter Type Description
state dict LangGraph state (positional, required)
llm BaseChatModel LLM pre-bound with all tools via bind_tools()
tools list[BaseTool] All tools (user tools + middleware tools)
prompt str Fully composed prompt (template + middleware sections)
config RunnableConfig LangGraph config for the current invocation
runtime Any LangGraph runtime context (passed through from graph kwargs)

Both sync and async handlers are supported — sync handlers are detected via inspect.isawaitable and awaited automatically.

Custom state types — annotate the handler's state parameter:

class MyState(TypedDict, total=False):
    messages: Annotated[list, add_messages]
    draft: dict | None

class my_agent(agent):
    llm = ChatOpenAI(model="gpt-4o")

    async def handler(state: MyState, *, llm):
        ...

Without an annotation, AgentState is used by default.

Middleware protocol

Any class with tools (property) and prompt(state, config) (method) satisfies the protocol via structural subtyping — no base class needed:

class MyMiddleware:
    @property
    def tools(self) -> list[BaseTool]:
        return [my_tool]

    def prompt(self, state: dict, config: RunnableConfig) -> str | None:
        return "You have access to my_tool."

Built-in middleware:

Middleware Tools Prompt
SkillsMiddleware(skills_dirs) Skill, SkillRead Progressive disclosure skill list with load instructions
TasksMiddleware() TaskCreate, TaskUpdate, TaskList, TaskGet Base agent behavior + task context with status icons

TasksMiddleware

Task management middleware with Command-based tools that update graph state via LangGraph's ToolNode.

# Default — auto-creates task tools
mw = TasksMiddleware()

# Custom tools
mw = TasksMiddleware(task_tools=[my_create, my_update])

# Custom task formatter
mw = TasksMiddleware(formatter=my_format_function)

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.

You can also create task tools directly:

from langchain_agentkit import create_task_tools

tools = create_task_tools()  # [TaskCreate, TaskUpdate, TaskList, TaskGet]

AgentKit(middleware, prompt=None)

Composition engine that merges tools and prompts from middleware.

  • tools — All tools from all middleware, deduplicated by name (first middleware wins, cached)
  • prompt(state, config) — Template + middleware sections, joined with double newline (dynamic per call)

Prompt templates can be inline strings, file paths, or a list of either:

kit = AgentKit(middleware, prompt="You are helpful.")
kit = AgentKit(middleware, prompt=Path("prompts/system.txt"))
kit = AgentKit(middleware, prompt=["prompts/base.txt", "Extra instructions"])

node

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.

class my_agent(node):
    llm = ChatOpenAI(model="gpt-4o")
    tools = [web_search]
    skills = "skills/"  # str, list[str], or SkillKit instance

    async def handler(state, *, llm, tools, runtime): ...

AgentState

Minimal LangGraph state type with task support:

Field Type Description
messages Annotated[list, add_messages] Conversation history with LangGraph message reducer
sender str Name of the last node that produced output
tasks list[dict[str, Any]] Task list managed by TasksMiddleware tools

Extend with your own fields:

class MyState(AgentState):
    current_project: str
    iteration_count: int

Security

  • Path traversal prevention: Skill file paths resolved to absolute and checked against skill directories. Reference file names reject . and .. patterns.
  • Name validation: Skill names validated per AgentSkills.io spec — lowercase alphanumeric + hyphens, 1-64 chars.
  • Tool scoping: Each agent only has access to the tools declared in its tools attribute plus middleware-provided tools.
  • Prompt trust boundary: Prompt templates and middleware prompt sections are set by the developer at construction time, not by end-user input.

Migrating from langchain-skillkit

langchain-agentkit v0.4.0 is the successor to langchain-skillkit. The old import path works with a deprecation warning:

# Still works — emits DeprecationWarning
from langchain_skillkit import node, SkillKit, AgentState

# Update to:
from langchain_agentkit import node, SkillKit, AgentState

What changed:

Before (langchain-skillkit) After (langchain-agentkit)
from langchain_skillkit import ... from langchain_agentkit import ...
node with skills attribute node (unchanged) + new agent with middleware
SkillKit only SkillKit + SkillsMiddleware + TasksMiddleware
No middleware system Middleware protocol + AgentKit composition
Handler injectables: llm, tools, runtime agent adds: prompt, config

Migration steps:

  1. Update imports from langchain_skillkit to langchain_agentkit
  2. Existing node subclasses work unchanged
  3. Optionally migrate node to agent for middleware support:
    • Replace skills = "skills/" with middleware = [SkillsMiddleware("skills/")]
    • Add prompt and config injectables as needed

Contributing

git clone https://github.com/rsmdt/langchain-agentkit.git
cd langchain-agentkit
uv sync --extra dev
uv run pytest --tb=short -q
uv run ruff check src/ tests/
uv run mypy src/

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

langchain_agentkit-0.4.0.tar.gz (124.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

langchain_agentkit-0.4.0-py3-none-any.whl (30.0 kB view details)

Uploaded Python 3

File details

Details for the file langchain_agentkit-0.4.0.tar.gz.

File metadata

  • Download URL: langchain_agentkit-0.4.0.tar.gz
  • Upload date:
  • Size: 124.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for langchain_agentkit-0.4.0.tar.gz
Algorithm Hash digest
SHA256 6911ca37de1dc6915274de8e5bf75a55c0a4370ae50dbd0d4acafb479606eef1
MD5 ae7fc802029c20c45bbe0265fc0de21a
BLAKE2b-256 66e3ea441b8c4c5af7ef7da13b85efedc7cad5e9c106c117aa609a25aa2a1fe2

See more details on using hashes here.

File details

Details for the file langchain_agentkit-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: langchain_agentkit-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 30.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for langchain_agentkit-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 981d738c23848a8713050827c999e728e24af174a0d1f872653e131e9ceb3a62
MD5 8eb7d184f03e68123d3095e224825bd8
BLAKE2b-256 ebe6fc586822868e768c874be86ff6fbaaf16992da2fdbbda1f06c18583a5684

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page