chore: linter fix

This commit is contained in:
bryan
2026-03-03 20:15:42 -08:00
parent 9ce753055c
commit 06a9adb051
8 changed files with 247 additions and 83 deletions
@@ -251,9 +251,7 @@ class EmailReplyAgent:
errors.append(f"Terminal node '{t}' not found")
for ep_id, nid in self.entry_points.items():
if nid not in node_ids:
errors.append(
f"Entry point '{ep_id}' references unknown node '{nid}'"
)
errors.append(f"Entry point '{ep_id}' references unknown node '{nid}'")
return {"valid": len(errors) == 0, "errors": errors, "warnings": warnings}
@@ -36,7 +36,9 @@ default_config = RuntimeConfig()
class AgentMetadata:
name: str = "Email Reply Agent"
version: str = "1.0.0"
description: str = "Filter unreplied emails, confirm recipients, send personalized replies."
description: str = (
"Filter unreplied emails, confirm recipients, send personalized replies."
)
intro_message: str = "Tell me which emails you want to reply to (e.g., 'emails from @company.com in the last week')."
@@ -6,9 +6,12 @@ from .agent import default_agent, MeetingScheduler
def setup_logging(verbose=False, debug=False):
if debug: level, fmt = logging.DEBUG, "%(asctime)s %(name)s: %(message)s"
elif verbose: level, fmt = logging.INFO, "%(message)s"
else: level, fmt = logging.WARNING, "%(levelname)s: %(message)s"
if debug:
level, fmt = logging.DEBUG, "%(asctime)s %(name)s: %(message)s"
elif verbose:
level, fmt = logging.INFO, "%(message)s"
else:
level, fmt = logging.WARNING, "%(levelname)s: %(message)s"
logging.basicConfig(level=level, format=fmt, stream=sys.stderr)
@@ -21,18 +24,28 @@ def cli():
@cli.command()
@click.option("--attendee", "-a", required=True, help="Attendee email address")
@click.option("--duration", "-d", type=int, required=True, help="Meeting duration in minutes")
@click.option(
"--duration", "-d", type=int, required=True, help="Meeting duration in minutes"
)
@click.option("--title", "-t", required=True, help="Meeting title")
@click.option("--verbose", "-v", is_flag=True)
def run(attendee, duration, title, verbose):
"""Execute the scheduler."""
setup_logging(verbose=verbose)
result = asyncio.run(default_agent.run({
"attendee_email": attendee,
"meeting_duration_minutes": str(duration),
"meeting_title": title,
}))
click.echo(json.dumps({"success": result.success, "output": result.output}, indent=2, default=str))
result = asyncio.run(
default_agent.run(
{
"attendee_email": attendee,
"meeting_duration_minutes": str(duration),
"meeting_title": title,
}
)
)
click.echo(
json.dumps(
{"success": result.success, "output": result.output}, indent=2, default=str
)
)
sys.exit(0 if result.success else 1)
@@ -52,18 +65,37 @@ def tui():
storage = Path.home() / ".hive" / "agents" / "meeting_scheduler"
storage.mkdir(parents=True, exist_ok=True)
mcp_cfg = Path(__file__).parent / "mcp_servers.json"
if mcp_cfg.exists(): agent._tool_registry.load_mcp_config(mcp_cfg)
llm = LiteLLMProvider(model=agent.config.model, api_key=agent.config.api_key, api_base=agent.config.api_base)
if mcp_cfg.exists():
agent._tool_registry.load_mcp_config(mcp_cfg)
llm = LiteLLMProvider(
model=agent.config.model,
api_key=agent.config.api_key,
api_base=agent.config.api_base,
)
runtime = create_agent_runtime(
graph=agent._build_graph(), goal=agent.goal, storage_path=storage,
entry_points=[EntryPointSpec(id="start", name="Start", entry_node="intake", trigger_type="manual", isolation_level="isolated")],
llm=llm, tools=list(agent._tool_registry.get_tools().values()), tool_executor=agent._tool_registry.get_executor())
graph=agent._build_graph(),
goal=agent.goal,
storage_path=storage,
entry_points=[
EntryPointSpec(
id="start",
name="Start",
entry_node="intake",
trigger_type="manual",
isolation_level="isolated",
)
],
llm=llm,
tools=list(agent._tool_registry.get_tools().values()),
tool_executor=agent._tool_registry.get_executor(),
)
await runtime.start()
try:
app = AdenTUI(runtime)
await app.run_async()
finally:
await runtime.stop()
asyncio.run(run_tui())
@@ -71,18 +103,24 @@ def tui():
def info():
"""Show agent info."""
data = default_agent.info()
click.echo(f"Agent: {data['name']}\nVersion: {data['version']}\nDescription: {data['description']}")
click.echo(f"Nodes: {', '.join(data['nodes'])}\nClient-facing: {', '.join(data['client_facing_nodes'])}")
click.echo(
f"Agent: {data['name']}\nVersion: {data['version']}\nDescription: {data['description']}"
)
click.echo(
f"Nodes: {', '.join(data['nodes'])}\nClient-facing: {', '.join(data['client_facing_nodes'])}"
)
@cli.command()
def validate():
"""Validate agent structure."""
v = default_agent.validate()
if v["valid"]: click.echo("Agent is valid")
if v["valid"]:
click.echo("Agent is valid")
else:
click.echo("Errors:")
for e in v["errors"]: click.echo(f" {e}")
for e in v["errors"]:
click.echo(f" {e}")
sys.exit(0 if v["valid"] else 1)
+126 -33
View File
@@ -20,15 +20,54 @@ goal = Goal(
name="Schedule Meetings",
description="Check calendar availability, find optimal meeting times, record meetings, and send reminders.",
success_criteria=[
SuccessCriterion(id="sc-1", description="Meeting time found within requested duration", metric="calendar_availability", target="success", weight=0.35),
SuccessCriterion(id="sc-2", description="Meeting recorded in spreadsheet accurately", metric="data_persistence", target="recorded", weight=0.30),
SuccessCriterion(id="sc-3", description="Attendee email reminder sent", metric="communication", target="sent", weight=0.25),
SuccessCriterion(id="sc-4", description="User confirms meeting details", metric="user_acknowledgment", target="confirmed", weight=0.10),
SuccessCriterion(
id="sc-1",
description="Meeting time found within requested duration",
metric="calendar_availability",
target="success",
weight=0.35,
),
SuccessCriterion(
id="sc-2",
description="Meeting recorded in spreadsheet accurately",
metric="data_persistence",
target="recorded",
weight=0.30,
),
SuccessCriterion(
id="sc-3",
description="Attendee email reminder sent",
metric="communication",
target="sent",
weight=0.25,
),
SuccessCriterion(
id="sc-4",
description="User confirms meeting details",
metric="user_acknowledgment",
target="confirmed",
weight=0.10,
),
],
constraints=[
Constraint(id="c-1", description="Must use Google Calendar API for availability check", constraint_type="hard", category="functional"),
Constraint(id="c-2", description="Meeting duration must match requested time", constraint_type="hard", category="accuracy"),
Constraint(id="c-3", description="Spreadsheet record must include date, time, attendee, title", constraint_type="hard", category="quality"),
Constraint(
id="c-1",
description="Must use Google Calendar API for availability check",
constraint_type="hard",
category="functional",
),
Constraint(
id="c-2",
description="Meeting duration must match requested time",
constraint_type="hard",
category="accuracy",
),
Constraint(
id="c-3",
description="Spreadsheet record must include date, time, attendee, title",
constraint_type="hard",
category="quality",
),
],
)
@@ -37,14 +76,29 @@ nodes = [intake_node, schedule_node, confirm_node]
# Edge definitions
edges = [
EdgeSpec(id="intake-to-schedule", source="intake", target="schedule",
condition=EdgeCondition.ON_SUCCESS, priority=1),
EdgeSpec(id="schedule-to-confirm", source="schedule", target="confirm",
condition=EdgeCondition.ON_SUCCESS, priority=1),
EdgeSpec(
id="intake-to-schedule",
source="intake",
target="schedule",
condition=EdgeCondition.ON_SUCCESS,
priority=1,
),
EdgeSpec(
id="schedule-to-confirm",
source="schedule",
target="confirm",
condition=EdgeCondition.ON_SUCCESS,
priority=1,
),
# Loop back for another booking
EdgeSpec(id="confirm-to-intake", source="confirm", target="intake",
condition=EdgeCondition.CONDITIONAL,
condition_expr="str(next_action).lower() == 'another'", priority=1),
EdgeSpec(
id="confirm-to-intake",
source="confirm",
target="intake",
condition=EdgeCondition.CONDITIONAL,
condition_expr="str(next_action).lower() == 'another'",
priority=1,
),
]
# Graph configuration
@@ -56,7 +110,11 @@ terminal_nodes = [] # Forever-alive
# Module-level vars read by AgentRunner.load()
conversation_mode = "continuous"
identity_prompt = "You are a helpful meeting scheduler assistant that manages calendar availability and sends confirmations."
loop_config = {"max_iterations": 100, "max_tool_calls_per_turn": 20, "max_history_tokens": 32000}
loop_config = {
"max_iterations": 100,
"max_tool_calls_per_turn": 20,
"max_history_tokens": 32000,
}
class MeetingScheduler:
@@ -99,17 +157,36 @@ class MeetingScheduler:
mcp_config = Path(__file__).parent / "mcp_servers.json"
if mcp_config.exists():
self._tool_registry.load_mcp_config(mcp_config)
llm = LiteLLMProvider(model=self.config.model, api_key=self.config.api_key, api_base=self.config.api_base)
llm = LiteLLMProvider(
model=self.config.model,
api_key=self.config.api_key,
api_base=self.config.api_base,
)
tools = list(self._tool_registry.get_tools().values())
tool_executor = self._tool_registry.get_executor()
self._graph = self._build_graph()
self._agent_runtime = create_agent_runtime(
graph=self._graph, goal=self.goal, storage_path=self._storage_path,
entry_points=[EntryPointSpec(id="default", name="Default", entry_node=self.entry_node,
trigger_type="manual", isolation_level="shared")],
llm=llm, tools=tools, tool_executor=tool_executor,
checkpoint_config=CheckpointConfig(enabled=True, checkpoint_on_node_complete=True,
checkpoint_max_age_days=7, async_checkpoint=True),
graph=self._graph,
goal=self.goal,
storage_path=self._storage_path,
entry_points=[
EntryPointSpec(
id="default",
name="Default",
entry_node=self.entry_node,
trigger_type="manual",
isolation_level="shared",
)
],
llm=llm,
tools=tools,
tool_executor=tool_executor,
checkpoint_config=CheckpointConfig(
enabled=True,
checkpoint_on_node_complete=True,
checkpoint_max_age_days=7,
async_checkpoint=True,
),
)
async def start(self):
@@ -123,26 +200,37 @@ class MeetingScheduler:
await self._agent_runtime.stop()
self._agent_runtime = None
async def trigger_and_wait(self, entry_point="default", input_data=None, timeout=None, session_state=None):
async def trigger_and_wait(
self, entry_point="default", input_data=None, timeout=None, session_state=None
):
if self._agent_runtime is None:
raise RuntimeError("Agent not started. Call start() first.")
return await self._agent_runtime.trigger_and_wait(
entry_point_id=entry_point, input_data=input_data or {}, session_state=session_state)
entry_point_id=entry_point,
input_data=input_data or {},
session_state=session_state,
)
async def run(self, context, session_state=None):
await self.start()
try:
result = await self.trigger_and_wait("default", context, session_state=session_state)
result = await self.trigger_and_wait(
"default", context, session_state=session_state
)
return result or ExecutionResult(success=False, error="Execution timeout")
finally:
await self.stop()
def info(self):
return {
"name": metadata.name, "version": metadata.version, "description": metadata.description,
"name": metadata.name,
"version": metadata.version,
"description": metadata.description,
"goal": {"name": self.goal.name, "description": self.goal.description},
"nodes": [n.id for n in self.nodes], "edges": [e.id for e in self.edges],
"entry_node": self.entry_node, "entry_points": self.entry_points,
"nodes": [n.id for n in self.nodes],
"edges": [e.id for e in self.edges],
"entry_node": self.entry_node,
"entry_points": self.entry_points,
"terminal_nodes": self.terminal_nodes,
"client_facing_nodes": [n.id for n in self.nodes if n.client_facing],
}
@@ -151,13 +239,18 @@ class MeetingScheduler:
errors, warnings = [], []
node_ids = {n.id for n in self.nodes}
for e in self.edges:
if e.source not in node_ids: errors.append(f"Edge {e.id}: source '{e.source}' not found")
if e.target not in node_ids: errors.append(f"Edge {e.id}: target '{e.target}' not found")
if self.entry_node not in node_ids: errors.append(f"Entry node '{self.entry_node}' not found")
if e.source not in node_ids:
errors.append(f"Edge {e.id}: source '{e.source}' not found")
if e.target not in node_ids:
errors.append(f"Edge {e.id}: target '{e.target}' not found")
if self.entry_node not in node_ids:
errors.append(f"Entry node '{self.entry_node}' not found")
for t in self.terminal_nodes:
if t not in node_ids: errors.append(f"Terminal node '{t}' not found")
if t not in node_ids:
errors.append(f"Terminal node '{t}' not found")
for ep_id, nid in self.entry_points.items():
if nid not in node_ids: errors.append(f"Entry point '{ep_id}' references unknown node '{nid}'")
if nid not in node_ids:
errors.append(f"Entry point '{ep_id}' references unknown node '{nid}'")
return {"valid": len(errors) == 0, "errors": errors, "warnings": warnings}
@@ -12,7 +12,11 @@ intake_node = NodeSpec(
max_node_visits=0,
input_keys=["attendee_email", "meeting_duration_minutes"],
output_keys=["attendee_email", "meeting_duration_minutes", "meeting_title"],
nullable_output_keys=["attendee_email", "meeting_duration_minutes", "meeting_title"],
nullable_output_keys=[
"attendee_email",
"meeting_duration_minutes",
"meeting_title",
],
success_criteria="User has provided attendee email, meeting duration, and title.",
system_prompt="""\
You are a meeting scheduler assistant.
@@ -37,7 +41,13 @@ schedule_node = NodeSpec(
node_type="event_loop",
max_node_visits=0,
input_keys=["attendee_email", "meeting_duration_minutes", "meeting_title"],
output_keys=["meeting_time", "booking_confirmed", "spreadsheet_recorded", "email_sent", "meet_link"],
output_keys=[
"meeting_time",
"booking_confirmed",
"spreadsheet_recorded",
"email_sent",
"meet_link",
],
nullable_output_keys=[],
success_criteria="Meeting time found, Google Meet created, Google Sheet 'Meeting Scheduler' updated with date/time/attendee/title/meet_link, and confirmation email sent.",
system_prompt="""\
@@ -18,6 +18,7 @@ AGENT_PATH = str(Path(__file__).resolve().parents[1])
def agent_module():
"""Import the agent package for structural validation."""
import importlib
return importlib.import_module(Path(AGENT_PATH).name)
@@ -26,6 +27,7 @@ def runner_loaded():
"""Load the agent through AgentRunner (structural only, no LLM needed)."""
from framework.runner.runner import AgentRunner
from framework.credentials.models import CredentialError
try:
return AgentRunner.load(AGENT_PATH)
except CredentialError:
@@ -2,8 +2,16 @@
import pytest
from meeting_scheduler import (
default_agent, goal, nodes, edges, entry_node, entry_points,
terminal_nodes, conversation_mode, identity_prompt, loop_config
default_agent,
goal,
nodes,
edges,
entry_node,
entry_points,
terminal_nodes,
conversation_mode,
identity_prompt,
loop_config,
)
@@ -30,7 +38,12 @@ class TestNodeStructure:
assert nodes[0].client_facing is True
def test_schedule_has_required_tools(self):
required = {"calendar_check_availability", "calendar_create_event", "google_sheets_append_values", "send_email"}
required = {
"calendar_check_availability",
"calendar_create_event",
"google_sheets_append_values",
"send_email",
}
actual = set(nodes[1].tools)
assert required.issubset(actual)