685 lines
24 KiB
Python
685 lines
24 KiB
Python
import uuid
|
|
from collections.abc import Iterable
|
|
|
|
import zmq
|
|
from pydantic import ValidationError
|
|
from slugify import slugify
|
|
from zmq.asyncio import Context
|
|
|
|
from control_backend.agents import BaseAgent
|
|
from control_backend.core.config import settings
|
|
from control_backend.schemas.program import (
|
|
Action,
|
|
BasicBelief,
|
|
BasicNorm,
|
|
Belief,
|
|
ConditionalNorm,
|
|
GestureAction,
|
|
Goal,
|
|
InferredBelief,
|
|
KeywordBelief,
|
|
LLMAction,
|
|
LogicalOperator,
|
|
Phase,
|
|
Plan,
|
|
Program,
|
|
ProgramElement,
|
|
SemanticBelief,
|
|
SpeechAction,
|
|
Trigger,
|
|
)
|
|
|
|
test_program = Program(
|
|
phases=[
|
|
Phase(
|
|
norms=[
|
|
BasicNorm(norm="Talk like a pirate", id=uuid.uuid4()),
|
|
ConditionalNorm(
|
|
condition=InferredBelief(
|
|
left=KeywordBelief(keyword="Arr", id=uuid.uuid4()),
|
|
right=SemanticBelief(
|
|
description="testing", name="semantic belief", id=uuid.uuid4()
|
|
),
|
|
operator=LogicalOperator.OR,
|
|
name="Talking to a pirate",
|
|
id=uuid.uuid4(),
|
|
),
|
|
norm="Use nautical terms",
|
|
id=uuid.uuid4(),
|
|
),
|
|
ConditionalNorm(
|
|
condition=SemanticBelief(
|
|
description="We are talking to a child",
|
|
name="talking to child",
|
|
id=uuid.uuid4(),
|
|
),
|
|
norm="Do not use cuss words",
|
|
id=uuid.uuid4(),
|
|
),
|
|
],
|
|
triggers=[
|
|
Trigger(
|
|
condition=InferredBelief(
|
|
left=KeywordBelief(keyword="key", id=uuid.uuid4()),
|
|
right=InferredBelief(
|
|
left=KeywordBelief(keyword="key2", id=uuid.uuid4()),
|
|
right=SemanticBelief(
|
|
description="Decode this", name="semantic belief 2", id=uuid.uuid4()
|
|
),
|
|
operator=LogicalOperator.OR,
|
|
name="test trigger inferred inner",
|
|
id=uuid.uuid4(),
|
|
),
|
|
operator=LogicalOperator.OR,
|
|
name="test trigger inferred outer",
|
|
id=uuid.uuid4(),
|
|
),
|
|
plan=Plan(
|
|
steps=[
|
|
SpeechAction(text="Testing trigger", id=uuid.uuid4()),
|
|
Goal(
|
|
name="Testing trigger",
|
|
plan=Plan(
|
|
steps=[LLMAction(goal="Do something", id=uuid.uuid4())],
|
|
id=uuid.uuid4(),
|
|
),
|
|
id=uuid.uuid4(),
|
|
),
|
|
],
|
|
id=uuid.uuid4(),
|
|
),
|
|
id=uuid.uuid4(),
|
|
)
|
|
],
|
|
goals=[
|
|
Goal(
|
|
name="Determine user age",
|
|
plan=Plan(
|
|
steps=[LLMAction(goal="Determine the age of the user.", id=uuid.uuid4())],
|
|
id=uuid.uuid4(),
|
|
),
|
|
id=uuid.uuid4(),
|
|
),
|
|
Goal(
|
|
name="Find the user's name",
|
|
plan=Plan(
|
|
steps=[
|
|
Goal(
|
|
name="Greet the user",
|
|
plan=Plan(
|
|
steps=[LLMAction(goal="Greet the user.", id=uuid.uuid4())],
|
|
id=uuid.uuid4(),
|
|
),
|
|
can_fail=False,
|
|
id=uuid.uuid4(),
|
|
),
|
|
Goal(
|
|
name="Ask for name",
|
|
plan=Plan(
|
|
steps=[
|
|
LLMAction(goal="Obtain the user's name.", id=uuid.uuid4())
|
|
],
|
|
id=uuid.uuid4(),
|
|
),
|
|
id=uuid.uuid4(),
|
|
),
|
|
],
|
|
id=uuid.uuid4(),
|
|
),
|
|
id=uuid.uuid4(),
|
|
),
|
|
Goal(
|
|
name="Tell a joke",
|
|
plan=Plan(
|
|
steps=[LLMAction(goal="Tell a joke.", id=uuid.uuid4())], id=uuid.uuid4()
|
|
),
|
|
id=uuid.uuid4(),
|
|
),
|
|
],
|
|
id=uuid.uuid4(),
|
|
),
|
|
Phase(
|
|
id=uuid.uuid4(),
|
|
norms=[
|
|
BasicNorm(norm="Use very gentle speech.", id=uuid.uuid4()),
|
|
ConditionalNorm(
|
|
condition=SemanticBelief(
|
|
description="We are talking to a child",
|
|
name="talking to child",
|
|
id=uuid.uuid4(),
|
|
),
|
|
norm="Do not use cuss words",
|
|
id=uuid.uuid4(),
|
|
),
|
|
],
|
|
triggers=[
|
|
Trigger(
|
|
condition=InferredBelief(
|
|
left=KeywordBelief(keyword="help", id=uuid.uuid4()),
|
|
right=SemanticBelief(
|
|
description="User is stuck", name="stuck", id=uuid.uuid4()
|
|
),
|
|
operator=LogicalOperator.OR,
|
|
name="help_or_stuck",
|
|
id=uuid.uuid4(),
|
|
),
|
|
plan=Plan(
|
|
steps=[
|
|
Goal(
|
|
name="Unblock user",
|
|
plan=Plan(
|
|
steps=[
|
|
LLMAction(
|
|
goal="Provide a step-by-step path to "
|
|
"resolve the user's issue.",
|
|
id=uuid.uuid4(),
|
|
)
|
|
],
|
|
id=uuid.uuid4(),
|
|
),
|
|
id=uuid.uuid4(),
|
|
),
|
|
],
|
|
id=uuid.uuid4(),
|
|
),
|
|
id=uuid.uuid4(),
|
|
),
|
|
],
|
|
goals=[
|
|
Goal(
|
|
name="Clarify intent",
|
|
plan=Plan(
|
|
steps=[
|
|
LLMAction(
|
|
goal="Ask 1-2 targeted questions to clarify the "
|
|
"user's intent, then proceed.",
|
|
id=uuid.uuid4(),
|
|
)
|
|
],
|
|
id=uuid.uuid4(),
|
|
),
|
|
id=uuid.uuid4(),
|
|
),
|
|
Goal(
|
|
name="Provide solution",
|
|
plan=Plan(
|
|
steps=[
|
|
LLMAction(
|
|
goal="Deliver a solution to complete the user's goal.",
|
|
id=uuid.uuid4(),
|
|
)
|
|
],
|
|
id=uuid.uuid4(),
|
|
),
|
|
id=uuid.uuid4(),
|
|
),
|
|
Goal(
|
|
name="Summarize next steps",
|
|
plan=Plan(
|
|
steps=[
|
|
LLMAction(
|
|
goal="Summarize what the user should do next.", id=uuid.uuid4()
|
|
)
|
|
],
|
|
id=uuid.uuid4(),
|
|
),
|
|
id=uuid.uuid4(),
|
|
),
|
|
],
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
def do_things():
|
|
print(AgentSpeakGenerator().generate(test_program))
|
|
|
|
|
|
class AgentSpeakGenerator:
|
|
"""
|
|
Converts Pydantic representation of behavior programs into AgentSpeak(L) code string.
|
|
"""
|
|
|
|
arrow_prefix = f"{' ' * 2}<-{' ' * 2}"
|
|
colon_prefix = f"{' ' * 2}:{' ' * 3}"
|
|
indent_prefix = " " * 6
|
|
|
|
def generate(self, program: Program) -> str:
|
|
lines = []
|
|
lines.append("")
|
|
|
|
lines += self._generate_initial_beliefs(program)
|
|
|
|
lines += self._generate_basic_flow(program)
|
|
|
|
lines += self._generate_phase_transitions(program)
|
|
|
|
lines += self._generate_norms(program)
|
|
|
|
lines += self._generate_belief_inference(program)
|
|
|
|
lines += self._generate_goals(program)
|
|
|
|
lines += self._generate_triggers(program)
|
|
|
|
return "\n".join(lines)
|
|
|
|
def _generate_initial_beliefs(self, program: Program) -> Iterable[str]:
|
|
yield "// --- Initial beliefs and agent startup ---"
|
|
|
|
yield "phase(start)."
|
|
|
|
yield ""
|
|
|
|
yield "+started"
|
|
yield f"{self.colon_prefix}phase(start)"
|
|
yield f"{self.arrow_prefix}phase({program.phases[0].id if program.phases else 'end'})."
|
|
|
|
yield from ["", ""]
|
|
|
|
def _generate_basic_flow(self, program: Program) -> Iterable[str]:
|
|
yield "// --- Basic flow ---"
|
|
|
|
for phase in program.phases:
|
|
yield from self._generate_basic_flow_per_phase(phase)
|
|
|
|
yield from ["", ""]
|
|
|
|
def _generate_basic_flow_per_phase(self, phase: Phase) -> Iterable[str]:
|
|
yield "+user_said(Message)"
|
|
yield f"{self.colon_prefix}phase({phase.id})"
|
|
|
|
goals = phase.goals
|
|
if goals:
|
|
yield f"{self.arrow_prefix}{self._slugify(goals[0], include_prefix=True)}"
|
|
for goal in goals[1:]:
|
|
yield f"{self.indent_prefix}{self._slugify(goal, include_prefix=True)}"
|
|
|
|
yield f"{self.indent_prefix if goals else self.arrow_prefix}!transition_phase."
|
|
|
|
def _generate_phase_transitions(self, program: Program) -> Iterable[str]:
|
|
yield "// --- Phase transitions ---"
|
|
|
|
if len(program.phases) == 0:
|
|
yield from ["", ""]
|
|
return
|
|
|
|
# TODO: remove outdated things
|
|
|
|
for i in range(-1, len(program.phases)):
|
|
predecessor = program.phases[i] if i >= 0 else None
|
|
successor = program.phases[i + 1] if i < len(program.phases) - 1 else None
|
|
yield from self._generate_phase_transition(predecessor, successor)
|
|
|
|
yield from self._generate_phase_transition(None, None) # to avoid failing plan
|
|
|
|
yield from ["", ""]
|
|
|
|
def _generate_phase_transition(
|
|
self, phase: Phase | None = None, next_phase: Phase | None = None
|
|
) -> Iterable[str]:
|
|
yield "+!transition_phase"
|
|
|
|
if phase is None and next_phase is None: # base case true to avoid failing plan
|
|
yield f"{self.arrow_prefix}true."
|
|
return
|
|
|
|
yield f"{self.colon_prefix}phase({phase.id if phase else 'start'})"
|
|
yield f"{self.arrow_prefix}-+phase({next_phase.id if next_phase else 'end'})."
|
|
|
|
def _generate_norms(self, program: Program) -> Iterable[str]:
|
|
yield "// --- Norms ---"
|
|
|
|
for phase in program.phases:
|
|
for norm in phase.norms:
|
|
if type(norm) is BasicNorm:
|
|
yield f"{self._slugify(norm)} :- phase({phase.id})."
|
|
if type(norm) is ConditionalNorm:
|
|
yield (
|
|
f"{self._slugify(norm)} :- phase({phase.id}) & "
|
|
f"{self._slugify(norm.condition)}."
|
|
)
|
|
|
|
yield from ["", ""]
|
|
|
|
def _generate_belief_inference(self, program: Program) -> Iterable[str]:
|
|
yield "// --- Belief inference rules ---"
|
|
|
|
for phase in program.phases:
|
|
for norm in phase.norms:
|
|
if not isinstance(norm, ConditionalNorm):
|
|
continue
|
|
|
|
yield from self._belief_inference_recursive(norm.condition)
|
|
|
|
for trigger in phase.triggers:
|
|
yield from self._belief_inference_recursive(trigger.condition)
|
|
|
|
yield from ["", ""]
|
|
|
|
def _belief_inference_recursive(self, belief: Belief) -> Iterable[str]:
|
|
if type(belief) is KeywordBelief:
|
|
yield (
|
|
f"{self._slugify(belief)} :- user_said(Message) & "
|
|
f'.substring(Message, "{belief.keyword}", Pos) & Pos >= 0.'
|
|
)
|
|
if type(belief) is InferredBelief:
|
|
yield (
|
|
f"{self._slugify(belief)} :- {self._slugify(belief.left)} "
|
|
f"{'&' if belief.operator == LogicalOperator.AND else '|'} "
|
|
f"{self._slugify(belief.right)}."
|
|
)
|
|
|
|
yield from self._belief_inference_recursive(belief.left)
|
|
yield from self._belief_inference_recursive(belief.right)
|
|
|
|
def _generate_goals(self, program: Program) -> Iterable[str]:
|
|
yield "// --- Goals ---"
|
|
|
|
for phase in program.phases:
|
|
previous_goal: Goal | None = None
|
|
for goal in phase.goals:
|
|
yield from self._generate_goal_plan_recursive(goal, phase, previous_goal)
|
|
previous_goal = goal
|
|
|
|
yield from ["", ""]
|
|
|
|
def _generate_goal_plan_recursive(
|
|
self, goal: Goal, phase: Phase, previous_goal: Goal | None = None
|
|
) -> Iterable[str]:
|
|
yield f"+{self._slugify(goal, include_prefix=True)}"
|
|
|
|
# Context
|
|
yield f"{self.colon_prefix}phase({phase.id}) &"
|
|
yield f"{self.indent_prefix}not responded_this_turn &"
|
|
yield f"{self.indent_prefix}not achieved_{self._slugify(goal)} &"
|
|
if previous_goal:
|
|
yield f"{self.indent_prefix}achieved_{self._slugify(previous_goal)}"
|
|
else:
|
|
yield f"{self.indent_prefix}true"
|
|
|
|
extra_goals_to_generate = []
|
|
|
|
steps = goal.plan.steps
|
|
|
|
if len(steps) == 0:
|
|
yield f"{self.arrow_prefix}true."
|
|
return
|
|
|
|
first_step = steps[0]
|
|
yield (
|
|
f"{self.arrow_prefix}{self._slugify(first_step, include_prefix=True)}"
|
|
f"{'.' if len(steps) == 1 and goal.can_fail else ';'}"
|
|
)
|
|
if isinstance(first_step, Goal):
|
|
extra_goals_to_generate.append(first_step)
|
|
|
|
for step in steps[1:-1]:
|
|
yield f"{self.indent_prefix}{self._slugify(step, include_prefix=True)};"
|
|
if isinstance(step, Goal):
|
|
extra_goals_to_generate.append(step)
|
|
|
|
if len(steps) > 1:
|
|
last_step = steps[-1]
|
|
yield (
|
|
f"{self.indent_prefix}{self._slugify(last_step, include_prefix=True)}"
|
|
f"{'.' if goal.can_fail else ';'}"
|
|
)
|
|
if isinstance(last_step, Goal):
|
|
extra_goals_to_generate.append(last_step)
|
|
|
|
if not goal.can_fail:
|
|
yield f"{self.indent_prefix}+achieved_{self._slugify(goal)}."
|
|
|
|
yield f"+{self._slugify(goal, include_prefix=True)}"
|
|
yield f"{self.arrow_prefix}true."
|
|
|
|
yield ""
|
|
|
|
extra_previous_goal: Goal | None = None
|
|
for extra_goal in extra_goals_to_generate:
|
|
yield from self._generate_goal_plan_recursive(extra_goal, phase, extra_previous_goal)
|
|
extra_previous_goal = extra_goal
|
|
|
|
def _generate_triggers(self, program: Program) -> Iterable[str]:
|
|
yield "// --- Triggers ---"
|
|
|
|
for phase in program.phases:
|
|
for trigger in phase.triggers:
|
|
yield from self._generate_trigger_plan(trigger, phase)
|
|
|
|
yield from ["", ""]
|
|
|
|
def _generate_trigger_plan(self, trigger: Trigger, phase: Phase) -> Iterable[str]:
|
|
belief_name = self._slugify(trigger.condition)
|
|
|
|
yield f"+{belief_name}"
|
|
yield f"{self.colon_prefix}phase({phase.id})"
|
|
|
|
extra_goals_to_generate = []
|
|
|
|
steps = trigger.plan.steps
|
|
|
|
if len(steps) == 0:
|
|
yield f"{self.arrow_prefix}true."
|
|
return
|
|
|
|
first_step = steps[0]
|
|
yield (
|
|
f"{self.arrow_prefix}{self._slugify(first_step, include_prefix=True)}"
|
|
f"{'.' if len(steps) == 1 else ';'}"
|
|
)
|
|
if isinstance(first_step, Goal):
|
|
extra_goals_to_generate.append(first_step)
|
|
|
|
for step in steps[1:-1]:
|
|
yield f"{self.indent_prefix}{self._slugify(step, include_prefix=True)};"
|
|
if isinstance(step, Goal):
|
|
extra_goals_to_generate.append(step)
|
|
|
|
if len(steps) > 1:
|
|
last_step = steps[-1]
|
|
yield f"{self.indent_prefix}{self._slugify(last_step, include_prefix=True)}."
|
|
if isinstance(last_step, Goal):
|
|
extra_goals_to_generate.append(last_step)
|
|
|
|
yield ""
|
|
|
|
extra_previous_goal: Goal | None = None
|
|
for extra_goal in extra_goals_to_generate:
|
|
yield from self._generate_trigger_plan_recursive(extra_goal, phase, extra_previous_goal)
|
|
extra_previous_goal = extra_goal
|
|
|
|
def _generate_trigger_plan_recursive(
|
|
self, goal: Goal, phase: Phase, previous_goal: Goal | None = None
|
|
) -> Iterable[str]:
|
|
yield f"+{self._slugify(goal, include_prefix=True)}"
|
|
|
|
extra_goals_to_generate = []
|
|
|
|
steps = goal.plan.steps
|
|
|
|
if len(steps) == 0:
|
|
yield f"{self.arrow_prefix}true."
|
|
return
|
|
|
|
first_step = steps[0]
|
|
yield (
|
|
f"{self.arrow_prefix}{self._slugify(first_step, include_prefix=True)}"
|
|
f"{'.' if len(steps) == 1 and goal.can_fail else ';'}"
|
|
)
|
|
if isinstance(first_step, Goal):
|
|
extra_goals_to_generate.append(first_step)
|
|
|
|
for step in steps[1:-1]:
|
|
yield f"{self.indent_prefix}{self._slugify(step, include_prefix=True)};"
|
|
if isinstance(step, Goal):
|
|
extra_goals_to_generate.append(step)
|
|
|
|
if len(steps) > 1:
|
|
last_step = steps[-1]
|
|
yield (
|
|
f"{self.indent_prefix}{self._slugify(last_step, include_prefix=True)}"
|
|
f"{'.' if goal.can_fail else ';'}"
|
|
)
|
|
if isinstance(last_step, Goal):
|
|
extra_goals_to_generate.append(last_step)
|
|
|
|
if not goal.can_fail:
|
|
yield f"{self.indent_prefix}+achieved_{self._slugify(goal)}."
|
|
|
|
yield f"+{self._slugify(goal, include_prefix=True)}"
|
|
yield f"{self.arrow_prefix}true."
|
|
|
|
yield ""
|
|
|
|
extra_previous_goal: Goal | None = None
|
|
for extra_goal in extra_goals_to_generate:
|
|
yield from self._generate_goal_plan_recursive(extra_goal, phase, extra_previous_goal)
|
|
extra_previous_goal = extra_goal
|
|
|
|
def _slugify(self, element: ProgramElement, include_prefix: bool = False) -> str:
|
|
def base_slugify_call(text: str):
|
|
return slugify(text, separator="_", stopwords=["a", "the"])
|
|
|
|
if type(element) is KeywordBelief:
|
|
return f'keyword_said("{element.keyword}")'
|
|
|
|
if type(element) is SemanticBelief:
|
|
name = element.name
|
|
return f"semantic_{base_slugify_call(name if name else element.description)}"
|
|
|
|
if isinstance(element, BasicNorm):
|
|
return f'norm("{element.norm}")'
|
|
|
|
if isinstance(element, Goal):
|
|
return f"{'!' if include_prefix else ''}{base_slugify_call(element.name)}"
|
|
|
|
if isinstance(element, SpeechAction):
|
|
return f'.say("{element.text}")'
|
|
|
|
if isinstance(element, GestureAction):
|
|
return f'.gesture("{element.gesture}")'
|
|
|
|
if isinstance(element, LLMAction):
|
|
return f'!generate_response_with_goal("{element.goal}")'
|
|
|
|
if isinstance(element, Action.__value__):
|
|
raise NotImplementedError(
|
|
"Have not implemented an ASL string representation for this action."
|
|
)
|
|
|
|
if element.name == "":
|
|
raise ValueError("Name must be initialized for this type of ProgramElement.")
|
|
|
|
return base_slugify_call(element.name)
|
|
|
|
def _extract_basic_beliefs_from_program(self, program: Program) -> list[BasicBelief]:
|
|
beliefs = []
|
|
|
|
for phase in program.phases:
|
|
for norm in phase.norms:
|
|
if isinstance(norm, ConditionalNorm):
|
|
beliefs += self._extract_basic_beliefs_from_belief(norm.condition)
|
|
|
|
for trigger in phase.triggers:
|
|
beliefs += self._extract_basic_beliefs_from_belief(trigger.condition)
|
|
|
|
return beliefs
|
|
|
|
def _extract_basic_beliefs_from_belief(self, belief: Belief) -> list[BasicBelief]:
|
|
if isinstance(belief, InferredBelief):
|
|
return self._extract_basic_beliefs_from_belief(
|
|
belief.left
|
|
) + self._extract_basic_beliefs_from_belief(belief.right)
|
|
return [belief]
|
|
|
|
|
|
class BDIProgramManager(BaseAgent):
|
|
"""
|
|
BDI Program Manager Agent.
|
|
|
|
This agent is responsible for receiving high-level programs (sequences of instructions/goals)
|
|
from the external HTTP API (via ZMQ) and translating them into core beliefs (norms and goals)
|
|
for the BDI Core Agent. In the future, it will be responsible for determining when goals are
|
|
met, and passing on new norms and goals accordingly.
|
|
|
|
:ivar sub_socket: The ZMQ SUB socket used to receive program updates.
|
|
"""
|
|
|
|
def __init__(self, **kwargs):
|
|
super().__init__(**kwargs)
|
|
self.sub_socket = None
|
|
|
|
# async def _send_to_bdi(self, program: Program):
|
|
# """
|
|
# Convert a received program into BDI beliefs and send them to the BDI Core Agent.
|
|
#
|
|
# Currently, it takes the **first phase** of the program and extracts:
|
|
# - **Norms**: Constraints or rules the agent must follow.
|
|
# - **Goals**: Objectives the agent must achieve.
|
|
#
|
|
# These are sent as a ``BeliefMessage`` with ``replace=True``, meaning they will
|
|
# overwrite any existing norms/goals of the same name in the BDI agent.
|
|
#
|
|
# :param program: The program object received from the API.
|
|
# """
|
|
# first_phase = program.phases[0]
|
|
# norms_belief = Belief(
|
|
# name="norms",
|
|
# arguments=[norm.norm for norm in first_phase.norms],
|
|
# replace=True,
|
|
# )
|
|
# goals_belief = Belief(
|
|
# name="goals",
|
|
# arguments=[goal.description for goal in first_phase.goals],
|
|
# replace=True,
|
|
# )
|
|
# program_beliefs = BeliefMessage(beliefs=[norms_belief, goals_belief])
|
|
#
|
|
# message = InternalMessage(
|
|
# to=settings.agent_settings.bdi_core_name,
|
|
# sender=self.name,
|
|
# body=program_beliefs.model_dump_json(),
|
|
# thread="beliefs",
|
|
# )
|
|
# await self.send(message)
|
|
# self.logger.debug("Sent new norms and goals to the BDI agent.")
|
|
|
|
async def _receive_programs(self):
|
|
"""
|
|
Continuous loop that receives program updates from the HTTP endpoint.
|
|
|
|
It listens to the ``program`` topic on the internal ZMQ SUB socket.
|
|
When a program is received, it is validated and forwarded to BDI via :meth:`_send_to_bdi`.
|
|
"""
|
|
while True:
|
|
topic, body = await self.sub_socket.recv_multipart()
|
|
|
|
try:
|
|
program = Program.model_validate_json(body)
|
|
except ValidationError:
|
|
self.logger.exception("Received an invalid program.")
|
|
continue
|
|
|
|
await self._send_to_bdi(program)
|
|
|
|
async def setup(self):
|
|
"""
|
|
Initialize the agent.
|
|
|
|
Connects the internal ZMQ SUB socket and subscribes to the 'program' topic.
|
|
Starts the background behavior to receive programs.
|
|
"""
|
|
context = Context.instance()
|
|
|
|
self.sub_socket = context.socket(zmq.SUB)
|
|
self.sub_socket.connect(settings.zmq_settings.internal_sub_address)
|
|
self.sub_socket.subscribe("program")
|
|
|
|
self.add_behavior(self._receive_programs())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
do_things()
|