Planner¶
The ReactPlanner and its supporting configuration: tool context, reflection and
tool-selection policies, parallel-call/join primitives, and trajectory objects.
Skill provider and configuration types re-exported by the planner are documented under Skills.
planner
¶
Planner entry points.
ReactPlanner
¶
ReactPlanner(llm: str | Mapping[str, Any] | None = None, *, nodes: Sequence[Node] | None = None, catalog: Sequence[NodeSpec] | None = None, registry: ModelRegistry | None = None, llm_client: JSONLLMClient | None = None, max_iters: int = 8, temperature: float = 0.0, json_schema_mode: bool = True, system_prompt_extra: str | None = None, token_budget: int | None = None, pause_enabled: bool = True, state_store: Any | None = None, artifact_store: ArtifactStore | None = None, observation_guardrail: ObservationGuardrailConfig | None = None, summarizer_llm: str | Mapping[str, Any] | None = None, planning_hints: Mapping[str, Any] | None = None, repair_attempts: int = 3, max_consecutive_arg_failures: int = 3, arg_fill_enabled: bool = True, deadline_s: float | None = None, hop_budget: int | None = None, time_source: Callable[[], float] | None = None, event_callback: PlannerEventCallback | None = None, llm_timeout_s: float = 360.0, llm_max_retries: int = 3, use_native_reasoning: bool = True, reasoning_effort: str | None = None, reasoning_display: ReasoningDisplay = None, llm_fallback: ModelFallbackConfig | None = None, absolute_max_parallel: int = 50, reflection_config: ReflectionConfig | None = None, reflection_llm: str | Mapping[str, Any] | None = None, tool_policy: ToolPolicy | None = None, stream_final_response: bool = False, final_response_model: type[BaseModel] | None = None, final_response_retries: int = 1, tool_call_mode: str = 'prompted', llm_transport: str | None = None, short_term_memory: ShortTermMemory | ShortTermMemoryConfig | None = None, background_tasks: BackgroundTasksConfig | None = None, error_recovery: ErrorRecoveryConfig | None = None, multi_action_sequential: bool = False, multi_action_read_only_only: bool = True, multi_action_max_tools: int = 2, auto_seq_enabled: bool = False, auto_seq_execute: bool = False, auto_seq_read_only_only: bool = True, use_native_llm: bool = False, guardrail_gateway: Any | None = None, guardrail_conversation_history_turns: int = 1, tool_search: ToolSearchConfig | None = None, tool_examples: ToolExamplesConfig | None = None, skills: SkillsConfig | None = None, skills_provider: Any | None = None, skills_provider_factory: Any | None = None, llm_context_hooks: Sequence[LLMContextHook] | None = None)
JSON-only ReAct planner for autonomous multi-step workflows.
The ReactPlanner orchestrates a loop where an LLM selects and sequences PenguiFlow nodes/tools based on structured JSON contracts. It supports pause/resume for approvals, adaptive re-planning on failures, parallel execution, and trajectory compression for long-running sessions.
Thread Safety
NOT thread-safe. Create separate planner instances per task.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
llm
|
str | Mapping[str, Any] | None
|
LiteLLM model name (e.g., "gpt-4") or config dict. Required if llm_client is not provided. |
None
|
nodes
|
Sequence[Node] | None
|
Sequence of PenguiFlow nodes to make available as tools. Either (nodes + registry) or catalog must be provided. |
None
|
catalog
|
Sequence[NodeSpec] | None
|
Pre-built tool catalog. If provided, nodes and registry are ignored. |
None
|
registry
|
ModelRegistry | None
|
Model registry for type resolution. Required if nodes is provided. |
None
|
llm_client
|
JSONLLMClient | None
|
Custom LLM client implementation. If provided, llm is ignored. |
None
|
max_iters
|
int
|
Maximum planning iterations before returning no_path. Default: 8. |
8
|
temperature
|
float
|
LLM sampling temperature. Default: 0.0 for deterministic output. |
0.0
|
json_schema_mode
|
bool
|
Enable strict JSON schema enforcement via LLM response_format. Default: True. |
True
|
system_prompt_extra
|
str | None
|
Optional instructions for interpreting custom context (e.g., memory format). Use this to specify how the planner should use structured data passed via llm_context. The library provides baseline injection; this parameter lets you define format-specific semantics. Examples:
|
None
|
token_budget
|
int | None
|
If set, triggers trajectory summarization when history exceeds limit. Token count is estimated by character length (approx). |
None
|
pause_enabled
|
bool
|
Allow nodes to trigger pause/resume flow. Default: True. |
True
|
state_store
|
Any | None
|
Optional durable state adapter for pause/resume persistence. |
None
|
summarizer_llm
|
str | Mapping[str, Any] | None
|
Separate (cheaper) LLM for trajectory compression. Falls back to main LLM if not set. |
None
|
reflection_config
|
ReflectionConfig | None
|
Optional configuration enabling automatic answer critique before finishing. Disabled by default. |
None
|
reflection_llm
|
str | Mapping[str, Any] | None
|
Optional LiteLLM identifier used for critique when
|
None
|
planning_hints
|
Mapping[str, Any] | None
|
Structured constraints and preferences (ordering, disallowed nodes, max_parallel, etc.). See plan.md for schema. |
None
|
tool_policy
|
ToolPolicy | None
|
Optional runtime policy that filters the tool catalog (whitelists, blacklists, or tag requirements) for multi-tenant and safety use cases. |
None
|
repair_attempts
|
int
|
Max attempts to repair invalid JSON from LLM. Default: 3. |
3
|
max_consecutive_arg_failures
|
int
|
Max consecutive tool arg validation failures before forcing a finish with requires_followup=True. Helps small models avoid infinite loops when they repeatedly produce invalid args. Default: 3. |
3
|
arg_fill_enabled
|
bool
|
Enable arg-fill mode for missing tool arguments. When True, if a tool call has valid tool selection but missing/invalid args, the planner will make a simplified LLM call asking only for the missing values instead of requiring a full JSON repair. This significantly improves success rates for small models. Default: True. |
True
|
deadline_s
|
float | None
|
Wall-clock deadline for planning session (seconds from start). |
None
|
hop_budget
|
int | None
|
Maximum tool invocations allowed. |
None
|
time_source
|
Callable[[], float] | None
|
Override time.monotonic for testing. |
None
|
event_callback
|
PlannerEventCallback | None
|
Optional callback receiving PlannerEvent instances for observability. |
None
|
llm_timeout_s
|
float
|
Per-LLM-call timeout in seconds. Default: 360.0. |
360.0
|
llm_max_retries
|
int
|
Max retry attempts for transient LLM failures. Default: 3. |
3
|
absolute_max_parallel
|
int
|
System-level safety limit on parallel execution regardless of hints. Default: 50. |
50
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If neither (nodes + registry) nor catalog is provided, or if neither llm nor llm_client is provided. |
RuntimeError
|
If LiteLLM is not installed and llm_client is not provided. |
Example
planner = ReactPlanner( ... llm="gpt-4", ... nodes=[triage_node, retrieve_node, summarize_node], ... registry=my_registry, ... max_iters=10, ... ) result = await planner.run("Explain PenguiFlow's architecture") print(result.reason) # "answer_complete", "no_path", or "budget_exhausted"
artifact_store
property
¶
artifact_store: ArtifactStore
Return the configured artifact store (NoOp when disabled).
fork
¶
fork(*, catalog_filter: Callable[[NodeSpec], bool] | None = None, tool_policy: ToolPolicy | None = None, inherit_policy: bool = True, background_tasks: BackgroundTasksConfig | None | Literal['inherit'] = 'inherit') -> ReactPlanner
Create a new planner instance with the same configuration.
Background task orchestration requires a fresh ReactPlanner per task because the planner maintains mutable per-run state and is not thread-safe.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
catalog_filter
|
Callable[[NodeSpec], bool] | None
|
Optional predicate applied to each NodeSpec to restrict which tools the forked planner exposes. |
None
|
tool_policy
|
ToolPolicy | None
|
Optional runtime policy applied to the forked planner. When
|
None
|
inherit_policy
|
bool
|
When True, merge |
True
|
background_tasks
|
BackgroundTasksConfig | None | Literal['inherit']
|
Background tasks configuration for the forked planner. Defaults to
|
'inherit'
|
Returns:
| Type | Description |
|---|---|
ReactPlanner
|
A new ReactPlanner instance sharing this planner's configuration. |
run
async
¶
run(query: str, *, input_parts: Sequence[ContentPart] | None = None, llm_context: Mapping[str, Any] | None = None, context_meta: Mapping[str, Any] | None = None, tool_context: Mapping[str, Any] | None = None, memory_key: MemoryKey | None = None, steering: SteeringInbox | None = None, tool_visibility: ToolVisibilityPolicy | None = None) -> PlannerFinish | PlannerPause
Execute planner on a query until completion or pause.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
query
|
str
|
Natural language task description. |
required |
input_parts
|
Sequence[ContentPart] | None
|
Optional image/audio content parts appended to the initial user message. Omitted by default to preserve text-only behavior. |
None
|
llm_context
|
Mapping[str, Any] | None
|
Optional context visible to LLM (memories, status_history, etc.). Should NOT include internal metadata like tenant_id or trace_id. |
None
|
context_meta
|
Mapping[str, Any] | None
|
Deprecated: Use llm_context instead. This parameter is kept for backward compatibility but will be removed in a future version. |
None
|
tool_context
|
Mapping[str, Any] | None
|
Tool-only context (callbacks, loggers, telemetry objects). Not visible to the LLM. May contain non-serialisable objects. |
None
|
memory_key
|
MemoryKey | None
|
Optional explicit short-term memory key. If omitted, the planner may
derive a key from |
None
|
steering
|
SteeringInbox | None
|
Optional steering inbox for injecting mid-run guidance into the planning loop. |
None
|
tool_visibility
|
ToolVisibilityPolicy | None
|
Optional policy restricting which tools are visible for this call. |
None
|
Returns:
| Type | Description |
|---|---|
PlannerFinish | PlannerPause
|
PlannerFinish if task completed/failed, PlannerPause if paused for human intervention. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If LLM client fails after all retries. |
resume
async
¶
resume(token: str, user_input: str | None = None, *, tool_context: Mapping[str, Any] | None = None, memory_key: MemoryKey | None = None, steering: SteeringInbox | None = None, tool_visibility: ToolVisibilityPolicy | None = None) -> PlannerFinish | PlannerPause
Resume a paused planning session.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
Resume token from a previous PlannerPause. |
required |
user_input
|
str | None
|
Optional user response to the pause (e.g., approval decision). |
None
|
tool_context
|
Mapping[str, Any] | None
|
Tool-only context (callbacks, loggers, telemetry objects). Not visible to the LLM. May contain non-serialisable objects. Overrides any tool_context captured in the pause record. |
None
|
memory_key
|
MemoryKey | None
|
Optional explicit short-term memory key for the resumed session. If
omitted, the planner may derive a key from |
None
|
steering
|
SteeringInbox | None
|
Optional steering inbox for injecting mid-run guidance into the planning loop. |
None
|
tool_visibility
|
ToolVisibilityPolicy | None
|
Optional policy restricting which tools are visible for this call. |
None
|
Returns:
| Type | Description |
|---|---|
PlannerFinish | PlannerPause
|
Updated result after resuming execution. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If resume token is invalid or expired. |
pause
async
¶
pause(reason: PlannerPauseReason, payload: Mapping[str, Any] | None = None) -> PlannerPause
ToolContext
¶
Bases: Protocol
Protocol for planner tool execution context.
tool_context
property
¶
tool_context: dict[str, Any]
Tool-only context (callbacks, telemetry objects, loggers, etc.).
meta
property
¶
meta: MutableMapping[str, Any]
Combined context. Deprecated: prefer llm_context/tool_context.
artifacts
property
¶
artifacts: ScopedArtifacts
Scoped artifact facade for tool developers.
Example
ref = await ctx.artifacts.upload( pdf_bytes, mime_type="application/pdf", filename="report.pdf", ) return {"artifact": ref, "summary": "Downloaded PDF"}
kv
property
¶
kv: SessionKV
Durable session key/value facade.
Backed by the configured StateStore's optional memory persistence. Default scope is session-scoped with no TTL. Task scope is opt-in and uses a fixed TTL of 3600 seconds.
pause
¶
pause(reason: PlannerPauseReason, payload: Mapping[str, Any] | None = None) -> Awaitable[Any]
Pause execution for human input or policy decisions.
emit_chunk
¶
emit_chunk(stream_id: str, seq: int, text: str, *, done: bool = False, meta: Mapping[str, Any] | None = None) -> Awaitable[None]
Emit a streaming chunk.
emit_artifact
¶
emit_artifact(stream_id: str, chunk: Any, *, done: bool = False, artifact_type: str | None = None, meta: Mapping[str, Any] | None = None) -> Awaitable[None]
Emit a streaming artifact chunk (e.g., partial chart config).
PlannerAction
¶
Bases: BaseModel
Unified action format (RFC_UNIFIED_ACTION_SCHEMA).
The LLM-facing schema is always:
- next_node: non-null string opcode or tool name
- args: object payload (defaults to {})
Internally, we keep a best-effort thought field for trajectory logging and
repair prompts, but it is excluded from the JSON schema so it is not required
(or encouraged) in structured outputs.
Special next_node values: - "final_response": Terminal action, args.answer streams to user - "parallel": Parallel execution, args contains steps and join config - "task.subagent": Background subagent task, args contains query and group config - "task.tool": Background single-tool job, args contains tool/tool_args and group config - Any other value: Tool call, args passed to the tool
next_node
class-attribute
instance-attribute
¶
next_node: str = Field(description="Non-null opcode or tool name: 'final_response', 'parallel', 'task.subagent', 'task.tool', or any other tool name for a regular tool call.")
args
class-attribute
instance-attribute
¶
args: dict[str, Any] = Field(default_factory=dict, description='Argument payload for next_node.')
thought
class-attribute
instance-attribute
¶
thought: SkipJsonSchema[str] = Field(default='', description='Best-effort reasoning trace for trajectory logging and repair prompts.')
raw_llm_response
class-attribute
instance-attribute
¶
raw_llm_response: SkipJsonSchema[str | None] = Field(default=None, exclude=True, description='Raw LLM response text, kept for debugging only (excluded from serialization).')
alternate_actions
class-attribute
instance-attribute
¶
alternate_actions: SkipJsonSchema[list[dict[str, Any]] | None] = Field(default=None, exclude=True, description='Additional action candidates parsed from mixed model output, if any.')
answer_text
¶
answer_text() -> str | None
Extract answer from args.answer or args.raw_answer (backward compatible).
PlannerFinish
¶
Bases: BaseModel
Terminal result of a planner run.
reason
class-attribute
instance-attribute
¶
reason: Literal['answer_complete', 'no_path', 'budget_exhausted'] = Field(description='Why the planner finished.')
payload
class-attribute
instance-attribute
¶
payload: Any = Field(default=None, description='Final answer payload, if any.')
metadata
class-attribute
instance-attribute
¶
metadata: dict[str, Any] = Field(default_factory=dict, description='Additional metadata about the run.')
PlannerPause
¶
Bases: BaseModel
Signals that planner execution has paused and awaits external resumption.
reason
class-attribute
instance-attribute
¶
reason: PlannerPauseReason = Field(description='Why the planner paused (e.g. HITL confirmation).')
payload
class-attribute
instance-attribute
¶
payload: dict[str, Any] = Field(default_factory=dict, description='Data associated with the pause reason.')
resume_token
class-attribute
instance-attribute
¶
resume_token: str = Field(description='Opaque token required to resume this paused run.')
PlannerEvent
dataclass
¶
PlannerEvent(event_type: str, ts: float, trajectory_step: int, thought: str | None = None, node_name: str | None = None, latency_ms: float | None = None, token_estimate: int | None = None, error: str | None = None, extra: Mapping[str, Any] = dict())
Structured event emitted during planner execution for observability.
to_payload
¶
to_payload() -> dict[str, Any]
Render a dictionary payload suitable for structured logging.
ReflectionConfig
¶
Bases: BaseModel
Configuration controlling the reflection loop behaviour.
enabled
class-attribute
instance-attribute
¶
enabled: bool = Field(default=False, description='Whether the reflection/critique loop is enabled.')
criteria
class-attribute
instance-attribute
¶
criteria: ReflectionCriteria = Field(default_factory=ReflectionCriteria, description='Quality criteria used by the reflection LLM to critique answers.')
quality_threshold
class-attribute
instance-attribute
¶
quality_threshold: float = Field(default=0.8, ge=0.0, le=1.0, description='Minimum critique score required to accept an answer.')
max_revisions
class-attribute
instance-attribute
¶
max_revisions: int = Field(default=2, ge=1, le=10, description='Maximum number of revision attempts before giving up.')
use_separate_llm
class-attribute
instance-attribute
¶
use_separate_llm: bool = Field(default=False, description="Whether to use a separate LLM client for reflection instead of the planner's main client.")
ReflectionCriteria
¶
Bases: BaseModel
Quality criteria used when critiquing an answer.
completeness
class-attribute
instance-attribute
¶
completeness: str = Field(default='Addresses all parts of the query', description='Criterion describing what counts as a complete answer.')
accuracy
class-attribute
instance-attribute
¶
accuracy: str = Field(default='Factually correct based on observations', description='Criterion describing what counts as an accurate answer.')
clarity
class-attribute
instance-attribute
¶
clarity: str = Field(default='Well-explained and coherent', description='Criterion describing what counts as a clear answer.')
ReflectionCritique
¶
Bases: BaseModel
Structured critique returned by the reflection LLM.
suggestions
class-attribute
instance-attribute
¶
suggestions: list[str] = Field(default_factory=list)
ErrorRecoveryConfig
dataclass
¶
ErrorRecoveryConfig(enabled: bool = True, max_compress_retries: int = 1, compression_threshold_chars: int = 2000, summarize_on_compress: bool = True)
Configuration for LLM error recovery.
compression_threshold_chars
class-attribute
instance-attribute
¶
compression_threshold_chars: int = 2000
LLMContextHook
¶
Bases: Protocol
Hook that can patch llm_context before the first LLM call.
Hooks are opt-in and best-effort; failures should not block a run.
Optional attributes (read via getattr):
- name: str: Used for events/logging. Defaults to class name.
- overwrite: bool: When True, the hook may overwrite existing keys.
Default: False.
LLMContextHookInput
dataclass
¶
LLMContextHookInput(query: str, llm_context: Mapping[str, Any], tool_context: Mapping[str, Any], memory_key: MemoryKey | None)
Inputs to LLM context hooks.
llm_context and tool_context are the normalized values that will be used
for the run. Hooks should treat them as read-only.
LLMErrorType
¶
JoinInjection
¶
Bases: BaseModel
Mapping of join args to parallel execution data sources.
mapping
class-attribute
instance-attribute
¶
mapping: dict[str, str] = Field(default_factory=dict, description="Mapping of join argument names to result references (e.g. '$results').")
ParallelCall
¶
Bases: BaseModel
Single tool invocation within a parallel execution plan.
ParallelJoin
¶
Bases: BaseModel
Aggregation step run after all parallel calls in a plan complete.
node
class-attribute
instance-attribute
¶
node: str = Field(description='Name of the tool/node to invoke for aggregation.')
args
class-attribute
instance-attribute
¶
args: dict[str, Any] = Field(default_factory=dict, description='Arguments passed to the join call.')
inject
class-attribute
instance-attribute
¶
inject: JoinInjection | None = Field(default=None, description="Optional mapping of parallel results into the join call's args.")
ToolPolicy
¶
Bases: BaseModel
Runtime policy for tool availability and permissions.
allowed_tools
class-attribute
instance-attribute
¶
allowed_tools: set[str] | None = Field(default=None, description='If set, only tool names in this set may be used; all others are denied.')
denied_tools
class-attribute
instance-attribute
¶
denied_tools: set[str] = Field(default_factory=set, description='Tool names that are always denied.')
require_tags
class-attribute
instance-attribute
¶
require_tags: set[str] = Field(default_factory=set, description="Tags a tool's node must all carry to be allowed.")
is_allowed
¶
is_allowed(node_name: str, node_tags: Mapping[str, Any] | Sequence[str]) -> bool
Check whether a tool is permitted under this policy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node_name
|
str
|
Name of the tool/node being checked. |
required |
node_tags
|
Mapping[str, Any] | Sequence[str]
|
Tags associated with the node, either as a mapping (keys used as tags) or a sequence of tag strings. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the tool is allowed, False if it is denied, not in the allow |
bool
|
list, or missing one or more required tags. |
ToolVisibilityPolicy
¶
Bases: Protocol
Dynamic, per-run filtering for which tools are shown to the LLM.
This is opt-in and intended for per-tenant/per-user tool visibility without
constructing a brand new planner instance. Implementations must only return
specs from the provided specs sequence.
visible_tools
¶
visible_tools(specs: Sequence[NodeSpec], tool_context: Mapping[str, Any]) -> Sequence[NodeSpec]
ToolSearchConfig
¶
Bases: BaseModel
Configuration for on-demand tool search and lazy tool loading.
enabled
class-attribute
instance-attribute
¶
enabled: bool = Field(default=False, description='Whether tool search/lazy loading is enabled.')
cache_dir
class-attribute
instance-attribute
¶
cache_dir: str = Field(default='.penguiflow', description='Directory used to persist the tool search index.')
default_loading_mode
class-attribute
instance-attribute
¶
default_loading_mode: ToolLoadingMode = Field(default=ToolLoadingMode.ALWAYS, description='Default loading mode applied to tools without an explicit mode.')
always_loaded_patterns
class-attribute
instance-attribute
¶
always_loaded_patterns: list[str] = Field(default=['tasks.*', 'tool_search', 'tool_get', 'finish'], description='Name patterns for tools that are always loaded regardless of loading mode.')
activation_scope
class-attribute
instance-attribute
¶
activation_scope: Literal['run', 'session'] = Field(default='run', description='Scope at which a discovered tool remains activated: per-run or per-session.')
preferred_namespaces
class-attribute
instance-attribute
¶
preferred_namespaces: list[str] = Field(default=[], description='Namespaces prioritized when ranking tool search results.')
fts_fallback_to_regex
class-attribute
instance-attribute
¶
fts_fallback_to_regex: bool = Field(default=True, description='Whether to fall back to regex search when full-text search finds no matches.')
enable_incremental_index
class-attribute
instance-attribute
¶
enable_incremental_index: bool = Field(default=True, description='Whether to update the search index incrementally instead of rebuilding fully.')
rebuild_cache_on_init
class-attribute
instance-attribute
¶
rebuild_cache_on_init: bool = Field(default=False, description='Whether to force a full rebuild of the cached index on planner initialization.')
max_search_results
class-attribute
instance-attribute
¶
max_search_results: int = Field(default=10, description='Maximum number of results returned per search.')
hints
class-attribute
instance-attribute
¶
hints: ToolHintsConfig = Field(default_factory=ToolHintsConfig, description='Configuration for optional tool-search hints in the prompt.')
directory
class-attribute
instance-attribute
¶
directory: ToolDirectoryConfig = Field(default_factory=ToolDirectoryConfig, description='Configuration for the optional tool directory summary in the prompt.')
ToolExamplesConfig
¶
Bases: BaseModel
Configuration for including tool usage examples in the planner prompt.
enabled
class-attribute
instance-attribute
¶
enabled: bool = Field(default=True, description='Whether to include tool usage examples in the prompt.')
max_examples_per_tool
class-attribute
instance-attribute
¶
max_examples_per_tool: int = Field(default=3, ge=1, le=10, description='Maximum number of examples shown per tool.')
include_descriptions
class-attribute
instance-attribute
¶
include_descriptions: bool = Field(default=True, description='Whether to include a short description alongside each example.')
ToolDirectoryConfig
¶
Bases: BaseModel
Configuration for the tool directory summary shown to the planner LLM.
enabled
class-attribute
instance-attribute
¶
enabled: bool = Field(default=False, description='Whether to include the tool directory in the prompt.')
max_groups
class-attribute
instance-attribute
¶
max_groups: int = Field(default=20, ge=1, le=100, description='Maximum number of groups to list.')
max_tools_per_group
class-attribute
instance-attribute
¶
max_tools_per_group: int = Field(default=6, ge=0, le=50, description='Maximum number of tools listed per group.')
include_tool_counts
class-attribute
instance-attribute
¶
include_tool_counts: bool = Field(default=True, description='Whether to show the number of tools in each group.')
include_default_groups
class-attribute
instance-attribute
¶
include_default_groups: bool = Field(default=True, description='Whether to auto-generate default groups in addition to configured ones.')
groups
class-attribute
instance-attribute
¶
groups: list[ToolGroupConfig] = Field(default_factory=list, description='Explicitly configured tool groups.')
ToolGroupConfig
¶
Bases: BaseModel
Definition of a named tool group used to organize the tool directory.
name
class-attribute
instance-attribute
¶
name: str = Field(description='Unique identifier for the group.')
title
class-attribute
instance-attribute
¶
title: str | None = Field(default=None, description='Human-readable title shown in the directory.')
trigger
class-attribute
instance-attribute
¶
trigger: str | None = Field(default=None, description='Description of when to use tools in this group.')
task_type
class-attribute
instance-attribute
¶
task_type: Literal['browser', 'api', 'code', 'domain', 'unknown'] | None = Field(default=None, description="Category of task this group's tools address.")
match_namespaces
class-attribute
instance-attribute
¶
match_namespaces: list[str] = Field(default_factory=list, description='Tool namespaces whose members belong to this group.')
match_tags
class-attribute
instance-attribute
¶
match_tags: list[str] = Field(default_factory=list, description='Tags used to match tools into this group.')
match_name_patterns
class-attribute
instance-attribute
¶
match_name_patterns: list[str] = Field(default_factory=list, description='Glob/regex-like name patterns used to match tools into this group.')
tool_names
class-attribute
instance-attribute
¶
tool_names: list[str] = Field(default_factory=list, description='Explicit tool names assigned to this group.')
ToolHintsConfig
¶
Bases: BaseModel
Configuration for surfacing tool-search hints in the planner prompt.
enabled
class-attribute
instance-attribute
¶
enabled: bool = Field(default=False, description='Whether to include tool-search hints in the prompt.')
top_k
class-attribute
instance-attribute
¶
top_k: int = Field(default=5, ge=1, le=20, description='Maximum number of hinted tools to include.')
include_always_loaded
class-attribute
instance-attribute
¶
include_always_loaded: bool = Field(default=False, description='Whether always-loaded tools are also included among the hints.')
search_type
class-attribute
instance-attribute
¶
search_type: Literal['fts', 'regex', 'exact'] = Field(default='fts', description='Search strategy used to generate hints: full-text, regex, or exact match.')
BackgroundTasksConfig
¶
Bases: BaseModel
Configuration for background tasks/subagent orchestration.
This is the single source of truth for background task settings, consumed by: - ReactPlanner (prompt guidance, tool validation) - SessionManager/TaskService (runtime enforcement) - Spec generation engine (agent.yaml generation) - Template engine (scaffolding new agents)
Downstream teams configure these values in their agent's Config class, which builds this model before passing to ReactPlanner.
enabled
class-attribute
instance-attribute
¶
enabled: bool = False
Master switch for background task capabilities.
include_prompt_guidance
class-attribute
instance-attribute
¶
include_prompt_guidance: bool = True
Whether to inject background task guidance into the system prompt.
allow_tool_background
class-attribute
instance-attribute
¶
allow_tool_background: bool = False
Whether tools marked with background=True can spawn async tasks.
default_mode
class-attribute
instance-attribute
¶
default_mode: str = 'subagent'
Default execution mode: 'subagent' (full reasoning) or 'job' (single tool).
default_merge_strategy
class-attribute
instance-attribute
¶
default_merge_strategy: str = 'HUMAN_GATED'
How task results merge into context: HUMAN_GATED, APPEND, or REPLACE.
context_depth
class-attribute
instance-attribute
¶
context_depth: str = 'full'
Context snapshot depth for spawned tasks: 'full', 'summary', or 'minimal'.
propagate_on_cancel
class-attribute
instance-attribute
¶
propagate_on_cancel: str = 'cascade'
Cancel propagation: 'cascade' (cancel children), 'orphan' (leave running).
spawn_requires_confirmation
class-attribute
instance-attribute
¶
spawn_requires_confirmation: bool = False
Whether spawning a task requires explicit user confirmation.
max_concurrent_tasks
class-attribute
instance-attribute
¶
max_concurrent_tasks: int = 5
Maximum number of tasks running concurrently per session.
max_tasks_per_session
class-attribute
instance-attribute
¶
max_tasks_per_session: int = 50
Maximum total tasks (active + completed) per session.
task_timeout_s
class-attribute
instance-attribute
¶
task_timeout_s: int = 3600
Task timeout in seconds (default: 1 hour).
max_pending_steering
class-attribute
instance-attribute
¶
max_pending_steering: int = 2
Maximum steering messages queued per task before backpressure.
proactive_report_enabled
class-attribute
instance-attribute
¶
proactive_report_enabled: bool = False
Master switch for proactive messages on auto-merge completion.
proactive_report_strategies
class-attribute
instance-attribute
¶
proactive_report_strategies: list[str] = ['APPEND', 'REPLACE']
Merge strategies that trigger proactive reports (not HUMAN_GATED).
proactive_report_max_queued
class-attribute
instance-attribute
¶
proactive_report_max_queued: int = 5
Maximum queued reports before dropping oldest.
proactive_report_timeout_s
class-attribute
instance-attribute
¶
proactive_report_timeout_s: float = 30.0
Timeout for proactive message generation.
proactive_report_max_hops
class-attribute
instance-attribute
¶
proactive_report_max_hops: int = 2
Maximum proactive recursion hops before disabling background spawning.
proactive_report_fallback_notification
class-attribute
instance-attribute
¶
proactive_report_fallback_notification: bool = True
Fall back to notification panel if generation fails.
default_group_merge_strategy
class-attribute
instance-attribute
¶
default_group_merge_strategy: str = 'APPEND'
Default merge strategy for task groups.
default_group_report
class-attribute
instance-attribute
¶
default_group_report: str = 'all'
Default report strategy for groups: 'all', 'any', or 'none'.
group_timeout_s
class-attribute
instance-attribute
¶
group_timeout_s: float = 600.0
Timeout for group completion (seal to complete).
group_partial_on_failure
class-attribute
instance-attribute
¶
group_partial_on_failure: bool = True
If True, report partial results when some tasks in a group fail.
max_tasks_per_group
class-attribute
instance-attribute
¶
max_tasks_per_group: int = 10
Maximum tasks allowed in a single group.
auto_seal_groups_on_foreground_yield
class-attribute
instance-attribute
¶
auto_seal_groups_on_foreground_yield: bool = True
Auto-seal OPEN groups when foreground yields to user.
retain_turn_timeout_s
class-attribute
instance-attribute
¶
retain_turn_timeout_s: float = 60.0
Max time foreground waits for retained tasks/groups before force-yield.
background_continuation_max_hops
class-attribute
instance-attribute
¶
background_continuation_max_hops: int = 2
Maximum background continuation cycles after retain-timeout.
background_continuation_cooldown_s
class-attribute
instance-attribute
¶
background_continuation_cooldown_s: float = 0.0
Delay between background continuation cycles.
BackgroundTaskHandle
¶
Bases: BaseModel
Return type for tools that run asynchronously in the background.
task_id
class-attribute
instance-attribute
¶
task_id: str = Field(description='Unique identifier of the spawned background task.')
status
class-attribute
instance-attribute
¶
status: str = Field(default='PENDING', description='Current status of the task (e.g. PENDING, RUNNING).')
message
class-attribute
instance-attribute
¶
message: str | None = Field(default=None, description='Optional human-readable status message.')
BackgroundTaskResult
dataclass
¶
BackgroundTaskResult(task_id: str, group_id: str | None = None, status: Literal['completed', 'failed'] = 'completed', summary: str | None = None, payload: Any = None, facts: dict[str, Any] = dict(), artifacts: list[dict[str, Any]] = list(), consumed: bool = False, completed_at: float = time.time())
Result from a completed background task, stored in trajectory.
Attributes:
| Name | Type | Description |
|---|---|---|
task_id |
str
|
Unique identifier of the background task this result belongs to. |
group_id |
str | None
|
Optional identifier grouping related background tasks together. |
status |
Literal['completed', 'failed']
|
Terminal status of the task, either |
summary |
str | None
|
Optional human-readable summary of the task's outcome. |
payload |
Any
|
Arbitrary result payload produced by the task. |
facts |
dict[str, Any]
|
Structured facts extracted from the task's execution. |
artifacts |
list[dict[str, Any]]
|
List of artifact records (as dicts) produced by the task. |
consumed |
bool
|
Whether this result has already been consumed by the planner. |
completed_at |
float
|
Unix timestamp (seconds) when the task completed. |
artifacts
class-attribute
instance-attribute
¶
artifacts: list[dict[str, Any]] = field(default_factory=list)
completed_at
class-attribute
instance-attribute
¶
completed_at: float = field(default_factory=time.time)
from_payload
classmethod
¶
from_payload(payload: Mapping[str, Any]) -> BackgroundTaskResult | None
Build a :class:BackgroundTaskResult from a raw mapping payload.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payload
|
Mapping[str, Any]
|
Mapping containing at least a |
required |
Returns:
| Type | Description |
|---|---|
BackgroundTaskResult | None
|
BackgroundTaskResult | None: The constructed result, or |
BackgroundTaskResult | None
|
|
to_payload
¶
to_payload() -> dict[str, Any]
Serialise this result to a JSON-safe dict.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
dict[str, Any]: A dict with |
dict[str, Any]
|
|
dict[str, Any]
|
|
DSPyLLMClient
¶
DSPyLLMClient(llm: str | dict[str, Any], *, output_schema: type[BaseModel] | None = None, temperature: float = 0.0, max_retries: int = 3, timeout_s: float = 360.0, max_tokens: int = 4096)
LLM client using DSPy for structured outputs.
Deprecated
DSPyLLMClient is deprecated and unmaintained as of 3.11.0a6. It is no longer part of
PenguiFlow's supported LLM surface and does not participate in built-in rate-limit
fallback (llm_fallback). Use the native LLM layer (default) or
transport="pydantic-ai" instead; this class will be removed in a future release.
This client implements the JSONLLMClient protocol and uses DSPy's signature system to generate structured outputs. DSPy handles the prompt engineering and parsing internally, providing more reliable structured outputs across different LLM providers.
Benefits over direct LiteLLM: - Better structured output reliability across providers - Automatic prompt optimization for structure extraction - Works with models that don't support native JSON schema mode - Graceful degradation with retry logic
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
llm
|
str | dict[str, Any]
|
Model identifier (e.g., "gpt-4o-mini", "databricks/databricks-gpt-oss-120b") |
required |
temperature
|
float
|
Sampling temperature (0.0 = deterministic) |
0.0
|
max_retries
|
int
|
Number of retry attempts for transient failures |
3
|
timeout_s
|
float
|
Timeout per LLM call in seconds |
360.0
|
Example
client = DSPyLLMClient( ... llm="databricks/databricks-gpt-oss-120b", ... temperature=0.0, ... ) response = await client.complete( messages=[{"role": "user", "content": "..."}], response_format={"type": "json_schema", "json_schema": {...}}, )
from_base_client
classmethod
¶
from_base_client(base_client: DSPyLLMClient, output_schema: type[BaseModel]) -> DSPyLLMClient
Create a new DSPy client with a different output schema.
This factory method allows creating separate clients for different tasks (e.g., reflection, summarization) while reusing the same LLM configuration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_client
|
DSPyLLMClient
|
Existing DSPy client to clone configuration from |
required |
output_schema
|
type[BaseModel]
|
New output schema for the cloned client |
required |
Returns:
| Type | Description |
|---|---|
DSPyLLMClient
|
New DSPyLLMClient instance with same config but different output schema |
Example
planner_client = DSPyLLMClient(llm="gpt-4o") reflection_client = DSPyLLMClient.from_base_client( ... planner_client, ReflectionCritique ... )
complete
async
¶
complete(*, messages: Sequence[Mapping[str, Any]], response_format: Mapping[str, Any] | None = None, stream: bool = False, on_stream_chunk: Any = None, on_reasoning_chunk: Any = None) -> tuple[str, float]
Generate completion with structured output via DSPy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
messages
|
Sequence[Mapping[str, Any]]
|
OpenAI-style message list |
required |
response_format
|
Mapping[str, Any] | None
|
Optional JSON schema for structured output |
None
|
Returns:
| Type | Description |
|---|---|
tuple[str, float]
|
Tuple of JSON string response and cost in USD (DSPy cost is 0.0) |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If all retry attempts fail |
TimeoutError
|
If the call exceeds timeout_s |
Trajectory
dataclass
¶
Trajectory(query: str, llm_context: Mapping[str, Any] | None = None, tool_context: dict[str, Any] | None = None, input_parts: tuple[ContentPart, ...] = (), artifacts: dict[str, Any] = dict(), sources: list[Mapping[str, Any]] = list(), metadata: dict[str, Any] = dict(), steps: list[TrajectoryStep] = list(), summary: TrajectorySummary | None = None, hint_state: dict[str, Any] = dict(), resume_user_input: str | None = None, steering_inputs: list[str] = list(), background_results: dict[str, BackgroundTaskResult] = dict())
Mutable record of a planner run: inputs, executed steps, and derived state.
Tracks the original query and context alongside every :class:TrajectoryStep
executed so far, plus auxiliary state (artifacts, sources, background task
results, hints) needed to resume or summarise the run.
Attributes:
| Name | Type | Description |
|---|---|---|
query |
str
|
The original user query that started this trajectory. |
llm_context |
Mapping[str, Any] | None
|
Optional read-only context mapping shared with the LLM. |
tool_context |
dict[str, Any] | None
|
Optional mutable context mapping available to tools. |
input_parts |
tuple[ContentPart, ...]
|
Tuple of non-text content parts (images/audio) supplied with the query. |
artifacts |
dict[str, Any]
|
Named artifacts accumulated over the run. |
sources |
list[Mapping[str, Any]]
|
List of source records (as mappings) collected during the run. |
metadata |
dict[str, Any]
|
Arbitrary metadata associated with the run. |
steps |
list[TrajectoryStep]
|
Ordered list of executed :class: |
summary |
TrajectorySummary | None
|
Optional compact :class: |
hint_state |
dict[str, Any]
|
Mutable state used to track planner hints across steps. |
resume_user_input |
str | None
|
Optional user input supplied when resuming a paused run. |
steering_inputs |
list[str]
|
List of user steering messages injected during the run. |
background_results |
dict[str, BackgroundTaskResult]
|
Mapping of task id to :class: |
artifacts
class-attribute
instance-attribute
¶
artifacts: dict[str, Any] = field(default_factory=dict)
sources
class-attribute
instance-attribute
¶
sources: list[Mapping[str, Any]] = field(default_factory=list)
metadata
class-attribute
instance-attribute
¶
metadata: dict[str, Any] = field(default_factory=dict)
steps
class-attribute
instance-attribute
¶
steps: list[TrajectoryStep] = field(default_factory=list)
hint_state
class-attribute
instance-attribute
¶
hint_state: dict[str, Any] = field(default_factory=dict)
steering_inputs
class-attribute
instance-attribute
¶
steering_inputs: list[str] = field(default_factory=list)
background_results
class-attribute
instance-attribute
¶
background_results: dict[str, BackgroundTaskResult] = field(default_factory=dict)
to_history
¶
to_history() -> list[dict[str, Any]]
Return the dumped history of all steps.
Returns:
| Type | Description |
|---|---|
list[dict[str, Any]]
|
list[dict[str, Any]]: One serialised dict (via :meth: |
list[dict[str, Any]]
|
per step, in execution order. |
add_background_result
¶
add_background_result(result: BackgroundTaskResult) -> None
Record a completed background task result on this trajectory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
result
|
BackgroundTaskResult
|
The background task result to store, keyed by its |
required |
mark_background_consumed
¶
mark_background_consumed(task_id: str) -> bool
Mark a stored background task result as consumed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task_id
|
str
|
Identifier of the background task result to mark. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
True if a result with |
bool
|
False if no such result exists. |
clear_consumed_background
¶
clear_consumed_background() -> int
Remove all background task results already marked as consumed.
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
The number of background task results that were removed. |
get_unconsumed_background
¶
get_unconsumed_background() -> dict[str, BackgroundTaskResult]
Return background task results that have not yet been consumed.
Returns:
| Type | Description |
|---|---|
dict[str, BackgroundTaskResult]
|
dict[str, BackgroundTaskResult]: Mapping of task id to result, for |
dict[str, BackgroundTaskResult]
|
entries whose |
serialise
¶
serialise() -> dict[str, Any]
Serialise the full trajectory to a plain, JSON-safe dict.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
dict[str, Any]: A dict capturing |
dict[str, Any]
|
|
dict[str, Any]
|
|
dict[str, Any]
|
meth: |
dict[str, Any]
|
|
from_serialised
classmethod
¶
from_serialised(payload: Mapping[str, Any]) -> Trajectory
Reconstruct a :class:Trajectory from a previously serialised payload.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payload
|
Mapping[str, Any]
|
Mapping produced by :meth: |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Trajectory |
Trajectory
|
A new trajectory populated from |
Trajectory
|
restored steps, summary, background results, and auxiliary state. |
compress
¶
compress() -> TrajectorySummary
Compute and store a compact :class:TrajectorySummary for this trajectory.
Derives goals, gathered facts, pending retry actions, and a truncated digest of the last observation from the recorded steps, using simple rule-based heuristics (no LLM call).
Returns:
| Name | Type | Description |
|---|---|---|
TrajectorySummary |
TrajectorySummary
|
The computed summary, which is also assigned to |
TrajectorySummary
|
|
TrajectoryStep
dataclass
¶
TrajectoryStep(action: PlannerAction, observation: Any | None = None, llm_observation: Any | None = None, error: str | None = None, failure: Mapping[str, Any] | None = None, streams: Mapping[str, Sequence[Mapping[str, Any]]] | None = None)
A single recorded step (action + observation) within a :class:Trajectory.
Attributes:
| Name | Type | Description |
|---|---|---|
action |
PlannerAction
|
The planner action that was executed for this step. |
observation |
Any | None
|
Raw observation/result returned by executing |
llm_observation |
Any | None
|
Optional pre-serialised observation tailored for LLM
consumption, used in place of |
error |
str | None
|
Optional error message if the step failed. |
failure |
Mapping[str, Any] | None
|
Optional mapping with structured failure details. |
streams |
Mapping[str, Sequence[Mapping[str, Any]]] | None
|
Optional mapping of stream id to the sequence of chunks emitted on that stream during this step. |
streams
class-attribute
instance-attribute
¶
streams: Mapping[str, Sequence[Mapping[str, Any]]] | None = None
dump
¶
dump() -> dict[str, Any]
Serialise this step to a plain, JSON-safe dict.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
dict[str, Any]: A dict with |
dict[str, Any]
|
|
dict[str, Any]
|
and |
serialise_for_llm
¶
serialise_for_llm() -> Any
Return the observation representation to send to the LLM.
Prefers llm_observation when set, otherwise falls back to the serialised
observation.
Returns:
| Name | Type | Description |
|---|---|---|
Any |
Any
|
The LLM-facing observation value for this step. |
TrajectorySummary
¶
Bases: BaseModel
Compact, LLM-facing summary of a trajectory's progress.
Produced by :meth:Trajectory.compress (or restored via model_validate) to
give callers/LLMs a bounded, JSON-serialisable snapshot of what a run has
accomplished so far.
Attributes:
| Name | Type | Description |
|---|---|---|
goals |
list[str]
|
List of goal statements the trajectory is working toward. |
facts |
dict[str, Any]
|
Arbitrary key/value facts gathered while executing the trajectory. |
pending |
list[str]
|
List of pending follow-up actions (e.g., retries) still outstanding. |
last_output_digest |
str | None
|
Truncated string digest of the most recent observation, if any. |
note |
str | None
|
Optional free-form annotation describing how the summary was produced. |
compact
¶
compact() -> dict[str, Any]
Return a compact JSON-serialisable dict view of this summary.
Omits the note field entirely when it is falsy, keeping the payload
minimal for contexts (e.g. LLM prompts) where every token counts.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
dict[str, Any]: A dict with |
dict[str, Any]
|
|