Node Flags
Every node in a Reasoning Graph declares its capabilities and behaviors through typed flags. Flags are checked by the engine at runtime to control execution flow, caching, error handling, and observability.
Using Flags
from promptise.engine import PromptNode, NodeFlag
# Set flags on any node
PromptNode("analyze",
instructions="Analyze the data.",
flags={NodeFlag.CRITICAL, NodeFlag.OBSERVABLE},
)
# Functional nodes support flags too
@node("validate", flags={NodeFlag.VALIDATE_OUTPUT, NodeFlag.RETRYABLE})
async def validate(state: GraphState) -> NodeResult:
...
# Check flags at runtime
if node.has_flag(NodeFlag.CRITICAL):
...
Flag Reference
Execution Control
| Flag |
Engine Behavior |
ENTRY |
Marks the graph's starting node. Set via is_entry=True. |
TERMINAL |
Reaching this node can end the graph. Set via is_terminal=True. |
CRITICAL |
If this node errors, the engine aborts the entire graph immediately. The error is captured in ExecutionReport.error. |
SKIP_ON_ERROR |
If the previous node produced an error, this node is skipped entirely. Useful for optional enrichment steps. |
RETRYABLE |
On failure, the engine retries this node with exponential backoff (0.5s, 1s, 2s...) up to max_iterations times. Between retries, state.context["_retry_error"] contains the last error so the node can adapt. |
REQUIRES_HUMAN |
Flags state.context["_awaiting_human"] with the node name. The node still executes, but downstream consumers can check this flag to pause for human input. |
Context & Memory
| Flag |
Engine Behavior |
NO_HISTORY |
The engine strips all conversation messages before execution, keeping only the system message. After the node runs, original messages are restored (plus any new messages the node added). |
ISOLATED_CONTEXT |
The node runs with a clean state.context containing only its input_keys data. After execution, the original context is restored and only output_key is merged back. Prevents context pollution between nodes. |
CACHEABLE |
The engine caches the node's NodeResult keyed on node.name + input_keys values. On subsequent visits with the same inputs, the cached result is returned without re-executing. |
Model Selection
| Flag |
Engine Behavior |
INJECT_TOOLS |
The node receives all MCP tools discovered by build_agent() at runtime. Set via inject_tools=True on PromptNode. |
LIGHTWEIGHT |
If the node has no model_override and the engine was created with a lightweight_model, the engine swaps to that model for this node. Use for routing, classification, or simple analysis where a smaller model suffices. |
Observability
| Flag |
Engine Behavior |
OBSERVABLE |
Emits an additional on_node_metrics event to hooks with timing, token count, and tool call stats. |
VERBOSE |
Logs the full raw output at DEBUG level. Use for audit-critical nodes. |
Output Processing
| Flag |
Engine Behavior |
SUMMARIZE_OUTPUT |
If raw_output exceeds 1000 characters, the engine calls the LLM to produce a concise summary. Preserves key facts, numbers, and conclusions while removing redundancy. |
VALIDATE_OUTPUT |
Validates result.output against the node's output_schema (Pydantic model or dict). Validation errors are added to result.guards_failed. |
Concurrency & State
| Flag |
Engine Behavior |
READONLY |
Declares that this node only reads state, never writes. Used by ParallelNode and the engine to determine safe concurrent execution. |
STATEFUL |
Declares that this node modifies state.context. Used for dependency tracking. |
PARALLEL_SAFE |
Declares that this node can run concurrently with other PARALLEL_SAFE nodes. |
Pre-built Reasoning Node Flags
Each reasoning node ships with sensible default flags:
| Node |
Default Flags |
Rationale |
| ThinkNode |
READONLY, LIGHTWEIGHT |
Pure reasoning, no state writes, smaller model sufficient |
| ReflectNode |
STATEFUL, OBSERVABLE |
Writes reflections to state, worth logging |
| ObserveNode |
STATEFUL |
Writes entities/facts to state.context |
| JustifyNode |
READONLY, OBSERVABLE, VERBOSE |
Audit trail — full logging matters |
| CritiqueNode |
READONLY, OBSERVABLE |
Pure analysis, severity worth logging |
| PlanNode |
STATEFUL, OBSERVABLE |
Writes to state.plan |
| SynthesizeNode |
OBSERVABLE |
Final output, important to capture |
| ValidateNode |
READONLY, VALIDATE_OUTPUT |
Quality gate, validates own output |
| RetryNode |
RETRYABLE |
Built for retry behavior |
| FanOutNode |
PARALLEL_SAFE |
Designed for concurrent execution |
Custom Flags
The flag set accepts any hashable value. You can define domain-specific flags:
class MyFlags:
AUDIT_REQUIRED = "audit_required"
PII_SENSITIVE = "pii_sensitive"
PromptNode("handle_user_data",
flags={MyFlags.PII_SENSITIVE, NodeFlag.OBSERVABLE},
)
# Check in hooks
class AuditHook:
async def post_node(self, node, result, state):
if node.has_flag(MyFlags.AUDIT_REQUIRED):
await audit_log.record(node.name, result)
return result
Engine Configuration
from promptise.engine import PromptGraphEngine
engine = PromptGraphEngine(
graph=graph,
model=main_model,
lightweight_model=small_model, # Used for LIGHTWEIGHT nodes
)