Skip to content

Multi-agent runtime: Concierge/Property/Scheduling + OpenOrca live-agent contract - #80

Merged
mahimairaja merged 20 commits into
mainfrom
feat/multi-agent-runtime
Jul 10, 2026
Merged

Multi-agent runtime: Concierge/Property/Scheduling + OpenOrca live-agent contract#80
mahimairaja merged 20 commits into
mainfrom
feat/multi-agent-runtime

Conversation

@mahimairaja

@mahimairaja mahimairaja commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Brings the multi-agent system onto the Super Realty base.

Agent: replaces the single RealtyAgent with an openrtc AgentPool hosting ConciergeAgent, which hands off to PropertyAgent (search/show) and SchedulingAgent (availability/booking) within one session. Shared per-call CallContext, per-specialist instruction builders.

Backend: /openorca live-agent contract (snapshot, runtime-info, events SSE, graph-token) backed by an in-memory live-agent registry with TTL sweep and SSE fan-out, so an openorca-ui console can visualize live calls. Signed graph token for query-string auth.

Clean merge off #74; main's turnkey/landing/rename/deploy-removal are untouched. Full unit coverage for every new agent and endpoint.

Summary by CodeRabbit

  • New Features

    • Introduced a concierge-led call experience with personalized greetings, returning-buyer recall, lead capture, and privacy controls.
    • Added specialized property search and showing-scheduling assistance, including listing previews, availability checks, and booking confirmations.
    • Added live call status tracking with agent handoff visibility and real-time dashboard updates.
    • Added secured runtime snapshots, event streaming, and agent state reporting.
  • Bug Fixes

    • Improved graceful handling of unavailable listings, backend errors, invalid booking slots, and incomplete caller information.
  • Tests

    • Added coverage for concierge workflows, property search, scheduling, live status updates, streaming events, and secure runtime access.

Adds GET /openorca/events: emits an initial snapshot.replace then fans out
registry events over text/event-stream with a 15s keep-alive, so openorca-ui
renders live agent state without polling. Integration test covers the stream.
Brings the ConciergeAgent -> PropertyAgent/SchedulingAgent multi-agent group
(openrtc AgentPool) and the backend /openorca live-agent contract onto the
current Super Realty base. Clean merge: the branch was cut at #74, so main's
later turnkey, landing, rename, and deploy-removal work only touched files the
branch did not, and the branch only added agent + backend code main did not.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 10, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
superrealty e1a419f Jul 10 2026, 04:37 PM

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Changes

Voice specialist agent flow

