Multi-agent runtime: Concierge/Property/Scheduling + OpenOrca live-agent contract - #80
Conversation
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.
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
superrealty | e1a419f | Jul 10 2026, 04:37 PM |
WalkthroughChangesVoice specialist agent flow
OpenOrca runtime surface
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
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
…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.
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (11)
backend/src/core/graph_token.py (1)
25-35: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd docstrings to
mint_graph_tokenandverify_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 winAdd docstrings to public functions and the
AgentStateInclass.
AgentStateIn,agent_state,snapshot,runtime_info,resolve_intervention, andeventslack 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 winReplace raw
dictreturns with Pydantic response models.Five endpoints return
dictinstead of typed response models. As per path instructions, avoid rawdictin route signatures and use Pydantic models for response validation.Simple response models for
agent_state,graph_token,runtime_info, andresolve_interventionare straightforward. Thesnapshotendpoint may require a model matching theto_snapshotreturn structure fromopenorca_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 winAdd 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 winAdd a docstring to
to_snapshot.
to_snapshotis 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 valueAdd docstrings to the three exported instruction builders.
concierge_instructions,property_instructions, andscheduling_instructionslack 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_preambleduplicatesrealtor_instructionspersona logic.The persona field extraction and line-building logic (lines 177-195) is identical to
realtor_instructions(lines 90-107).realtor_instructionscould delegate to_persona_preambleto 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 valueAdd docstrings to
RealtyBaseAgentand__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 valueAdd 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 winAdd 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 valueAdd 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
📒 Files selected for processing (28)
agent/src/agent.pyagent/src/agents/agent_realty.pyagent/src/agents/base_agent.pyagent/src/agents/call_context.pyagent/src/agents/concierge_agent.pyagent/src/agents/property_agent.pyagent/src/agents/scheduling_agent.pyagent/src/prompts/instructions.pyagent/src/services/api_client.pyagent/tests/unit/test_agent_boot.pyagent/tests/unit/test_agent_realty.pyagent/tests/unit/test_api_client.pyagent/tests/unit/test_call_context.pyagent/tests/unit/test_concierge_agent.pyagent/tests/unit/test_instructions.pyagent/tests/unit/test_property_agent.pyagent/tests/unit/test_scheduling_agent.pybackend/src/api/endpoints/openorca.pybackend/src/api/routes.pybackend/src/core/graph_token.pybackend/src/runtime/__init__.pybackend/src/runtime/live_agents.pybackend/src/runtime/openorca_mapper.pybackend/tests/integration/test_agent_state_sse.pybackend/tests/unit/test_graph_token.pybackend/tests/unit/test_live_agents.pybackend/tests/unit/test_openorca_endpoints.pybackend/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
| 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() |
There was a problem hiding this comment.
🩺 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.
| 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.
| @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." |
There was a problem hiding this comment.
🗄️ 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.
| @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.
| class PropertyAgent(RealtyBaseAgent): | ||
| ID = PROPERTY | ||
| ACTION = "Searching listings" | ||
|
|
||
| def __init__(self, ctx: CallContext) -> None: | ||
| super().__init__(ctx, instructions=property_instructions(ctx.persona or None)) |
There was a problem hiding this comment.
📐 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.
| 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
| 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)) |
There was a problem hiding this comment.
📐 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.
| 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
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
Bug Fixes
Tests