Action Policies¶
BaseActionPolicy¶
polymathera.colony.agents.patterns.actions.policies.BaseActionPolicy(agent, action_map=None, action_providers=[], io=None, consciousness_streams=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
¶
Run one iteration, then per-iteration stream maintenance.
The maintenance (flush new entries to each compaction-enabled
stream's durable log + run the token-budget safety-net) runs in
a finally so it happens however the iteration exits — the
last iteration's entries are durably persisted even on an early
return or exception. _maintain_streams never raises and is a
no-op for legacy (non-compacted) streams.
Source code in src/polymathera/colony/agents/patterns/actions/policies.py
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, emits_lifecycle=True)
¶
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
|
emits_lifecycle
|
bool
|
If True (default), the codegen action policy
publishes |
True
|
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
750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 | |