1 VOXRole
jfabian edited this page 2026-08-13 11:15:47 -03:00

VOXRole

File: vox/roles/base.py

A role is a modular behavioral unit attached to an agent. It handles events and exposes commands. Every role is a Python file in agent_dir/roles/ containing a VOXRole subclass (one role class per module). Roles declare which capabilities they need; the framework discovers this statically via AST analysis of REQUIRES and mounts those capabilities.

Command vs Event

@command("run_analysis", description="...")
async def run_analysis(self, ...):
    ...

self.on("inbound_message")(self._receive_message)
self.on("on_boot")(self._greet)
Type Registered via Shows in get_commands() Dispatched by
Command @command() decorator Yes NLU classifier or slash command
Event self.on(name) No agent.emit(name, ...)

Contract

Member Required? Purpose
REQUIRES No set[str] — capability IDs this role needs (default set())
PREFERRED_MODEL No str | None — preferred LLM model for this role
@command() No Decorator for public command handlers
self.on() No Register event handlers
self.agent Inherited Weakref to the owning agent (raises AgentHostDeadError if gone)
handle_event(event, **kwargs) Inherited Dispatch an event to a registered handler
get_commands() Inherited {command_name: CommandInfo}

CommandInfo

@dataclass
class CommandInfo:
    handler: Callable
    description: str = ""
    params: dict[str, str] = field(default_factory=dict)
    sample_prompts: list | None = None

Registered by the @command(name, description, **metadata) decorator. The NLU classifier renders descriptions and sample prompts inline to guide intent detection.

Capability dependencies

Roles declare capability needs via REQUIRES; the ASTAgentAnalyzer scans role files statically (top-level REQUIRES = {...} sets) so capabilities are mounted before roles run. Roles whose required capability is unavailable or whose SENSITIVE_PARAMS cannot be satisfied from the vault are disabled at boot, and the agent is flagged DEGRADED.

Chat role and outbound messaging

The chat role (a conventional chat.py in agent role dirs) acts as the NLU router and message sink: it registers inbound_message, on_boot, and send_message events. Other roles typically emit send_message events rather than touching channel adapters directly; outbound delivery goes through the mounted comm.gateway capability:

await self.agent.capabilities["comm.gateway"].send_text(
    "telegram", recipient_id, text  # channel, recipient, text
)

The recipient is a per-call argument — the channel adapters hold no hardcoded user ID. Roles never access channels directly — I/O stays capability-gated.