Action Policies¶
BaseActionPolicy¶
polymathera.colony.agents.patterns.actions.policies.BaseActionPolicy(agent, action_map=None, action_providers=[], io=None)
¶
Bases: ActionPolicy
Base class for action policies with dataflow and nested policy support.
Provides: - Automatic action dispatcher creation - Integration with agent capabilities - Nested policy execution with scope inheritance - Dispatch with automatic Ref resolution
Subclasses implement plan_step to produce the next action or child policy.
The base execute_iteration handles:
- Delegating to active child policies
- Executing actions returned by plan_step
- Setting up child policies returned by plan_step
TODO: For example, we can orchestrate iterative reasoning to follow the pattern
(PLAN → ACT → REFLECT → CRITIQUE → ADAPT) by adding AgentCapabilities that
implement each step as an action executor, and then implementing plan_step
to select the next action based on the current state. This can be enforced by:
- Restricting available actions in the action dispatcher depending on the
last completed step, or
- Using an ActionPolicy subclass that implements the iterative pattern by
overriding execute_iteration to enforce the sequence of steps, and
only calling plan_step to get parameters for each step, or
- Prompting the LLM planner with this workflow.
Example
class MyPolicy(BaseActionPolicy):
io = ActionPolicyIO(
inputs={"query": str},
outputs={"result": dict}
)
async def plan_step(self, state) -> Action | None:
# Return None when policy is complete
if state.custom.get("done"):
return None
# Return an Action to execute
return Action(
action_id="analyze_001",
agent_id=self.agent.agent_id,
action_type="analyze",
parameters={"query": state.scope.get("query")}
)
# Or return an ActionPolicy for nested execution
# return ChildPolicy(self.agent)
Source code in src/polymathera/colony/agents/patterns/actions/policies.py
execute_iteration(state)
async
¶
Execute one iteration of this policy.
This method is @hookable so memory capabilities can observe iterations.
Calls plan_step to get next action, then dispatches it.
For hierarchical composition (nested policies), spawn child agents
instead of nesting policies. Use self.agent.spawn_child_agents().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
ActionPolicyExecutionState
|
Execution state for this policy (all mutable state lives here) |
required |
Returns:
| Type | Description |
|---|---|
ActionPolicyIterationResult
|
Iteration result |
Source code in src/polymathera/colony/agents/patterns/actions/policies.py
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 | |
plan_step(state)
async
¶
Produce the next action to execute.
Override this method to implement policy-specific planning logic.
For hierarchical composition, spawn child agents instead of nesting
policies. Use self.agent.spawn_child_agents() with appropriate
action policies for child agents.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
ActionPolicyExecutionState
|
Execution state for this policy |
required |
Returns:
| Type | Description |
|---|---|
Action | None
|
|
Action | None
|
|
Example
async def plan_step(self, state) -> Action | None:
phase = state.custom.get("phase", "act")
if phase == "act":
action = self._get_next_action(state)
if action is None:
state.custom["policy_complete"] = True
return None
state.custom["phase"] = "process"
return action
elif phase == "process":
# Do some processing without dispatching an action
self._process_results(state)
state.custom["phase"] = "act"
return None # Skip iteration, continue policy
Source code in src/polymathera/colony/agents/patterns/actions/policies.py
action_executor decorator¶
polymathera.colony.agents.patterns.actions.dispatcher.action_executor(action_key=None, *, input_schema=None, output_schema=None, reads=None, writes=None, exclude_from_planning=False, planning_summary=None, tags=None, interruptible=False)
¶
Decorator to turn any method into an action executor.
Automatically infers input/output schemas from type hints if not provided.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
action_key
|
str | ActionType | None
|
Key identifying the action type. If None, uses method name. |
None
|
input_schema
|
type[BaseModel] | None
|
Optional Pydantic model for input validation. If None, inferred from method signature. |
None
|
output_schema
|
type[BaseModel] | None
|
Optional Pydantic model for output validation. If None, inferred from return type hint. |
None
|
reads
|
list[str] | None
|
List of scope variable names this action reads. |
None
|
writes
|
list[str] | None
|
List of scope variable names this action writes. |
None
|
exclude_from_planning
|
bool
|
If True, this action is not exposed to the LLM planner. Use this for actions that are only meant to be invoked programmatically in response to events (e.g., game moves in response to spawned agent events). Default is False. |
False
|
tags
|
frozenset[str] | None
|
Optional domain/modality tags for this action (e.g., frozenset({"memory", "expensive"})). Used for future per-action tag-based filtering and grouping. |
None
|
interruptible
|
bool
|
If True, the dispatcher wraps this action's execution in
an asyncio.Task so it can be cancelled mid-flight via
|
False
|
Example
@action_executor()
async def route_query(
self,
query: str,
max_results: int = 10
) -> list[str]:
'''Route query to find relevant pages.'''
...
@action_executor(writes=["analysis_result"])
async def analyze_pages(
self,
page_ids: list[str],
goal: str
) -> AnalysisResult:
'''Analyze pages for the given goal.'''
...
# Event-driven action not visible to planner
@action_executor(exclude_from_planning=True)
async def submit_move(self, game_id: str, move: dict) -> None:
'''Submit move in response to game event.'''
...
Source code in src/polymathera/colony/agents/patterns/actions/dispatcher.py
649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 | |