Agent System¶
The agent system defines how autonomous computational entities are created, managed, and coordinated. Every agent follows the same core lifecycle but can be specialized through capabilities and action policies.
Guiding Principle¶
Agent control flow and all decisions should be driven by a reasoning LLM given sufficient context, not hardcoded logic. But the actions available to the LLM planner are as important as the context it reasons over. The framework provides a rich ecosystem of capabilities that expose different actions and cognitive processes to the LLM, enabling emergent behavior from the combinatorial explosion of possible action interleavings.
The framework provides structure (lifecycle, capabilities, blackboard access), but the LLM decides what to do next. Meta-choices available to the LLM include delegating to another agent, meta-reasoning about its own strategy, initiating multi-agent deliberation, and building new tools.
Agent Base Class¶
polymathera.colony.agents.base.Agent is the base class for all agents. It provides:
- Lifecycle management: State transitions through
AgentState - Blackboard access: Read/write shared state via
EnhancedBlackboard - VCM access: Read virtual context pages
- Inference submission: Submit requests to vLLM replicas
- Capability management: Add, remove, and query
AgentCapabilityinstances - Hook registry: Per-agent
AgentHookRegistryfor AOP interception - Session tracking: Every
run()call creates anAgentRuntracked in the session
Agent Lifecycle States¶
Defined in polymathera.colony.agents.models.AgentState:
INITIALIZED --> RUNNING --> IDLE --> STOPPED
| ^
v |
WAITING ----------------
|
v
SUSPENDED --> RUNNING (resumed)
|
v
FAILED
| State | Description |
|---|---|
INITIALIZED |
Agent created, not yet started |
RUNNING |
Actively executing an action policy iteration |
WAITING |
Blocked on input, tool result, or child agent |
LOADED |
VCM-bound agent with pages resident in cache |
UNLOADED |
VCM-bound agent with pages evicted from cache |
IDLE |
Between tasks, not actively executing |
SUSPENDED |
Resources freed, state serialized, can be resumed |
STOPPED |
Gracefully terminated |
FAILED |
Terminated due to error |
Agent Types¶
VCM-Bound Agents¶
Loaded and unloaded together with their assigned VCM pages. When their pages are evicted from cache, these agents are suspended. When pages are reloaded, agents resume from their serialized state.
Unbound Agents¶
Dynamically select which VCM pages to load. They are not tied to specific pages and can request page loads as needed during execution.
Service Agents¶
Always-running agents that provide services to other agents (e.g., a MemoryManagementAgent that consolidates memories across the system).
Supervisor Agents¶
Independent of page state. They monitor and coordinate other agents, make delegation decisions, and handle escalation.
AgentCapability¶
polymathera.colony.agents.base.AgentCapability is the extension point for agent functionality. Each capability encapsulates a specific aspect or protocol:
class AgentCapability(ABC):
"""Base class for capabilities.
Operates in four modes:
1. Local: runs within the owning agent
2. Remote: parent communicates with child via shared scope
3. Shared scope: multiple agents share a namespace
4. Detached: standalone without agent context
"""
def __init__(
self,
agent: Agent | None = None,
scope_id: str | None = None,
*,
blackboard: EnhancedBlackboard | None = None,
capability_key: str | None = None,
): ...
The four modes in practice:
# 1. Local mode — capability runs within its owning agent
memory = MemoryCapability(agent=self) # scope_id defaults to agent.agent_id
# 2. Remote mode — parent monitors a child agent's progress
child_cap = ResultCapability(agent=parent, scope_id=child_agent_id)
await child_cap.stream_events_to_queue(self.get_event_queue())
result = await asyncio.wait_for(child_cap.get_result_future(), timeout=30.0)
# 3. Shared scope — all game participants see each other's events
game_cap = NegotiationGameProtocol(agent=self, scope_id=game_id)
# 4. Detached mode — external system interacts with agents via blackboard
external_cap = MyCapability(agent=None, scope_id=target_agent_id)
Capabilities provide:
- Action executors: Methods decorated with
@action_executorthat theActionPolicycan invoke (conscious cognitive processes) - Hookables: Methods marked
@hookablethat other components can intercept - Hooks (method call interceptors): Hooks the capability registers on other components (via
register_hook()) - Event streams: Capabilities can publish events to their scoped blackboard, which other agents can subscribe to (e.g., for game protocols or parent-child communication)
- Event handlers: Capabilities can subscribe to events from other capabilities or agents, enabling reactive behavior and emergent coordination patterns
- Services or background processes: Subconscious cognitive processes (consolidation, rehearsal)
class PageGraphCapability(AgentCapability):
"""Example capability with action executors and hooks."""
@action_executor()
async def traverse(
self, start_pages: list[str], strategy: str = "bfs", max_depth: int = 5
) -> dict[str, Any]:
"""Auto-discovered by ActionPolicy — the LLM planner can invoke this."""
graph = await self._get_page_graph()
...
@action_executor(exclude_from_planning=True)
async def update_edge(self, source: str, target: str, weight: float) -> None:
"""exclude_from_planning=True: only invoked programmatically, not by the LLM."""
...
Capabilities as AOP Aspects
Each AgentCapability is an "aspect" in the aspect-oriented programming sense. The ActionPolicy plays the role of the "aspect weaver," deciding which capabilities to activate and in what order. Emergent behavior arises from the combinatorial explosion of possible capability interleavings -- the framework does not model all paths explicitly.
Merge with docs/design-insights/capabilities-as-aspects.md and reference here.
LLM-Decidable Actions
The LLM should be able to reason about when/how to use each AgentCapability action.
Add subconscious processes to planning prompt
Subconscious processes (e.g., memory consolidation) are not directly invoked by the LLM, but they affect the agent's state and capabilities. The LLM should be aware of these processes and their effects when making decisions. Add explanation of how to include subconscious processes in the planning prompt and how they interact with conscious actions.
Scope-Based Communication¶
Capabilities use scope_id for flexible communication patterns:
agent.agent_id(default): Agent-local scopechild_agent_id: Parent-to-child communicationgame_idortask_id: Shared scope for group coordination
The publish() method writes records to the capability's scoped blackboard. If the scope is VCM-mapped, writes are automatically discoverable by other agents via the VCM.
# publish() replaces manual blackboard key construction:
# key = f"{self.scope_id}:analysis:result:{result.result_id}"
# await bb.write(key=key, value=result.model_dump(), created_by=...)
# With:
await self.publish(result, tags={"analysis"})
# Key is resolved automatically via BlackboardPublishable protocol on the record.
ActionPolicy¶
polymathera.colony.agents.base.ActionPolicy is the abstract base for decision-making. It receives execution state and produces iteration results:
class ActionPolicy(ABC):
async def execute_iteration(
self, state: ActionPolicyExecutionState
) -> ActionPolicyIterationResult:
...
The policy manages which capabilities are active via use_agent_capabilities() and disable_agent_capabilities(). See Action Policies for the full planning architecture.
The agent's main execution loop calls run_step() repeatedly. Each step invokes the action policy, which gathers context from capabilities, asks the LLM what to do next, and dispatches the chosen action:
class Agent:
@hookable
async def run_step(self) -> None:
"""Execute one iteration of the agent's reasoning loop.
@hookable: capabilities can register BEFORE/AFTER/AROUND hooks
to prepare context, post-process results, or wrap execution.
Uses repeated run_step() instead of a single long-running run()
to facilitate suspension and state management across replicas.
"""
if self.state not in (AgentState.RUNNING, AgentState.IDLE):
return
result = await self.action_policy.execute_iteration(self._build_state())
await self._apply_iteration_result(result)
def add_capability(self, capability: AgentCapability, *,
include_actions: list[str] | None = None,
exclude_actions: list[str] | None = None) -> None:
"""Add a capability. Must still call action_policy.use_agent_capabilities()
to include it in planning."""
def get_capability(self, name: str) -> AgentCapability | None:
"""Retrieve a capability by name."""
ResourceExhausted Handling¶
When a replica has insufficient resources for a new agent, the system follows an ordered strategy:
- Hard page affinity: Only place on replicas with required pages
- Soft affinity: Try replicas with preferred pages, fall back to others
- Retry later: Queue the agent for later placement
- Suspend existing agents: Free resources by suspending lower-priority agents
The ResourceExhausted exception (in polymathera.colony.agents.base) triggers this cascade.
Agent Actions Taxonomy¶
Actions are typed via polymathera.colony.agents.models.ActionType:
| Category | Actions |
|---|---|
| Planning | PLAN_CREATE, PLAN_REVISE, PLAN_BACKTRACK |
| Reasoning | ANALYZE, HYPOTHESIS_TEST, DECISION_MAKE |
| Tools | TOOL_DISCOVER, TOOL_USE, TOOL_FIX, TOOL_CREATE |
| Context | CONTEXT_FETCH, CONTEXT_COMPACT, CONTEXT_SUMMARIZE |
| Communication | MESSAGE_SEND, MESSAGE_RECEIVE, NEGOTIATE |
| Memory | MEMORY_SEARCH, MEMORY_READ, MEMORY_WRITE |
| Orchestration | AGENT_SPAWN, AGENT_TERMINATE, AGENT_MONITOR |
| Output | OUTPUT_WRITE, OUTPUT_FORMAT, REPORT_GENERATE |
Long-running actions should be idempotent, pausable, resumable, checkpointable, and cancellable. Complex actions are best implemented as separate agents managed by the AgentSystemDeployment.
Blueprints¶
This section is incomplete and needs expansion
Add explanation of how blueprints work for serializable agent specifications, validation, and instantiation with dependency injection.
Agent configuration uses the blueprint pattern for serializable, deployable specifications:
AgentBlueprint: Full agent specification (capabilities, policy, resources)AgentCapabilityBlueprint: Capability class + constructor kwargsActionPolicyBlueprint: Policy class + constructor kwargs (created viaActionPolicy.bind())
Blueprints are validated for serializability at creation time and instantiated at deployment time with injected dependencies (agent reference, blackboard, etc.).