Library of composable node types for building AI agent graphs
Project description
quartermaster-nodes
Composable node types for building AI agent graphs.
Features
- 40 built-in node types across 6 categories: LLM, control flow, data, user interaction, memory, and utility
- Framework-agnostic via the
NodeContextprotocol -- integrates with any runtime - Stateless class-based design -- all nodes use
@classmethodwith no instance state - Chain-of-Responsibility pattern for composable LLM processing pipelines
- NodeRegistry with auto-discovery, version tracking, and catalog generation
- Configurable flow behavior per node: traversal strategy, thought type, message type, error handling
Installation
pip install quartermaster-nodes
Dependencies: jinja2>=3.1, quartermaster-graph (enums), quartermaster-providers (LLM config)
Quick Start
Register and Discover Nodes
from quartermaster_nodes import NodeRegistry
from quartermaster_nodes.nodes import InstructionNodeV1, StartNodeV1, EndNodeV1, Decision1
# Manual registration
registry = NodeRegistry()
registry.register(StartNodeV1)
registry.register(InstructionNodeV1)
registry.register(Decision1)
registry.register(EndNodeV1)
# Or auto-discover all built-in nodes
registry = NodeRegistry()
count = registry.discover("quartermaster_nodes.nodes")
print(f"Discovered {count} nodes")
# Look up a node by name
node_cls = registry.get("InstructionNode")
print(node_cls.info().description)
# Generate a JSON catalog of all nodes
catalog = registry.catalog_json()
Creating a Custom Node
from quartermaster_nodes import AbstractAssistantNode, AssistantInfo, FlowNodeConf
from quartermaster_nodes.enums import (
AvailableTraversingIn,
AvailableTraversingOut,
AvailableThoughtTypes,
AvailableMessageTypes,
)
class HttpRequestNode(AbstractAssistantNode):
"""Fetch data from an HTTP endpoint."""
@classmethod
def info(cls) -> AssistantInfo:
info = AssistantInfo()
info.version = cls.version()
info.description = "Make an HTTP request and return the response"
info.metadata = {"url": "", "method": "GET"}
return info
@classmethod
def name(cls) -> str:
return "HttpRequest"
@classmethod
def version(cls) -> str:
return "1.0"
@classmethod
def flow_config(cls) -> FlowNodeConf:
return FlowNodeConf(
traverse_in=AvailableTraversingIn.AwaitFirst,
traverse_out=AvailableTraversingOut.SpawnAll,
thought_type=AvailableThoughtTypes.NewThought1,
message_type=AvailableMessageTypes.Tool,
)
@classmethod
def think(cls, ctx) -> None:
import urllib.request
url = cls.get_metadata_key_value(ctx, "url", "")
method = cls.get_metadata_key_value(ctx, "method", "GET")
req = urllib.request.Request(url, method=method)
with urllib.request.urlopen(req) as response:
body = response.read().decode()
ctx.handle.append_text(body)
Implementing the NodeContext Protocol
Nodes are framework-agnostic. Implement the NodeContext protocol to integrate with your runtime:
from quartermaster_nodes.protocols import NodeContext
class MyRuntimeContext:
"""Adapts your runtime to the NodeContext protocol."""
def __init__(self, node_config, thought):
self._config = node_config
self._thought = thought
@property
def node_metadata(self) -> dict:
return self._config.metadata
@property
def flow_node_id(self):
return self._config.id
@property
def thought_id(self):
return self._thought.id if self._thought else None
@property
def thought(self):
return self._thought
@property
def handle(self):
return self._thought # Must implement ThoughtHandle protocol
@property
def assistant_node(self):
return self._config
@property
def chat_id(self):
return None
# Execute a node
ctx = MyRuntimeContext(node_config, thought)
InstructionNodeV1.think(ctx)
Node Categories
LLM Nodes (9)
| Node | Description |
|---|---|
InstructionNodeV1 |
Generate response from system instructions and conversation history |
InstructionImageVision1 |
LLM with image/vision input support |
InstructionParameters1 |
LLM with structured parameter extraction |
InstructionProgram1 |
LLM with tool/function execution |
InstructionProgramParameters1 |
LLM with tools and structured output |
Decision1 |
LLM picks a branch path |
| AgentNodeV1 | Autonomous agent with agentic loop and tool orchestration |
| Summarize1 | LLM-powered summarization |
| Merge1 | Combine parallel branches via LLM |
Control Flow Nodes (6)
| Node | Description |
|---|---|
StartNodeV1 |
Flow entry point, initializes memory |
EndNodeV1 |
Flow termination |
BreakNode1 |
Message collection boundary |
IfNode |
Binary conditional branching via expression evaluation |
SwitchNode1 |
Multi-way branching |
SubAssistant1 |
Invoke a sub-flow |
Data Nodes (9)
| Node | Description |
|---|---|
StaticNode1 |
Output static text content |
StaticMerge1 |
Merge with static content |
StaticDecision1 |
Rule-based decision (no LLM) |
StaticProgramParameters1 |
Static tool parameters |
VarNode |
Variable assignment via expression |
TextNode |
Jinja2 template rendering |
TextToVariableNode |
Extract text into a variable |
CodeNode |
Custom code definition |
ProgramRunner1 |
Execute a registered tool |
User Interaction Nodes (4)
| Node | Description |
|---|---|
UserNode1 |
Wait for user message input |
UserDecisionV1 |
Present choices to the user |
UserFormV1 |
Collect structured input via form |
UserProgramFormV1 |
User selects and configures a tool |
Memory Nodes (5)
| Node | Description |
|---|---|
FlowMemoryNode |
Flow-scoped persistent memory |
ReadMemoryNode |
Read from persistent memory |
WriteMemoryNode |
Write to persistent memory |
UpdateMemoryNode |
Update existing memory entries |
UserMemoryNode |
User-scoped persistent memory |
Utility Nodes (6)
| Node | Description |
|---|---|
BlankNode |
No-op placeholder |
CommentNode |
Documentation / annotation node |
ViewMetadataNode |
Debug: inspect thought metadata |
UseEnvironmentNode |
Activate an execution environment |
UnselectEnvironmentNode |
Deactivate current environment |
UseFileNode |
Attach a file to the context |
API Reference
AbstractAssistantNode
Base class for all nodes. All methods are @classmethod.
| Method | Description |
|---|---|
info() -> AssistantInfo |
Node metadata (description, default config) |
name() -> str |
Display name |
version() -> str |
Version string |
flow_config() -> FlowNodeConf |
Traversal, thought type, message type, error handling |
think(ctx: NodeContext) -> None |
Core execution logic |
get_metadata_key_value(ctx, key, default) |
Read from node metadata |
store_metadata_key_value(ctx, key, value) |
Write to node metadata |
deprecated() -> bool |
Whether this node is deprecated (default: False) |
AbstractLLMAssistantNode
Extends AbstractAssistantNode with LLM configuration.
| Method | Description |
|---|---|
llm_config(ctx) -> LLMConfig |
Build LLM config from node metadata |
context_manager_config(ctx, llm_config) -> ContextManagerConfig |
Build context management config |
Configurable metadata keys: llm_model, llm_provider, llm_temperature, llm_max_input_tokens, llm_max_output_tokens, llm_max_messages, llm_stream, llm_vision, llm_system_instruction, llm_thinking_level.
FlowNodeConf
Defines a node's flow behavior constraints.
| Field | Type | Description |
|---|---|---|
traverse_in |
AvailableTraversingIn |
AwaitFirst or AwaitAll |
traverse_out |
AvailableTraversingOut |
SpawnAll, SpawnNone, SpawnStart, SpawnPickedNode |
thought_type |
AvailableThoughtTypes |
How thoughts are created/displayed |
message_type |
AvailableMessageTypes |
Message role (Automatic, User, Assistant, etc.) |
error_handling_strategy |
AvailableErrorHandlingStrategies |
Stop, Continue, or Retry |
NodeRegistry
| Method | Description |
|---|---|
register(node_class) |
Register a node class |
get(name, version=None) |
Look up by name and optional version |
has(name, version=None) -> bool |
Check if a node is registered |
list_nodes() -> list[dict] |
List all nodes with metadata |
catalog_json() -> list[dict] |
JSON-serializable catalog |
discover(package) -> int |
Auto-discover nodes from a package |
count -> int |
Number of registered nodes |
Chain-of-Responsibility
LLM nodes use a composable handler chain for processing:
from quartermaster_nodes.chain import Chain
from quartermaster_nodes.chain.handlers import (
ValidateMemoryID,
PrepareMessages,
ContextManager,
TransformToProvider,
GenerateStreamResponse,
ProcessStreamResponse,
)
chain = (
Chain()
.add_handler(ValidateMemoryID())
.add_handler(PrepareMessages(client, config))
.add_handler(ContextManager(client, config, ctx_config))
.add_handler(TransformToProvider(transformer))
.add_handler(GenerateStreamResponse(client, config))
.add_handler(ProcessStreamResponse())
)
result = chain.run({"memory_id": thought_id, "flow_node_id": node_id, "ctx": ctx})
Integration with Sibling Packages
With quartermaster-graph (graph schema)
Enums are re-exported from quartermaster-graph for consistency:
from quartermaster_nodes.enums import AvailableNodeTypes # Alias for quartermaster_graph.enums.NodeType
from quartermaster_graph.enums import NodeType # Canonical source
With quartermaster-engine (execution runtime)
The engine resolves node types from the registry and calls think() through an adapter:
from quartermaster_nodes import NodeRegistry
from quartermaster_nodes.nodes import InstructionNodeV1
registry = NodeRegistry()
registry.discover("quartermaster_nodes.nodes")
# The engine looks up nodes by name and invokes think()
node_cls = registry.get("InstructionNode", "1.0")
node_cls.think(execution_context)
Contributing
See CONTRIBUTING.md for guidelines.
License
Apache License 2.0 -- see LICENSE for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file quartermaster_nodes-0.0.1.tar.gz.
File metadata
- Download URL: quartermaster_nodes-0.0.1.tar.gz
- Upload date:
- Size: 99.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9c83bfb8bb542a15698c082db630b4d5e58992f647b11b9556e53ac770c498a1
|
|
| MD5 |
5314657057e3fd29509f9eec5847dc27
|
|
| BLAKE2b-256 |
90c12accb763e0074adb2099b4c72f076e57b6ba5586540f6ce3850b1c51acc8
|
File details
Details for the file quartermaster_nodes-0.0.1-py3-none-any.whl.
File metadata
- Download URL: quartermaster_nodes-0.0.1-py3-none-any.whl
- Upload date:
- Size: 71.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
412de82dc40666ad8a8aa58693f2fea2a084574b1ac5759f5c438fbbe805198b
|
|
| MD5 |
89a88c9e30d03369ed0cc1015c4aecdb
|
|
| BLAKE2b-256 |
a9de6f5b5b0fd22da3033cf1649a2978a2d14692115dc0420170a631900e7fbc
|