Layer / File(s) Summary
Shared call context and state reporting
agent/src/agents/base_agent.py, agent/src/agents/call_context.py, agent/src/services/api_client.py, agent/tests/unit/test_call_context.py, agent/tests/unit/test_api_client.py
Adds shared call lifecycle state, UI events, agent-state reporting, handoff tracking, and idempotent teardown.
Specialist prompts and agent tools
agent/src/agents/{concierge_agent,property_agent,scheduling_agent}.py, agent/src/prompts/instructions.py, agent/tests/unit/*
Adds concierge, property, and scheduling agents with role prompts, lead capture, listing search, booking, and shared-context handoffs.
Agent pool registration
agent/src/agent.py, agent/tests/unit/test_agent_boot.py
Registers ConciergeAgent as the session entry agent and updates boot expectations.

OpenOrca runtime surface

Layer / File(s) Summary
Graph token authorization
backend/src/core/graph_token.py, backend/tests/unit/test_graph_token.py
Adds tenant-scoped HMAC graph tokens and verification tests.
Live registry and snapshot mapping
backend/src/runtime/live_agents.py, backend/src/runtime/openorca_mapper.py, backend/tests/unit/test_{live_agents,openorca_mapper}.py
Tracks live calls and handoffs, expires stale entries, manages subscribers, and maps state to snapshots.
OpenOrca endpoints and streaming
backend/src/api/endpoints/openorca.py, backend/src/api/routes.py, backend/tests/{unit,integration}/*openorca*
Adds agent-state intake, snapshot/runtime routes, SSE streaming, router wiring, and endpoint integration coverage.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant ConciergeAgent
  participant PropertyAgent
  participant SchedulingAgent
  participant Backend
  participant OpenOrcaSSE
  Caller->>ConciergeAgent: Start call
  ConciergeAgent->>Backend: Resolve context and report state
  ConciergeAgent->>PropertyAgent: Handoff for listing search
  PropertyAgent->>SchedulingAgent: Handoff for showing booking
  SchedulingAgent->>Backend: Book offered slot
  Backend->>OpenOrcaSSE: Publish snapshot replacement
  OpenOrcaSSE-->>Caller: Updated agent state
Loading

Poem

I’m a rabbit hopping through the flow,
Concierge greets, then specialists go.
Listings bloom and calendars chime,
Tokens guard the graph in time.
SSE carries each bright state—
A tidy trail from call to gate.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.61% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: a multi-agent runtime plus the OpenOrca live-agent contract.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/multi-agent-runtime

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…alls

report_state already guards on self.room before firing _report, and close()
must not persist a call log for a room that was never resolved. Pass the
narrowed room into _report and guard post_call_log, resolving two mypy
str|None arg-type errors. mypy clean, 101 agent tests pass.
… skips them

test_agent_state_sse streams the unbounded /openorca/events endpoint. Without
the integration marker it ran in 'pytest -m "not integration"' and hung the
backend unit gate. It lives in tests/integration/ and is meant for the live
suite; add the module marker.
@mahimairaja
mahimairaja merged commit 213f872 into main Jul 10, 2026
7 checks passed
@mahimairaja
mahimairaja deleted the feat/multi-agent-runtime branch July 10, 2026 16:38

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (11)
backend/src/core/graph_token.py (1)

25-35: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add docstrings to mint_graph_token and verify_graph_token.

Both are public functions lacking docstrings. As per path instructions, each public function should have a clear docstring explaining its purpose, parameters, and return values.

♻️ Proposed docstrings
 def mint_graph_token(tenant_id: str) -> str:
+    """Mint a short-lived HS256 JWT carrying the tenant id and openorca scope.
+
+    Args:
+        tenant_id: The tenant this token authorizes reads for.
+
+    Returns:
+        A signed JWT string valid for GRAPH_TOKEN_TTL_SECONDS seconds.
+    """
     secret = config.JWT_SECRET_KEY.get_secret_value()
 def verify_graph_token(token: str) -> str:
+    """Decode and validate a graph token, returning the tenant id.
+
+    Args:
+        token: The JWT string from the ?token= query parameter.
+
+    Returns:
+        The tenant id claim if the token is valid and scoped to openorca.
+
+    Raises:
+        HTTPException: 401 if the token is expired, tampered, or wrongly scoped.
+    """
     secret = config.JWT_SECRET_KEY.get_secret_value()

Also applies to: 38-48

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/core/graph_token.py` around lines 25 - 35, Add clear docstrings
to the public functions mint_graph_token and verify_graph_token, describing each
function’s purpose, parameters, and return value (including verification failure
behavior where applicable).

Source: Path instructions

backend/src/api/endpoints/openorca.py (2)

35-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add docstrings to public functions and the AgentStateIn class.

AgentStateIn, agent_state, snapshot, runtime_info, resolve_intervention, and events lack docstrings. As per path instructions, each public function and class should have a clear docstring explaining its purpose, parameters, and return values.

Also applies to: 44-62, 71-73, 76-82, 85-88, 116-122

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/api/endpoints/openorca.py` around lines 35 - 41, Add clear
docstrings to the public class AgentStateIn and functions agent_state, snapshot,
runtime_info, resolve_intervention, and events, describing each purpose,
parameters, and return value; place each docstring directly inside its
corresponding definition without changing behavior.

Source: Path instructions


44-62: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace raw dict returns with Pydantic response models.

Five endpoints return dict instead of typed response models. As per path instructions, avoid raw dict in route signatures and use Pydantic models for response validation.

Simple response models for agent_state, graph_token, runtime_info, and resolve_intervention are straightforward. The snapshot endpoint may require a model matching the to_snapshot return structure from openorca_mapper.py.

♻️ Example response models for the simple endpoints
+class OkResponse(BaseModel):
+    ok: bool
+
+
+class GraphTokenResponse(BaseModel):
+    token: str
+
+
+class RuntimeInfoResponse(BaseModel):
+    runtime: str
+    language: str
+    supports: dict[str, bool]
+

 `@state_router.post`("/agent-state", status_code=status.HTTP_202_ACCEPTED)
-async def agent_state(payload: AgentStateIn, tenant_id: AgentTenant) -> dict:
+async def agent_state(payload: AgentStateIn, tenant_id: AgentTenant) -> OkResponse:

 `@router.get`("/graph-token")
-async def graph_token(tenant_id: CurrentTenant) -> dict:
+async def graph_token(tenant_id: CurrentTenant) -> GraphTokenResponse:

 `@router.get`("/runtime-info")
-async def runtime_info(tenant_id: OpenOrcaTenant) -> dict:
+async def runtime_info(tenant_id: OpenOrcaTenant) -> RuntimeInfoResponse:

 `@router.post`("/interventions/resolve")
-async def resolve_intervention(tenant_id: OpenOrcaTenant) -> dict:
+async def resolve_intervention(tenant_id: OpenOrcaTenant) -> OkResponse:

Also applies to: 65-68, 71-73, 76-82, 85-88

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/api/endpoints/openorca.py` around lines 44 - 62, Replace raw dict
return annotations on the affected endpoints—agent_state, graph_token,
runtime_info, resolve_intervention, and snapshot—with dedicated Pydantic
response models. Define models for each response shape, including a model
matching to_snapshot output, reference them in the route response_model and
return annotations, and preserve the existing payload fields and behavior.

Source: Path instructions

backend/src/runtime/live_agents.py (1)

20-105: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add docstrings to CallState, LiveAgentRegistry, and all public methods.

CallState, LiveAgentRegistry, and every public method (update, snapshot_calls, subscribe, unsubscribe, publish, reset) lack docstrings. As per path instructions, each public function and class must have a clear docstring explaining its purpose, parameters, and return values.

📝 Suggested docstring additions
 `@dataclass`
 class CallState:
+    """Per-call lifecycle state tracking the active specialist and handoff edges.
+
+    Attributes:
+        tenant_id: Owning tenant for this call.
+        room: LiveKit room identifier.
+        active: Currently active specialist agent name.
+        action: Human-readable description of the current action.
+        edges: Set of (from_agent, to_agent) handoff tuples recorded during the call.
+        started_at: Monotonic timestamp when the call was first registered.
+        updated_at: Monotonic timestamp of the most recent state update.
+    """
     tenant_id: str
     room: str
     active: str
     action: str
     edges: set[tuple[str, str]]
     started_at: float
     updated_at: float


 class LiveAgentRegistry:
+    """In-memory registry of live voice calls and per-tenant SSE subscriber queues.
+
+    Tracks which specialist agent currently holds each call, records handoff
+    edges, sweeps stale calls after TTL expiry, and fans out published payloads
+    to all subscribed SSE queues for a given tenant.
+    """
     def __init__(
         self,
         now: Callable[[], float] = time.monotonic,
         ttl: float = DEFAULT_TTL_SECONDS,
     ) -> None:
+        """Initialize the registry with a clock function and TTL duration.
+
+        Args:
+            now: Callable returning the current monotonic time.
+            ttl: Seconds after which a call with no updates is swept.
+        """
         self._now = now
         self._ttl = ttl
         self._calls: dict[tuple[str, str], CallState] = {}
         self._queues: dict[str, set[asyncio.Queue]] = {}

     def update(
         self,
         tenant_id: str,
         room: str,
         active: str,
         action: str,
         from_agent: str | None = None,
     ) -> CallState:
+        """Create or update a call's active agent and action, optionally recording a handoff edge.
+
+        Args:
+            tenant_id: Owning tenant for the call.
+            room: LiveKit room identifier.
+            active: Specialist agent now holding the call.
+            action: Human-readable description of the current action.
+            from_agent: Previous agent to record a handoff edge from, if valid and different.
+
+        Returns:
+            The updated CallState for this call.
+        """
         now = self._now()
     def snapshot_calls(self, tenant_id: str) -> list[CallState]:
+        """Sweep stale calls and return all live calls for the given tenant.
+
+        Args:
+            tenant_id: Tenant whose calls to return.
+
+        Returns:
+            List of CallState objects for non-expired calls belonging to this tenant.
+        """
         self._sweep()
         return [s for (tid, _), s in self._calls.items() if tid == tenant_id]

     def subscribe(self, tenant_id: str) -> asyncio.Queue:
+        """Create and register a new asyncio queue for a tenant's SSE stream.
+
+        Args:
+            tenant_id: Tenant whose events the subscriber should receive.
+
+        Returns:
+            An unbounded asyncio.Queue that will receive published payloads.
+        """
         q: asyncio.Queue = asyncio.Queue()
         self._queues.setdefault(tenant_id, set()).add(q)
         return q

     def unsubscribe(self, tenant_id: str, q: asyncio.Queue) -> None:
+        """Remove a subscriber's queue and clean up the tenant's queue set if empty.
+
+        Args:
+            tenant_id: Tenant the queue was subscribed to.
+            q: The queue to remove.
+        """
         subs = self._queues.get(tenant_id)
         if subs:
             subs.discard(q)
             if not subs:
                 del self._queues[tenant_id]

     async def publish(self, tenant_id: str, payload: object) -> None:
+        """Fan out a payload to all subscriber queues for a tenant.
+
+        Args:
+            tenant_id: Tenant whose subscribers should receive the payload.
+            payload: Arbitrary object to put on each subscriber's queue.
+        """
         for q in list(self._queues.get(tenant_id, ())):
             await q.put(payload)

     def reset(self) -> None:
+        """Clear all calls and subscriber queues (used in tests)."""
         self._calls.clear()
         self._queues.clear()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/runtime/live_agents.py` around lines 20 - 105, Add clear
docstrings to the CallState dataclass, LiveAgentRegistry class, and its public
methods update, snapshot_calls, subscribe, unsubscribe, publish, and reset.
Document each method’s purpose, parameters, and return value, including the
relevant state and payload types.

Source: Path instructions

backend/src/runtime/openorca_mapper.py (1)

49-85: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a docstring to to_snapshot.

to_snapshot is a public function but lacks a docstring. As per path instructions, each public function must have a clear docstring explaining its purpose, parameters, and return values.

📝 Suggested docstring
 def to_snapshot(calls: list[CallState], generated_at: str) -> dict:
+    """Convert a list of live call states into the OpenOrca snapshot dict.
+
+    Each call becomes one machine with three agent nodes (concierge, property,
+    scheduling). The active specialist is marked as active with its current
+    action; others are idle. Handoff edges become collaboratingWith links.
+
+    Args:
+        calls: Live CallState objects to render.
+        generated_at: ISO-8601 timestamp for the snapshot meta field.
+
+    Returns:
+        Dict with machines, agents, tasks, actionLog, interventions, swarms,
+        fleetHealth, and meta keys matching the openorca-ui contract.
+    """
     machines = [
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/runtime/openorca_mapper.py` around lines 49 - 85, Add a clear
docstring to the public to_snapshot function describing that it builds the
runtime snapshot, documenting the calls and generated_at parameters, and
specifying the returned snapshot dictionary.

Source: Path instructions

agent/src/prompts/instructions.py (2)

204-213: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add docstrings to the three exported instruction builders.

concierge_instructions, property_instructions, and scheduling_instructions lack docstrings. As per coding guidelines, each public function should have a clear docstring explaining its purpose, parameters, and return values.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/src/prompts/instructions.py` around lines 204 - 213, Add clear
docstrings to the public functions concierge_instructions,
property_instructions, and scheduling_instructions, describing each function’s
purpose, persona parameter, and returned instruction string.

Source: Path instructions


173-195: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

_persona_preamble duplicates realtor_instructions persona logic.

The persona field extraction and line-building logic (lines 177-195) is identical to realtor_instructions (lines 90-107). realtor_instructions could delegate to _persona_preamble to eliminate the copy.

♻️ Proposed refactor: `realtor_instructions` delegates to `_persona_preamble`
 def realtor_instructions(persona: dict[str, str | None] | None) -> str:
     """REALTOR_INSTRUCTIONS with a persona preamble when we know who the realtor is.

     The persona (name/agency/area/tagline/tone) is inferred from the realtor's own site during
     onboarding. When present, the assistant answers in their name and matches their voice; when
     absent (a file/CSV onboard, or nothing connected yet), it falls back to the generic prompt.
     """
-    if not persona:
-        return REALTOR_INSTRUCTIONS
-    name = _clean(persona.get("name"))
-    agency = _clean(persona.get("agency"))
-    area = _clean(persona.get("area"))
-    tagline = _clean(persona.get("tagline"))
-    tone = _clean(persona.get("tone"))
-    if not any((name, agency, area, tagline, tone)):
-        return REALTOR_INSTRUCTIONS
-    who = name or "a solo real estate agent"
-    at = f" at {agency}" if agency else ""
-    lines = [
-        f"You are the voice assistant for {who}{at}, and you answer in their name."
-    ]
-    if area:
-        lines.append(f"They serve {area}.")
-    if tagline:
-        lines.append(f'Their promise to clients is: "{tagline}".')
-    if tone:
-        lines.append(f"Match their voice: speak in a {tone} tone.")
-    return " ".join(lines) + "\n\n" + REALTOR_INSTRUCTIONS
+    preamble = _persona_preamble(persona)
+    if not preamble:
+        return REALTOR_INSTRUCTIONS
+    return preamble + "\n\n" + REALTOR_INSTRUCTIONS
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/src/prompts/instructions.py` around lines 173 - 195, Refactor
realtor_instructions to reuse _persona_preamble for persona extraction and
prompt construction instead of duplicating the same logic. Pass the available
persona data to _persona_preamble and incorporate its returned text while
preserving realtor_instructions’ existing output and behavior for missing or
empty persona values.
agent/src/agents/base_agent.py (1)

19-25: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add docstrings to RealtyBaseAgent and __init__.

The class and __init__ lack docstrings. As per coding guidelines, each public function and class should have a clear docstring explaining its purpose, parameters, and return values.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/src/agents/base_agent.py` around lines 19 - 25, Add clear docstrings to
the RealtyBaseAgent class and its __init__ method, describing the agent’s
purpose, the ctx and instructions parameters, and the initializer’s return
behavior.

Source: Path instructions

agent/src/agents/concierge_agent.py (3)

18-23: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add a class docstring to SchedulingAgent.

Per path instructions, each public class should have a clear docstring explaining its purpose.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/src/agents/concierge_agent.py` around lines 18 - 23, 添加
`SchedulingAgent` 类的清晰类级 docstring,简要说明其用途及职责,并遵循项目对公开类文档的约定。

Source: Path instructions


27-35: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a class docstring to ConciergeAgent.

Per path instructions, each public class should have a clear docstring explaining its purpose. The module docstring covers the role, but the class itself has none.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/src/agents/concierge_agent.py` around lines 27 - 35, Add a clear
class-level docstring immediately inside ConciergeAgent describing that it is
the agent responsible for greeting and assisting callers, while preserving its
existing ID, ACTION, and initialization behavior.

Source: Path instructions


69-74: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add a class docstring to PropertyAgent.

Per path instructions, each public class should have a clear docstring explaining its purpose.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/src/agents/concierge_agent.py` around lines 69 - 74, PropertyAgent
lacks the required class-level documentation. Add a clear docstring immediately
inside the PropertyAgent class declaration describing its purpose and role in
handling property-related conversations.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@agent/src/agents/call_context.py`:
- Around line 270-289: Ensure close() always releases the HTTP client even when
_log_usage_summary() fails: wrap the usage-summary call in try/except, handle or
log the exception, and continue into the existing API cleanup path so
self.api.aclose() executes.

In `@agent/src/agents/concierge_agent.py`:
- Around line 54-96: capture_lead currently treats API failures as success. In
the exception handler around self.ctx.api.capture_lead, log the failure and
immediately return a clear error message instructing the agent that saving
failed and it should offer a retry or collect the details manually; do not push
the lead event or execute the success/returning-buyer response when the API call
fails.

In `@agent/src/agents/property_agent.py`:
- Around line 69-74: Add clear docstrings to the public PropertyAgent class and
its __init__ method, describing the agent’s purpose, the ctx parameter, and the
constructor’s return value (None), while preserving the existing initialization
behavior.

In `@agent/src/agents/scheduling_agent.py`:
- Around line 18-23: Add a class docstring to SchedulingAgent describing its
scheduling purpose, and add an __init__ docstring documenting the ctx parameter
and that initialization returns None. Place both docstrings directly inside the
class and constructor definitions without changing behavior.

---

Nitpick comments:
In `@agent/src/agents/base_agent.py`:
- Around line 19-25: Add clear docstrings to the RealtyBaseAgent class and its
__init__ method, describing the agent’s purpose, the ctx and instructions
parameters, and the initializer’s return behavior.

In `@agent/src/agents/concierge_agent.py`:
- Around line 18-23: 添加 `SchedulingAgent` 类的清晰类级
docstring,简要说明其用途及职责,并遵循项目对公开类文档的约定。
- Around line 27-35: Add a clear class-level docstring immediately inside
ConciergeAgent describing that it is the agent responsible for greeting and
assisting callers, while preserving its existing ID, ACTION, and initialization
behavior.
- Around line 69-74: PropertyAgent lacks the required class-level documentation.
Add a clear docstring immediately inside the PropertyAgent class declaration
describing its purpose and role in handling property-related conversations.

In `@agent/src/prompts/instructions.py`:
- Around line 204-213: Add clear docstrings to the public functions
concierge_instructions, property_instructions, and scheduling_instructions,
describing each function’s purpose, persona parameter, and returned instruction
string.
- Around line 173-195: Refactor realtor_instructions to reuse _persona_preamble
for persona extraction and prompt construction instead of duplicating the same
logic. Pass the available persona data to _persona_preamble and incorporate its
returned text while preserving realtor_instructions’ existing output and
behavior for missing or empty persona values.

In `@backend/src/api/endpoints/openorca.py`:
- Around line 35-41: Add clear docstrings to the public class AgentStateIn and
functions agent_state, snapshot, runtime_info, resolve_intervention, and events,
describing each purpose, parameters, and return value; place each docstring
directly inside its corresponding definition without changing behavior.
- Around line 44-62: Replace raw dict return annotations on the affected
endpoints—agent_state, graph_token, runtime_info, resolve_intervention, and
snapshot—with dedicated Pydantic response models. Define models for each
response shape, including a model matching to_snapshot output, reference them in
the route response_model and return annotations, and preserve the existing
payload fields and behavior.

In `@backend/src/core/graph_token.py`:
- Around line 25-35: Add clear docstrings to the public functions
mint_graph_token and verify_graph_token, describing each function’s purpose,
parameters, and return value (including verification failure behavior where
applicable).

In `@backend/src/runtime/live_agents.py`:
- Around line 20-105: Add clear docstrings to the CallState dataclass,
LiveAgentRegistry class, and its public methods update, snapshot_calls,
subscribe, unsubscribe, publish, and reset. Document each method’s purpose,
parameters, and return value, including the relevant state and payload types.

In `@backend/src/runtime/openorca_mapper.py`:
- Around line 49-85: Add a clear docstring to the public to_snapshot function
describing that it builds the runtime snapshot, documenting the calls and
generated_at parameters, and specifying the returned snapshot dictionary.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b235da6f-2146-4003-9313-c5062a857469

📥 Commits

Reviewing files that changed from the base of the PR and between eddceca and e1a419f.

📒 Files selected for processing (28)
  • agent/src/agent.py
  • agent/src/agents/agent_realty.py
  • agent/src/agents/base_agent.py
  • agent/src/agents/call_context.py
  • agent/src/agents/concierge_agent.py
  • agent/src/agents/property_agent.py
  • agent/src/agents/scheduling_agent.py
  • agent/src/prompts/instructions.py
  • agent/src/services/api_client.py
  • agent/tests/unit/test_agent_boot.py
  • agent/tests/unit/test_agent_realty.py
  • agent/tests/unit/test_api_client.py
  • agent/tests/unit/test_call_context.py
  • agent/tests/unit/test_concierge_agent.py
  • agent/tests/unit/test_instructions.py
  • agent/tests/unit/test_property_agent.py
  • agent/tests/unit/test_scheduling_agent.py
  • backend/src/api/endpoints/openorca.py
  • backend/src/api/routes.py
  • backend/src/core/graph_token.py
  • backend/src/runtime/__init__.py
  • backend/src/runtime/live_agents.py
  • backend/src/runtime/openorca_mapper.py
  • backend/tests/integration/test_agent_state_sse.py
  • backend/tests/unit/test_graph_token.py
  • backend/tests/unit/test_live_agents.py
  • backend/tests/unit/test_openorca_endpoints.py
  • backend/tests/unit/test_openorca_mapper.py
💤 Files with no reviewable changes (2)
  • agent/src/agents/agent_realty.py
  • agent/tests/unit/test_agent_realty.py

Comment on lines +270 to +289
async def close(self, reason: str = "") -> None:
"""Per-call teardown, run exactly once (job shutdown callback): usage summary, persist
the call log and fold the conversation into memory, then release the HTTP pool."""
if self._closed:
return
self._closed = True
# Cancel the max-call timer we own so it never lingers past teardown (the LiveKit
# runtime also cancels it in production, but doing it here keeps close self-contained).
if self._max_call_task is not None:
self._max_call_task.cancel()
if self._log_usage_summary is not None:
self._log_usage_summary()
if self.api is not None:
try:
if self.room:
await post_call_log(
self.api, self.room, buyer_phone=self.last_phone
)
finally:
await self.api.aclose()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

_log_usage_summary() can prevent HTTP pool cleanup in close().

If self._log_usage_summary() raises at line 281, execution exits before the try/finally block, so await self.api.aclose() is never called. This leaks the httpx.AsyncClient connection pool for the call. In a long-running worker handling many calls, even rare failures here could accumulate leaked pools.

🔒 Proposed fix: wrap `_log_usage_summary` in try/except
         if self._log_usage_summary is not None:
-            self._log_usage_summary()
+            try:
+                self._log_usage_summary()
+            except Exception:
+                logger.warning("usage summary failed", exc_info=True)
         if self.api is not None:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async def close(self, reason: str = "") -> None:
"""Per-call teardown, run exactly once (job shutdown callback): usage summary, persist
the call log and fold the conversation into memory, then release the HTTP pool."""
if self._closed:
return
self._closed = True
# Cancel the max-call timer we own so it never lingers past teardown (the LiveKit
# runtime also cancels it in production, but doing it here keeps close self-contained).
if self._max_call_task is not None:
self._max_call_task.cancel()
if self._log_usage_summary is not None:
self._log_usage_summary()
if self.api is not None:
try:
if self.room:
await post_call_log(
self.api, self.room, buyer_phone=self.last_phone
)
finally:
await self.api.aclose()
async def close(self, reason: str = "") -> None:
"""Per-call teardown, run exactly once (job shutdown callback): usage summary, persist
the call log and fold the conversation into memory, then release the HTTP pool."""
if self._closed:
return
self._closed = True
# Cancel the max-call timer we own so it never lingers past teardown (the LiveKit
# runtime also cancels it in production, but doing it here keeps close self-contained).
if self._max_call_task is not None:
self._max_call_task.cancel()
if self._log_usage_summary is not None:
try:
self._log_usage_summary()
except Exception:
logger.warning("usage summary failed", exc_info=True)
if self.api is not None:
try:
if self.room:
await post_call_log(
self.api, self.room, buyer_phone=self.last_phone
)
finally:
await self.api.aclose()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/src/agents/call_context.py` around lines 270 - 289, Ensure close()
always releases the HTTP client even when _log_usage_summary() fails: wrap the
usage-summary call in try/except, handle or log the exception, and continue into
the existing API cleanup path so self.api.aclose() executes.

Comment on lines +54 to +96
@function_tool
@traced_tool
async def capture_lead(
self,
context: RunContext,
name: str | None = None,
phone: str | None = None,
area: str | None = None,
max_price: int | None = None,
min_beds: int | None = None,
) -> str:
"""Record the buyer's contact details (name, phone) and what they are looking for
(area, budget, bedrooms). Safe to call again as details firm up.
"""
if phone:
self.ctx.last_phone = phone
phone = phone or self.ctx.last_phone
criteria: dict[str, object] = {}
if area:
criteria["area"] = area
if max_price:
criteria["maxPrice"] = max_price
if min_beds:
criteria["minBeds"] = min_beds
try:
await self.ctx.api.capture_lead(
{"phone": phone or "", "name": name, "criteria": criteria or None}
)
except Exception as exc: # noqa: BLE001 (degrade gracefully)
logger.warning("capture_lead failed: %s", exc)
self.ctx.fire(
self.ctx.push_event(
"lead", {"name": name, "phone": phone, "criteria": criteria or None}
)
)
recalled = await self.ctx.recall_returning_buyer()
if recalled:
return (
"This is a returning buyer we remember. Welcome them back by name and reuse "
"what we already know instead of re-asking it. What we remember: "
+ recalled
)
return f"Thanks{', ' + name if name else ''}. I have your details."

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

capture_lead silently swallows API errors and still fires the event and returns success.

When self.ctx.api.capture_lead(...) raises (line 79-82), the exception is logged but execution continues: the lead event is still pushed (line 84-88) and the function returns a success message (line 96). The caller (the LLM) will tell the buyer their details were captured even though the backend never recorded them. Consider returning an error message to the LLM when the API call fails, so the agent can offer to retry or take details manually.

🐛 Proposed fix
         try:
             await self.ctx.api.capture_lead(
                 {"phone": phone or "", "name": name, "criteria": criteria or None}
             )
         except Exception as exc:  # noqa: BLE001  (degrade gracefully)
             logger.warning("capture_lead failed: %s", exc)
-            self.ctx.fire(
-                self.ctx.push_event(
-                    "lead", {"name": name, "phone": phone, "criteria": criteria or None}
-                )
-            )
-            recalled = await self.ctx.recall_returning_buyer()
-            if recalled:
-                return (
-                    "This is a returning buyer we remember. Welcome them back by name and reuse "
-                    "what we already know instead of re-asking it. What we remember: "
-                    + recalled
-                )
-            return f"Thanks{', ' + name if name else ''}. I have your details."
+            return "I wasn't able to save your details just now. Can I try again?"
         self.ctx.fire(
             self.ctx.push_event(
                 "lead", {"name": name, "phone": phone, "criteria": criteria or None}
             )
         )
         recalled = await self.ctx.recall_returning_buyer()
         if recalled:
             return (
                 "This is a returning buyer we remember. Welcome them back by name and reuse "
                 "what we already know instead of re-asking it. What we remember: "
                 + recalled
             )
         return f"Thanks{', ' + name if name else ''}. I have your details."
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@function_tool
@traced_tool
async def capture_lead(
self,
context: RunContext,
name: str | None = None,
phone: str | None = None,
area: str | None = None,
max_price: int | None = None,
min_beds: int | None = None,
) -> str:
"""Record the buyer's contact details (name, phone) and what they are looking for
(area, budget, bedrooms). Safe to call again as details firm up.
"""
if phone:
self.ctx.last_phone = phone
phone = phone or self.ctx.last_phone
criteria: dict[str, object] = {}
if area:
criteria["area"] = area
if max_price:
criteria["maxPrice"] = max_price
if min_beds:
criteria["minBeds"] = min_beds
try:
await self.ctx.api.capture_lead(
{"phone": phone or "", "name": name, "criteria": criteria or None}
)
except Exception as exc: # noqa: BLE001 (degrade gracefully)
logger.warning("capture_lead failed: %s", exc)
self.ctx.fire(
self.ctx.push_event(
"lead", {"name": name, "phone": phone, "criteria": criteria or None}
)
)
recalled = await self.ctx.recall_returning_buyer()
if recalled:
return (
"This is a returning buyer we remember. Welcome them back by name and reuse "
"what we already know instead of re-asking it. What we remember: "
+ recalled
)
return f"Thanks{', ' + name if name else ''}. I have your details."
`@function_tool`
`@traced_tool`
async def capture_lead(
self,
context: RunContext,
name: str | None = None,
phone: str | None = None,
area: str | None = None,
max_price: int | None = None,
min_beds: int | None = None,
) -> str:
"""Record the buyer's contact details (name, phone) and what they are looking for
(area, budget, bedrooms). Safe to call again as details firm up.
"""
if phone:
self.ctx.last_phone = phone
phone = phone or self.ctx.last_phone
criteria: dict[str, object] = {}
if area:
criteria["area"] = area
if max_price:
criteria["maxPrice"] = max_price
if min_beds:
criteria["minBeds"] = min_beds
try:
await self.ctx.api.capture_lead(
{"phone": phone or "", "name": name, "criteria": criteria or None}
)
except Exception as exc: # noqa: BLE001 (degrade gracefully)
logger.warning("capture_lead failed: %s", exc)
return "I wasn't able to save your details just now. Can I try again?"
self.ctx.fire(
self.ctx.push_event(
"lead", {"name": name, "phone": phone, "criteria": criteria or None}
)
)
recalled = await self.ctx.recall_returning_buyer()
if recalled:
return (
"This is a returning buyer we remember. Welcome them back by name and reuse "
"what we already know instead of re-asking it. What we remember: "
recalled
)
return f"Thanks{', ' + name if name else ''}. I have your details."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/src/agents/concierge_agent.py` around lines 54 - 96, capture_lead
currently treats API failures as success. In the exception handler around
self.ctx.api.capture_lead, log the failure and immediately return a clear error
message instructing the agent that saving failed and it should offer a retry or
collect the details manually; do not push the lead event or execute the
success/returning-buyer response when the API call fails.

Comment on lines +69 to +74
class PropertyAgent(RealtyBaseAgent):
ID = PROPERTY
ACTION = "Searching listings"

def __init__(self, ctx: CallContext) -> None:
super().__init__(ctx, instructions=property_instructions(ctx.persona or None))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add class and __init__ docstrings to PropertyAgent.

The class and its constructor lack docstrings. As per coding guidelines, each public class should have a clear docstring explaining its purpose, parameters, and return values.

📝 Proposed docstring additions
 class PropertyAgent(RealtyBaseAgent):
+    """Property specialist that searches realtor listings and shows homes to buyers."""
     ID = PROPERTY
     ACTION = "Searching listings"
 
     def __init__(self, ctx: CallContext) -> None:
+        """Initialize the PropertyAgent with shared call context and property instructions."""
         super().__init__(ctx, instructions=property_instructions(ctx.persona or None))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class PropertyAgent(RealtyBaseAgent):
ID = PROPERTY
ACTION = "Searching listings"
def __init__(self, ctx: CallContext) -> None:
super().__init__(ctx, instructions=property_instructions(ctx.persona or None))
class PropertyAgent(RealtyBaseAgent):
"""Property specialist that searches realtor listings and shows homes to buyers."""
ID = PROPERTY
ACTION = "Searching listings"
def __init__(self, ctx: CallContext) -> None:
"""Initialize the PropertyAgent with shared call context and property instructions."""
super().__init__(ctx, instructions=property_instructions(ctx.persona or None))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/src/agents/property_agent.py` around lines 69 - 74, Add clear
docstrings to the public PropertyAgent class and its __init__ method, describing
the agent’s purpose, the ctx parameter, and the constructor’s return value
(None), while preserving the existing initialization behavior.

Source: Coding guidelines

Comment on lines +18 to +23
class SchedulingAgent(RealtyBaseAgent):
ID = SCHEDULING
ACTION = "Checking the calendar"

def __init__(self, ctx: CallContext) -> None:
super().__init__(ctx, instructions=scheduling_instructions(ctx.persona or None))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add class and __init__ docstrings to SchedulingAgent.

The class and its constructor lack docstrings. As per coding guidelines, each public class should have a clear docstring explaining its purpose, parameters, and return values.

📝 Proposed docstring additions
 class SchedulingAgent(RealtyBaseAgent):
+    """Scheduling specialist that checks showing availability and books visits."""
     ID = SCHEDULING
     ACTION = "Checking the calendar"
 
     def __init__(self, ctx: CallContext) -> None:
+        """Initialize the SchedulingAgent with shared call context and scheduling instructions."""
         super().__init__(ctx, instructions=scheduling_instructions(ctx.persona or None))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class SchedulingAgent(RealtyBaseAgent):
ID = SCHEDULING
ACTION = "Checking the calendar"
def __init__(self, ctx: CallContext) -> None:
super().__init__(ctx, instructions=scheduling_instructions(ctx.persona or None))
class SchedulingAgent(RealtyBaseAgent):
"""Scheduling specialist that checks showing availability and books visits."""
ID = SCHEDULING
ACTION = "Checking the calendar"
def __init__(self, ctx: CallContext) -> None:
"""Initialize the SchedulingAgent with shared call context and scheduling instructions."""
super().__init__(ctx, instructions=scheduling_instructions(ctx.persona or None))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/src/agents/scheduling_agent.py` around lines 18 - 23, Add a class
docstring to SchedulingAgent describing its scheduling purpose, and add an
__init__ docstring documenting the ctx parameter and that initialization returns
None. Place both docstrings directly inside the class and constructor
definitions without changing behavior.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant