Skip to content

Agent HTTP server#284

Merged
dangusev merged 21 commits intomainfrom
feature/agents-http-server
Jan 19, 2026
Merged

Agent HTTP server#284
dangusev merged 21 commits intomainfrom
feature/agents-http-server

Conversation

@dangusev
Copy link
Collaborator

@dangusev dangusev commented Jan 9, 2026

What's changed

  1. Added a new Runner that supports both console and HTTP server modes for the same agents (see 06_agent_server_example for details)
  2. Added HTTP server to manage running Agent sessions

Summary by CodeRabbit

  • New Features

    • Session management for agents (start/close/retrieve), HTTP session endpoints, health/readiness checks, and unified runner supporting CLI and server modes
    • Agent identifiers and metrics exposure; improved logging for server/console
  • Dependencies

    • Added FastAPI, Uvicorn, and ASGI lifespan support for HTTP serving
  • Documentation

    • Added Agent Server Example with README and runnable example
  • Tests

    • Added comprehensive session and runner integration tests

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 9, 2026

📝 Walkthrough

Walkthrough

Adds HTTP server support and session lifecycle for Vision Agents: introduces AgentSession and session APIs in AgentLauncher, a FastAPI-based HTTP router and models, Runner to serve or run agents (CLI/HTTP), observability hooks, logging tweaks, tests, and example server project files.

Changes

Cohort / File(s) Summary
Agent & Session Management
agents-core/vision_agents/core/agents/agents.py, agents-core/vision_agents/core/agents/agent_launcher.py
Added Agent.id and MetricsCollector initialization; introduced AgentSession dataclass and session store; renamed public hooks to _create_agent/_join_call; added launcher properties (warmed_up, running, ready) and session APIs (start_session, close_session, get_session) with async task lifecycle management.
HTTP API Layer
agents-core/vision_agents/core/runner/http/api.py, agents-core/vision_agents/core/runner/http/models.py, agents-core/vision_agents/core/runner/http/dependencies.py, agents-core/vision_agents/core/runner/http/options.py
New FastAPI router, lifespan, DI helpers and ServeOptions; endpoints to start/close/get sessions, metrics, health and readiness; Pydantic request/response models and permission/user hooks.
Runtime & Orchestration
agents-core/vision_agents/core/runner/runner.py, agents-core/vision_agents/core/runner/__init__.py
New Runner class wiring AgentLauncher to console/CLI/HTTP modes, creating FastAPI app when needed, configuring logging, uvicorn serving, and Click CLI subcommands.
Logging & Observability
agents-core/vision_agents/core/utils/logging.py, agents-core/vision_agents/core/observability/agent.py
Added colored formatter helper and configure_fastapi_loggers(); refactored SDK logger setup; added AgentMetrics.to_dict(fields=...) to export selected metrics.
Public API Expansion
agents-core/vision_agents/core/__init__.py
Exported AgentSession, Runner, and ServeOptions from core package.
CLI Runner Integration
agents-core/vision_agents/core/cli/cli_runner.py
Replaced optional join_call flow with unified launcher.start_session(...); session.wait() session-based join flow.
Tests
tests/test_observability.py, tests/test_agents/test_agent_launcher.py, tests/test_agents/test_runner.py
Added tests for AgentMetrics.to_dict, AgentLauncher session lifecycle, and extensive Runner/HTTP session integration tests (health/readiness, permission hooks, metrics, beacon close).
Examples & Config
examples/08_agent_server_example/*, examples/02_golf_coach_example/golf_coach_example.py, agents-core/pyproject.toml, pyproject.toml
New agent-server example + README and pyproject; added FastAPI/uvicorn/asgi-lifespan dependencies; minor import reordering in an example.

Sequence Diagram(s)

sequenceDiagram
    participant Client as HTTP Client
    participant API as FastAPI Router
    participant Launcher as AgentLauncher
    participant Agent as Agent
    participant Sessions as Sessions Store

    Client->>API: POST /sessions {call_id}
    API->>Launcher: start_session(call_id)
    Launcher->>Launcher: _create_agent()
    Launcher->>Agent: initialize
    Launcher->>Sessions: track AgentSession
    Launcher->>Launcher: spawn join task
    API->>Client: 201 {session_id}
    
    Note over Launcher: Session runs asynchronously
    Launcher->>Agent: _join_call(call_type, call_id)
    Agent->>Agent: process call
    
    Client->>API: GET /sessions/{session_id}/metrics
    API->>Launcher: get_session(session_id)
    API->>Agent: agent.metrics
    API->>Client: 200 {metrics}
    
    Client->>API: DELETE /sessions/{session_id}
    API->>Launcher: close_session(session_id)
    Launcher->>Sessions: cancel/await join task
    Sessions->>Sessions: cleanup
    API->>Client: 204
Loading
sequenceDiagram
    participant User as CLI User
    participant Runner as Runner
    participant Launcher as AgentLauncher
    participant SDK as SDK Logger
    participant Agent as Agent
    participant Demo as Demo UI

    User->>Runner: run(call_id, call_type)
    Runner->>SDK: configure_sdk_logger()
    Runner->>Launcher: start()
    Launcher->>Agent: warmup
    Runner->>Agent: launch()
    alt Agent has open_demo_for_agent
        Runner->>Demo: open UI
    end
    Runner->>Launcher: start_session(call_id)
    Launcher->>Launcher: spawn join task
    Runner->>Launcher: await session.wait()
    Note over Launcher: Processing call
    Launcher->>Launcher: task completes
    Runner->>Launcher: stop()
    Runner->>User: return
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

cli

Suggested reviewers

  • d3xvn
  • Nash0x7E2

Poem

The engine starts: a small white throat of code,
It swallows air, a dark exactness of time,
Endpoints like teeth turn, clicking open rooms—
A session gathers mercury and waits,
The runner counts the quiet, making light.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.70% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Agent HTTP server' directly and specifically describes the main addition: a new HTTP server capability for running agents.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 Fix all issues with AI agents
In @agents-core/vision_agents/core/agents/agent_launcher.py:
- Around line 29-30: The type hints for create_agent and join_call use invalid
Coroutine generics; replace those malformatted types with correct generics such
as Coroutine[Any, Any, Agent] for create_agent’s return and Coroutine[Any, Any,
None] for join_call, or alternatively use Awaitable[Agent] and Awaitable[None]
from typing; update the annotations on create_agent, join_call and any imports
(e.g., add Any or Awaitable) accordingly so the signatures correctly express the
asynchronous return types for Agent and None.

In @agents-core/vision_agents/core/runners/http/routes.py:
- Around line 76-81: The current block catching a bare Exception around
launcher.join should be changed to catch the specific exceptions launcher.join
can raise (e.g., RuntimeError, asyncio.CancelledError, OSError) by replacing
"except Exception as e" with explicit except clauses for those exceptions and
handling each with logger.exception and raising the HTTPException; if you cannot
yet enumerate them, leave a single broad catch only after the specific catches
and add a clear comment above the block explaining why a catch-all is
temporarily required and that it must be narrowed before production, and ensure
the logger.exception and HTTPException detail remain intact (refer to
launcher.join, logger.exception, and HTTPException in this change).
- Around line 143-151: In the ready function, replace the incorrect
Response(200) call with a proper Response using the status_code kwarg (e.g.,
Response(status_code=200)) and remove the unnecessary else by returning early:
check launcher.warmed_up and return the 200 Response when true, otherwise return
the JSONResponse with status_code=400; reference the ready function and
launcher.warmed_up to locate the change.
- Around line 135-140: The alive route is creating a Response incorrectly by
passing 200 as the content; update the handler (the async function alive
decorated with @router.get("/alive")) to instantiate Response with the
status_code keyword (e.g., Response(status_code=200) or Response(content="",
status_code=200)) so the HTTP status is set explicitly rather than placed into
the response body.
- Line 33: The router-level lifespan passed into APIRouter (router =
APIRouter(lifespan=lifespan)) will not run when the router is mounted into a
FastAPI app; move the lifespan registration to the FastAPI app instead by
removing lifespan from APIRouter and passing the lifespan context manager to the
FastAPI constructor where the app is created (ensure any setup/teardown logic
currently in the router-level lifespan is invoked by the app-level lifespan);
update references to router and lifespan and remove the router-level lifespan
parameter so lifecycle handlers run correctly when the router is included.

In @agents-core/vision_agents/core/runners/http/runner.py:
- Around line 34-40: Replace the getattr-based lookup for numeric_level in the
run method with an explicit mapping: create a dict mapping common level names
("CRITICAL","ERROR","WARNING","INFO","DEBUG","NOTSET") to their logging.*
constants, compute key = log_level.upper(), set numeric_level = mapping.get(key,
logging.INFO) and then call configure_sdk_logger(level=numeric_level); keep the
rest of run (app creation and uvicorn.run) unchanged and validate unknown levels
by falling back to logging.INFO.

In @examples/05_agent_server_example/pyproject.toml:
- Line 4: Update the examples/05_agent_server_example pyproject.toml to relax
the Python requirement: change the requires-python entry (requires-python) from
">=3.13" to ">=3.10" so it aligns with the core vision-agents package and does
not unnecessarily restrict users.
🧹 Nitpick comments (6)
examples/05_agent_server_example/README.md (1)

1-1: Documentation placeholder needs completion.

This README is currently a stub. Consider adding documentation that covers:

  • Purpose of the example
  • Setup instructions
  • How to run the agent server
  • API endpoints available
  • Example usage/requests

Would you like me to generate initial README content for this example, or open an issue to track this documentation task?

examples/05_agent_server_example/agent_server_example.py (3)

14-24: Module docstring placement issue.

The module-level docstring should appear at the very top of the file before imports. Currently it's positioned after the imports, which means Python won't recognize it as the module's __doc__.

🔧 Suggested fix

Move the docstring to the top of the file:

+"""
+Agent example optimized for fast response time.
+
+Eager turn taking STT, LLM, TTS workflow
+- deepgram for optimal latency
+- eleven labs for TTS
+- gemini-2.5-flash-lite for fast responses
+- stream's edge network for video transport
+
+This example uses STT, for a realtime openAI/gemini example see 02_golf_coach_example
+"""
+
 import logging
 from typing import Any, Dict
 ...
-"""
-Agent example optimized for fast response time.
-...
-"""

47-48: Function docstring missing.

Per Google style guide for docstrings, async functions should have docstrings describing their purpose, args, and return value. As per coding guidelines, docstrings should follow the Google style guide.

📝 Suggested docstring
 @llm.register_function(description="Get current weather for a location")
 async def get_weather(location: str) -> Dict[str, Any]:
+    """Get current weather for a location.
+
+    Args:
+        location: The location to get weather for.
+
+    Returns:
+        Weather data dictionary for the specified location.
+    """
     return await get_weather_by_location(location)

53-64: Good structure for the join_call callback.

The TODO comment on lines 54-55 is a valid concern - the coupling to call_type and call_id parameters does tie this to a specific Edge implementation. Consider tracking this as a future improvement.

Would you like me to open an issue to track the abstraction of call parameters?

agents-core/vision_agents/core/runners/http/runner.py (1)

42-54: Minor inconsistency in return value.

The method uses app as a local reference but returns self._fast_api. While functionally equivalent, returning app would be more consistent.

🔧 Suggested fix
     def _configure_fastapi_app(self) -> FastAPI:
         app = self._fast_api
         app.state.launcher = self._launcher
         app.include_router(router)
         # TODO: Allow to control origins
         app.add_middleware(
             CORSMiddleware,
             allow_origins=["*"],
             allow_credentials=True,
             allow_methods=["*"],
             allow_headers=["*"],
         )
-        return self._fast_api
+        return app
agents-core/vision_agents/core/agents/agent_launcher.py (1)

144-152: Consider logging when cancelling an agent.

Adding a debug log when cancelling would aid troubleshooting.

🔧 Suggested enhancement
     async def close_agent(self, agent_id: str, wait: bool = False) -> None:
         task = self._call_tasks.pop(agent_id, None)
         if task is None:
             raise AgentNotFoundError(f"Agent with id {agent_id} not found")
 
+        logger.debug(f"Closing agent {agent_id}, wait={wait}")
         if wait:
             await cancel_and_wait(task)
         else:
             task.cancel()
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1740881 and 3e24225.

⛔ Files ignored due to path filters (2)
  • examples/05_agent_server_example/uv.lock is excluded by !**/*.lock
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • agents-core/pyproject.toml
  • agents-core/vision_agents/core/agents/agent_launcher.py
  • agents-core/vision_agents/core/runners/__init__.py
  • agents-core/vision_agents/core/runners/http/__init__.py
  • agents-core/vision_agents/core/runners/http/models.py
  • agents-core/vision_agents/core/runners/http/routes.py
  • agents-core/vision_agents/core/runners/http/runner.py
  • examples/05_agent_server_example/README.md
  • examples/05_agent_server_example/__init__.py
  • examples/05_agent_server_example/agent_server_example.py
  • examples/05_agent_server_example/pyproject.toml
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/python.mdc)

**/*.py: Never adjust sys.path in Python code
Never write except Exception as e - use specific exception handling
Avoid using getattr, hasattr, delattr and setattr; prefer normal attribute access in Python
Docstrings should follow the Google style guide for docstrings

Files:

  • agents-core/vision_agents/core/runners/http/__init__.py
  • agents-core/vision_agents/core/runners/http/routes.py
  • agents-core/vision_agents/core/runners/__init__.py
  • examples/05_agent_server_example/__init__.py
  • agents-core/vision_agents/core/runners/http/runner.py
  • examples/05_agent_server_example/agent_server_example.py
  • agents-core/vision_agents/core/runners/http/models.py
  • agents-core/vision_agents/core/agents/agent_launcher.py
🧠 Learnings (1)
📚 Learning: 2025-11-24T17:04:43.030Z
Learnt from: CR
Repo: GetStream/Vision-Agents PR: 0
File: .cursor/rules/python.mdc:0-0
Timestamp: 2025-11-24T17:04:43.030Z
Learning: Use uv for dependency management on this project, not python -m or system package managers

Applied to files:

  • examples/05_agent_server_example/pyproject.toml
🧬 Code graph analysis (5)
agents-core/vision_agents/core/runners/http/__init__.py (1)
agents-core/vision_agents/core/runners/http/runner.py (1)
  • HTTPServerRunner (23-54)
agents-core/vision_agents/core/runners/__init__.py (1)
agents-core/vision_agents/core/runners/http/runner.py (1)
  • HTTPServerRunner (23-54)
agents-core/vision_agents/core/runners/http/runner.py (3)
agents-core/vision_agents/core/agents/agent_launcher.py (1)
  • AgentLauncher (19-228)
agents-core/vision_agents/core/utils/logging.py (1)
  • configure_sdk_logger (19-46)
agents-core/vision_agents/core/edge/sfu_events.py (1)
  • level (496-500)
agents-core/vision_agents/core/runners/http/models.py (1)
agents-core/vision_agents/core/edge/sfu_events.py (1)
  • message (662-666)
agents-core/vision_agents/core/agents/agent_launcher.py (2)
agents-core/vision_agents/core/utils/utils.py (2)
  • cancel_and_wait (105-121)
  • await_or_run (92-102)
agents-core/vision_agents/core/agents/agents.py (1)
  • join (510-588)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: unit / Validate extra dependencies in "agents-core/pyproject.toml"
  • GitHub Check: unit / Test "not integration"
  • GitHub Check: unit / Mypy
  • GitHub Check: unit / Ruff
  • GitHub Check: unit / Validate extra dependencies in "agents-core/pyproject.toml"
  • GitHub Check: unit / Test "not integration"
  • GitHub Check: unit / Mypy
  • GitHub Check: unit / Ruff
🔇 Additional comments (12)
examples/05_agent_server_example/__init__.py (1)

1-1: Package initializer is correct.

An empty __init__.py is the standard pattern for Python packages. This is appropriate for an example package.

examples/05_agent_server_example/pyproject.toml (1)

17-22: Editable local dependencies configured correctly.

The use of [tool.uv.sources] with editable paths for local development is appropriate and follows the project's dependency management practices.

Based on learnings, this correctly uses uv for dependency management.

agents-core/vision_agents/core/runners/http/__init__.py (1)

1-1: LGTM!

The explicit re-export pattern correctly exposes HTTPServerRunner at the package level for clean imports.

agents-core/vision_agents/core/runners/__init__.py (1)

1-1: LGTM!

The re-export makes HTTPServerRunner conveniently accessible at the package level, enabling clean imports like from vision_agents.core.runners import HTTPServerRunner.

agents-core/pyproject.toml (1)

30-30: No issues found. FastAPI 0.128.0 is the latest stable version (released Dec 27, 2025) with no known direct security vulnerabilities. The version constraint is valid.

examples/05_agent_server_example/agent_server_example.py (1)

67-69: LGTM!

Clean main block wiring HTTPServerRunner with AgentLauncher.

agents-core/vision_agents/core/runners/http/runner.py (2)

32-32: Empty worker method - intentional placeholder?

The worker method is defined but empty. If this is intentional for future implementation, consider adding a docstring or removing it until needed.


47-53: Permissive CORS configuration acknowledged.

The TODO on line 46 acknowledges this needs refinement. For production, allow_origins=["*"] with allow_credentials=True is a security concern. The combination can expose credentials to any origin.

Consider prioritizing the CORS configuration before production deployment.

agents-core/vision_agents/core/runners/http/models.py (2)

1-48: Well-structured Pydantic models.

The models are cleanly defined with proper Field descriptions and sensible defaults. The use of str | None union syntax is appropriate for Python 3.10+.


127-142: Models defined but not yet used in routes.

AgentSessionInfo and ListAgentsResponse are defined but not currently used by any route in routes.py. This is fine for a WIP if you plan to add list/status endpoints.

Are these models intended for upcoming endpoints?

agents-core/vision_agents/core/agents/agent_launcher.py (1)

126-142: Good implementation of the join method.

The join method properly creates agents, warms them up, tracks tasks, and cleans up via done callback. The UUID-based agent_id provides good uniqueness.

agents-core/vision_agents/core/runners/http/routes.py (1)

22-30: Clean lifespan management pattern.

The async context manager for launcher lifecycle is well-structured with proper try/finally cleanup.

@dangusev dangusev force-pushed the feature/agents-http-server branch from 3e24225 to f718488 Compare January 12, 2026 13:46
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @agents-core/vision_agents/core/agents/agent_launcher.py:
- Around line 78-82: The loop is iterating over awaitables returned by
asyncio.as_completed(coros) but then calls .done()/.exception() on them without
awaiting; fix by awaiting each item yielded by asyncio.as_completed to get the
completed task/result (await result) before inspecting it, e.g., iterate "async
for coro in asyncio.as_completed(coros): completed = await coro" then check
completed.done()/completed.cancelled()/completed.exception() and log exceptions
from cancel_and_wait; ensure you reference the coros list, cancel_and_wait, and
self._call_tasks to locate and update the code.
- Around line 29-30: The Coroutine type parameters are in the wrong order;
import Any from typing and change Coroutine["Agent", ..., ...] to Coroutine[Any,
Any, "Agent"] (for create_agent) and change Coroutine[None, ..., ...] to
Coroutine[Any, Any, None] (for join_call), keeping the existing Callable
signatures and quoted "Agent" forward reference as-is; also add Any to the
module imports.
🧹 Nitpick comments (4)
agents-core/vision_agents/core/agents/agent_launcher.py (1)

126-142: Implementation looks solid; address the typing TODO.

The task lifecycle management with done callback for cleanup is well-structured. Consider addressing the TODO: Typing comment before merging—the method's return type annotation is missing.

Would you like me to help generate proper type annotations for this method?

examples/05_agent_server_example/README.md (1)

1-1: Address the TODO before merging.

This placeholder README should be populated with setup instructions, usage examples, and configuration details for the agent server example.

Would you like me to draft a README with sections for prerequisites, running the example, API endpoints, and configuration options?

examples/05_agent_server_example/pyproject.toml (1)

14-14: Consider moving pyinstrument to optional dependencies.

This profiling tool seems like a development/debugging aid rather than a runtime requirement for the example.

 dependencies = [
     "python-dotenv>=1.0",
     "vision-agents-plugins-deepgram",
     "vision-agents-plugins-elevenlabs",
     "vision-agents-plugins-getstream",
     "vision-agents-plugins-gemini",
     "vision-agents",
-    "pyinstrument>=5.1.1",
 ]
+
+[project.optional-dependencies]
+dev = ["pyinstrument>=5.1.1"]
agents-core/vision_agents/core/runners/http/runner.py (1)

36-36: Avoid using getattr per coding guidelines.

The coding guidelines state to avoid getattr, hasattr, delattr, and setattr; prefer normal attribute access.

✏️ Suggested fix
-        numeric_level = getattr(logging, log_level.upper(), logging.INFO)
+        log_level_map = {
+            "DEBUG": logging.DEBUG,
+            "INFO": logging.INFO,
+            "WARNING": logging.WARNING,
+            "ERROR": logging.ERROR,
+            "CRITICAL": logging.CRITICAL,
+        }
+        numeric_level = log_level_map.get(log_level.upper(), logging.INFO)

Based on coding guidelines.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3e24225 and f718488.

⛔ Files ignored due to path filters (2)
  • examples/05_agent_server_example/uv.lock is excluded by !**/*.lock
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • agents-core/pyproject.toml
  • agents-core/vision_agents/core/agents/agent_launcher.py
  • agents-core/vision_agents/core/runners/__init__.py
  • agents-core/vision_agents/core/runners/http/__init__.py
  • agents-core/vision_agents/core/runners/http/models.py
  • agents-core/vision_agents/core/runners/http/routes.py
  • agents-core/vision_agents/core/runners/http/runner.py
  • examples/05_agent_server_example/README.md
  • examples/05_agent_server_example/__init__.py
  • examples/05_agent_server_example/agent_server_example.py
  • examples/05_agent_server_example/pyproject.toml
✅ Files skipped from review due to trivial changes (1)
  • examples/05_agent_server_example/init.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • agents-core/vision_agents/core/runners/init.py
  • examples/05_agent_server_example/agent_server_example.py
  • agents-core/vision_agents/core/runners/http/routes.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/python.mdc)

**/*.py: Never adjust sys.path in Python code
Never write except Exception as e - use specific exception handling
Avoid using getattr, hasattr, delattr and setattr; prefer normal attribute access in Python
Docstrings should follow the Google style guide for docstrings

Files:

  • agents-core/vision_agents/core/runners/http/runner.py
  • agents-core/vision_agents/core/runners/http/__init__.py
  • agents-core/vision_agents/core/agents/agent_launcher.py
  • agents-core/vision_agents/core/runners/http/models.py
🧠 Learnings (1)
📚 Learning: 2025-11-24T17:04:43.030Z
Learnt from: CR
Repo: GetStream/Vision-Agents PR: 0
File: .cursor/rules/python.mdc:0-0
Timestamp: 2025-11-24T17:04:43.030Z
Learning: Use uv for dependency management on this project, not python -m or system package managers

Applied to files:

  • examples/05_agent_server_example/pyproject.toml
🧬 Code graph analysis (2)
agents-core/vision_agents/core/runners/http/runner.py (2)
agents-core/vision_agents/core/agents/agent_launcher.py (1)
  • AgentLauncher (19-228)
agents-core/vision_agents/core/utils/logging.py (1)
  • configure_sdk_logger (19-46)
agents-core/vision_agents/core/runners/http/__init__.py (1)
agents-core/vision_agents/core/runners/http/runner.py (1)
  • HTTPServerRunner (23-54)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: unit / Test "not integration"
  • GitHub Check: unit / Validate extra dependencies in "agents-core/pyproject.toml"
  • GitHub Check: unit / Mypy
  • GitHub Check: unit / Ruff
  • GitHub Check: unit / Test "not integration"
  • GitHub Check: unit / Validate extra dependencies in "agents-core/pyproject.toml"
  • GitHub Check: unit / Ruff
  • GitHub Check: unit / Mypy
🔇 Additional comments (9)
agents-core/vision_agents/core/agents/agent_launcher.py (2)

144-152: LGTM!

The close_agent method properly handles both synchronous and awaited cancellation patterns, and raises the appropriate custom exception for unknown agent IDs.


16-17: LGTM!

Simple, focused exception class for the specific error case.

examples/05_agent_server_example/pyproject.toml (1)

4-4: Python version requirement is stricter than the core package.

The example requires Python >=3.13 while vision-agents core supports >=3.10. This may confuse users who can run the core library but not this example.

Is the 3.13 requirement intentional (e.g., for a specific language feature), or should it align with the core package?

agents-core/vision_agents/core/runners/http/__init__.py (1)

1-1: LGTM!

Clean re-export using the explicit X as X pattern for public API exposure.

agents-core/vision_agents/core/runners/http/runner.py (1)

32-32: Clarify the purpose of the empty worker method.

This stub method has no implementation. Is it intended for future use (background task management, health monitoring), or should it be removed?

agents-core/vision_agents/core/runners/http/models.py (3)

8-47: Well-structured configuration model.

The AgentConfig model provides sensible defaults with clear field descriptions. Good use of Pydantic's Field for documentation.


99-101: Ensure user_id is not logged in request handlers.

The user_id field is a user identifier that could constitute PII. Verify that request logging (if any) in the HTTP routes redacts or excludes this field to maintain compliance.


127-148: LGTM!

The session info and response models are well-defined. AgentSessionInfo appropriately tracks timestamps for session lifecycle, and ErrorResponse follows standard API error patterns.

agents-core/pyproject.toml (1)

30-30: FastAPI 0.128.0 is confirmed available on PyPI with no known vulnerabilities.

Adding FastAPI as a core dependency means all vision-agents users will pull it in. Version 0.128.0 exists on PyPI and is the current latest release with no reported CVEs or security advisories.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In @agents-core/vision_agents/core/agents/agent_launcher.py:
- Around line 112-117: The stop() loop incorrectly treats items from
asyncio.as_completed as results; instead create tasks from cancel_and_wait for
each session (e.g., tasks = [asyncio.create_task(cancel_and_wait(s.task)) for s
in self._sessions.values()]) and then iterate: for fut in
asyncio.as_completed(tasks): use try/except around await fut to catch
asyncio.CancelledError (ignore) and catch other Exceptions to log via
logger.error with the exception; avoid calling fut.exception() on possibly
cancelled futures without awaiting them. This uses the existing cancel_and_wait
and self._sessions symbols and preserves correct async error handling.
- Around line 192-201: The session is being popped from self._sessions before
cancelling its task, which can make other code think the session is gone while
it’s still running; in close_session, first retrieve the session (use
self._sessions.get or similar), raise SessionNotFoundError if None, then perform
cancellation (await cancel_and_wait(session.task) if wait else
session.task.cancel()), and only after cancellation completes (or in a finally
block to guarantee cleanup) remove the session from self._sessions via
self._sessions.pop(session_id, None); reference close_session, session.task,
cancel_and_wait, and self._sessions.pop when applying the change.

In @agents-core/vision_agents/core/runner/http/api.py:
- Around line 76-78: The DELETE route declaration using router.delete with path
"/session/{session_id}" is inconsistent with other endpoints that use
"/sessions/"; change the path to "/sessions/{session_id}" in the router.delete
decorator (and update any associated summary or docstrings if needed) so all
endpoints use the plural resource naming; also scan for and update any
references or tests that call the old "/session/{session_id}" route to use
"/sessions/{session_id}" to avoid broken links.
- Line 32: Move the lifespan context manager off APIRouter and attach it to the
FastAPI app (create the app with FastAPI(lifespan=lifespan) instead of passing
lifespan into APIRouter), update router = APIRouter(...) to remove the lifespan
argument; replace the broad except Exception as e in the request/launch handler
with a specific exception type (e.g., except AgentLauncherError as e:) and
import/define that error type used by the launcher; and normalize route paths to
use plural "sessions" consistently (change "/session/{session_id}" to
"/sessions/{session_id}" or change "/sessions/{session_id}/leave" to singular,
ensuring both handlers use the same "/sessions/{session_id}" base).

In @agents-core/vision_agents/core/utils/logging.py:
- Line 7: The unconditional import of AccessFormatter from uvicorn (the
statement importing AccessFormatter) will fail if uvicorn is not installed;
update pyproject.toml to add uvicorn as a dependency or modify the import in
logging.py to try importing AccessFormatter from uvicorn and fall back to a
no-op or local simple formatter when the import raises ImportError, ensuring any
references to AccessFormatter (e.g., in logger setup functions) use the fallback
formatter if uvicorn is absent.
🧹 Nitpick comments (16)
agents-core/vision_agents/core/runner/http/models.py (2)

16-36: Consider consolidating JoinCallResponse and GetAgentSessionResponse.

These two models share identical fields (session_id, call_id, config, started_at). Consider using a single model or having one inherit from the other to reduce duplication.

Additionally, the config: dict typing is quite loose. The TODO is noted, but consider defining at least a TypedDict or a proper Pydantic model for better validation and documentation.

♻️ Suggested consolidation
-class JoinCallResponse(BaseModel):
-    """Response after successfully starting an agent."""
-
-    session_id: str = Field(..., description="The ID of the agent session")
-    call_id: str = Field(..., description="The ID of the call")
-    config: dict  # TODO: Make it a type
-    started_at: datetime
-
-class ErrorResponse(BaseModel):
-    """Standard error response."""
-
-    detail: str
-
-
-class GetAgentSessionResponse(BaseModel):
-    """Information about an active agent session."""
-
-    session_id: str
-    call_id: str
-    config: dict  # TODO: Make it a type
-    started_at: datetime
+class AgentSessionResponse(BaseModel):
+    """Information about an agent session."""
+
+    session_id: str = Field(..., description="The ID of the agent session")
+    call_id: str = Field(..., description="The ID of the call")
+    config: dict = Field(..., description="Agent configuration")  # TODO: Make it a typed model
+    started_at: datetime = Field(..., description="Session start time")
+
+
+# Aliases for semantic clarity in different contexts
+JoinCallResponse = AgentSessionResponse
+GetAgentSessionResponse = AgentSessionResponse
+
+
+class ErrorResponse(BaseModel):
+    """Standard error response."""
+
+    detail: str

23-24: Missing blank line before ErrorResponse class.

PEP 8 recommends two blank lines before top-level class definitions.

♻️ Formatting fix
     started_at: datetime
 
+
 class ErrorResponse(BaseModel):
agents-core/vision_agents/core/cli/cli_runner.py (3)

87-87: Avoid getattr for log level lookup per coding guidelines.

Replace getattr(logging, log_level.upper(), logging.INFO) with explicit level mapping.

Suggested fix
-        numeric_level = getattr(logging, log_level.upper(), logging.INFO)
+        log_levels = {
+            "DEBUG": logging.DEBUG,
+            "INFO": logging.INFO,
+            "WARNING": logging.WARNING,
+            "ERROR": logging.ERROR,
+            "CRITICAL": logging.CRITICAL,
+        }
+        numeric_level = log_levels.get(log_level.upper(), logging.INFO)

115-121: Avoid hasattr per coding guidelines.

Use direct attribute access with try/except or check via a protocol/interface instead.

Suggested fix
-                if (
-                    not no_demo
-                    and hasattr(agent, "edge")
-                    and hasattr(agent.edge, "open_demo_for_agent")
-                ):
+                if not no_demo:
+                    edge = getattr(agent, "edge", None)
+                    open_demo = getattr(edge, "open_demo_for_agent", None) if edge else None
+                    if callable(open_demo):
                         logger.info("🌐 Opening demo UI...")
                         await agent.edge.open_demo_for_agent(agent, call_type, call_id)

Alternatively, define a protocol or interface that guarantees the edge attribute exists with the expected method, then type-check accordingly.


131-133: Use specific exception handling instead of except Exception as e.

Per coding guidelines, avoid catching the base Exception. Consider catching more specific exceptions that start_session and wait might raise.

examples/05_agent_server_example/agent_server_example.py (1)

11-23: Module docstring placement is unconventional.

The module-level docstring appears after load_dotenv() execution. Move it to the top of the file before imports for standard Python convention.

Suggested structure
+"""
+Agent example optimized for fast response time.
+
+Eager turn taking STT, LLM, TTS workflow
+- deepgram for optimal latency
+- eleven labs for TTS
+- gemini-2.5-flash-lite for fast responses
+- stream's edge network for video transport
+
+This example uses STT, for a realtime openAI/gemini example see 02_golf_coach_example
+"""
+
 import logging
 from typing import Any, Dict
 
 from dotenv import load_dotenv
 ...
 
 load_dotenv()
-
-"""
-Agent example optimized for fast response time.
-...
-"""
agents-core/vision_agents/core/runner/http/api.py (2)

61-66: Use specific exception handling instead of except Exception as e.

Per coding guidelines, catch specific exceptions that launcher.start_session may raise rather than the broad Exception class.


161-171: Readiness check returns 400 for not-ready state.

Consider using 503 Service Unavailable instead of 400 Bad Request, as 503 is more semantically appropriate for readiness/health probes.

Suggested fix
     if launcher.warmed_up and launcher.running:
         return Response(status_code=200)
     else:
         raise HTTPException(
-            status_code=400, detail="Server is not ready to accept requests"
+            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
+            detail="Server is not ready to accept requests"
         )
agents-core/vision_agents/core/runner/runner.py (6)

108-109: Avoid getattr for log level lookup per coding guidelines.

Same issue as in cli_runner.py - use explicit mapping instead.

Suggested fix
-        numeric_level = getattr(logging, log_level.upper(), logging.INFO)
+        log_levels = {
+            "DEBUG": logging.DEBUG,
+            "INFO": logging.INFO,
+            "WARNING": logging.WARNING,
+            "ERROR": logging.ERROR,
+            "CRITICAL": logging.CRITICAL,
+        }
+        numeric_level = log_levels.get(log_level.upper(), logging.INFO)

134-140: Avoid hasattr per coding guidelines.

Use direct attribute access or protocol-based checking.


150-152: Use specific exception handling instead of except Exception as e.

Per coding guidelines, avoid catching base Exception.


121-155: Significant code duplication with cli_runner.py.

The _run() async function in Runner.run() is nearly identical to the one in cli_runner.py. Consider extracting this into a shared utility or having cli_runner.py delegate to Runner.


181-196: Incomplete docstring - Args descriptions are empty.

The serve() method docstring lists parameters but provides no descriptions.

Suggested fix
         """
         Start the HTTP server that spawns agents to the calls.

         Args:
-            host:
-            port:
-            agents_log_level:
-            http_log_level:
-            cors_allow_origins:
-            cors_allow_methods:
-            cors_allow_headers:
-            cors_allow_credentials:
-
-        Returns:
-
+            host: Server host address
+            port: Server port number
+            agents_log_level: Logging level for agent operations
+            http_log_level: Logging level for FastAPI/uvicorn
+            cors_allow_origins: Allowed CORS origins
+            cors_allow_methods: Allowed CORS methods
+            cors_allow_headers: Allowed CORS headers
+            cors_allow_credentials: Whether to allow credentials in CORS
         """

204-210: More getattr usage for log level configuration.

Apply the same fix pattern here for consistency.

agents-core/vision_agents/core/agents/agent_launcher.py (2)

26-26: Exception class body is empty - consider adding a pass statement or docstring.

The ellipsis works but a docstring would be clearer.

Suggested fix
-class SessionNotFoundError(Exception): ...
+class SessionNotFoundError(Exception):
+    """Raised when a session with the given ID is not found."""

178-182: Done callback signature: first parameter is the task itself.

The _done_cb function's first parameter (currently _) receives the completed task. This is fine but documenting it might help future maintainers.

Clearer naming
-        def _done_cb(_, agent_id_=agent.id):
+        def _done_cb(task: asyncio.Task, agent_id_: str = agent.id) -> None:
             self._sessions.pop(agent_id_, None)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cf7ec9c and e4a1ac9.

📒 Files selected for processing (11)
  • agents-core/vision_agents/core/__init__.py
  • agents-core/vision_agents/core/agents/agent_launcher.py
  • agents-core/vision_agents/core/agents/agents.py
  • agents-core/vision_agents/core/cli/cli_runner.py
  • agents-core/vision_agents/core/runner/__init__.py
  • agents-core/vision_agents/core/runner/http/__init__.py
  • agents-core/vision_agents/core/runner/http/api.py
  • agents-core/vision_agents/core/runner/http/models.py
  • agents-core/vision_agents/core/runner/runner.py
  • agents-core/vision_agents/core/utils/logging.py
  • examples/05_agent_server_example/agent_server_example.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/python.mdc)

**/*.py: Never adjust sys.path in Python code
Never write except Exception as e - use specific exception handling
Avoid using getattr, hasattr, delattr and setattr; prefer normal attribute access in Python
Docstrings should follow the Google style guide for docstrings

Files:

  • agents-core/vision_agents/core/runner/__init__.py
  • agents-core/vision_agents/core/cli/cli_runner.py
  • agents-core/vision_agents/core/runner/http/models.py
  • agents-core/vision_agents/core/agents/agents.py
  • agents-core/vision_agents/core/__init__.py
  • examples/05_agent_server_example/agent_server_example.py
  • agents-core/vision_agents/core/utils/logging.py
  • agents-core/vision_agents/core/runner/runner.py
  • agents-core/vision_agents/core/runner/http/api.py
  • agents-core/vision_agents/core/agents/agent_launcher.py
🧬 Code graph analysis (4)
agents-core/vision_agents/core/cli/cli_runner.py (3)
agents-core/vision_agents/core/vad/silero.py (1)
  • session (53-66)
agents-core/vision_agents/core/agents/agent_launcher.py (2)
  • start_session (164-190)
  • wait (45-49)
plugins/aws/vision_agents/plugins/aws/aws_realtime.py (1)
  • start_session (500-505)
agents-core/vision_agents/core/agents/agents.py (1)
agents-core/vision_agents/core/agents/agent_launcher.py (1)
  • id (42-43)
agents-core/vision_agents/core/__init__.py (4)
agents-core/vision_agents/core/cli/cli_runner.py (1)
  • cli (25-152)
agents-core/vision_agents/core/runner/runner.py (2)
  • cli (244-358)
  • Runner (41-358)
dev.py (1)
  • cli (50-53)
agents-core/vision_agents/core/edge/types.py (1)
  • User (15-18)
agents-core/vision_agents/core/agents/agent_launcher.py (3)
agents-core/vision_agents/core/agents/agents.py (2)
  • id (258-259)
  • set_video_track_override_path (907-915)
agents-core/vision_agents/core/runner/http/api.py (2)
  • join_call (52-73)
  • close_session (80-96)
agents-core/vision_agents/core/utils/utils.py (2)
  • cancel_and_wait (105-121)
  • await_or_run (92-102)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: unit / Mypy
  • GitHub Check: unit / Test "not integration"
  • GitHub Check: unit / Validate extra dependencies in "agents-core/pyproject.toml"
  • GitHub Check: unit / Ruff
  • GitHub Check: unit / Test "not integration"
  • GitHub Check: unit / Mypy
  • GitHub Check: unit / Ruff
  • GitHub Check: unit / Validate extra dependencies in "agents-core/pyproject.toml"
🔇 Additional comments (14)
agents-core/vision_agents/core/agents/agents.py (1)

150-150: LGTM! Clean implementation of the agent identity property.

The _id field initialized with uuid4() and the read-only id property provide a stable public identifier for the agent. This aligns well with the AgentSession.id usage in agent_launcher.py that delegates to self.agent.id.

Also applies to: 257-260

agents-core/vision_agents/core/utils/logging.py (2)

20-34: LGTM! Clean extraction of formatter creation logic.

The _get_colored_formatter helper reduces duplication and provides a consistent way to create colored formatters with the expanded color mapping (now including WARNING and ERROR levels).


59-96: LGTM! Well-structured FastAPI logger configuration.

The function follows the same patterns as configure_sdk_logger, maintaining consistency. The separation of AccessFormatter for access logs and generic colored formatter for other loggers is appropriate.

agents-core/vision_agents/core/runner/__init__.py (1)

1-1: LGTM! Proper re-export pattern.

The as Runner syntax correctly signals to type checkers that Runner is intentionally part of the public API.

agents-core/vision_agents/core/__init__.py (1)

1-13: LGTM! Public API surface properly extended.

The Runner class is now correctly exported alongside Agent, User, cli, and AgentLauncher. The __all__ list accurately reflects the module's public API.

agents-core/vision_agents/core/cli/cli_runner.py (1)

124-128: Session-based workflow looks good.

The transition to launcher.start_session() and session.wait() cleanly integrates with the new session lifecycle API.

examples/05_agent_server_example/agent_server_example.py (3)

26-49: Agent creation function looks well-structured.

The create_agent function properly configures the Agent with all required components and registers the MCP function correctly.


52-61: Consider adding error handling around call operations.

The join_call function has no exception handling. If agent.create_call, agent.join, agent.simple_response, or agent.finish fail, exceptions will propagate unhandled.

Is error handling intentionally delegated to the caller (Runner/AgentLauncher), or should this function handle specific failures gracefully?


64-67: Clean entry point using the new Runner API.

The wiring of AgentLauncher with Runner.cli() is straightforward and demonstrates the intended usage pattern.

agents-core/vision_agents/core/runner/http/api.py (2)

45-73: Endpoint implementation is solid.

The join_call endpoint correctly uses start_session and returns appropriate response data with proper status codes.


113-120: Beacon endpoint gracefully handles missing sessions.

Correctly returns 200 even when session not found, as the agent may have already been cleaned up. Good defensive design for sendBeacon semantics.

agents-core/vision_agents/core/runner/runner.py (1)

219-242: FastAPI app configuration is well-structured.

The _configure_fastapi_app method properly handles the bypass case, attaches state, and configures CORS middleware correctly.

agents-core/vision_agents/core/agents/agent_launcher.py (2)

29-50: Well-designed AgentSession dataclass.

Clean encapsulation of session state with useful finished and id properties, and async wait() method.


164-190: Session lifecycle management is well-implemented.

The start_session method correctly:

  • Launches the agent
  • Sets video track override if provided
  • Creates a named task for the join_call
  • Registers cleanup callback via add_done_callback
  • Stores session in _sessions dict

@dangusev dangusev force-pushed the feature/agents-http-server branch from e4a1ac9 to fac9abe Compare January 15, 2026 13:18
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@agents-core/vision_agents/core/runner/runner.py`:
- Around line 148-152: The catch-all "except Exception as e" in runner.py should
be replaced with explicit exception handlers for the known errors the agent
launch/session code can raise (for example RuntimeError, OSError,
asyncio.CancelledError or your project's AgentLaunchError/SessionError types)
instead of catching Exception; update the block around the logger.error call to
handle each specific exception type (logging with exc_info=True and performing
any specific cleanup or re-raise logic per case), and leave any truly unexpected
errors to propagate (or catch them in a top-level handler that records critical
diagnostics) rather than using a bare except Exception.
♻️ Duplicate comments (7)
examples/05_agent_server_example/pyproject.toml (1)

4-4: Python version requirement is overly restrictive.

This has been previously flagged: requires-python = ">=3.13" should be relaxed to ">=3.10" to align with the core vision-agents package.

agents-core/vision_agents/core/utils/logging.py (1)

7-7: Unconditional uvicorn import may cause import failures.

The import of AccessFormatter from uvicorn.logging will fail for users who install the package without uvicorn. Either add uvicorn to required dependencies in pyproject.toml, or wrap this import in a try/except with a fallback.

agents-core/vision_agents/core/runner/http/api.py (3)

32-32: Router-level lifespan is not executed when included in a FastAPI app.

The lifespan parameter on APIRouter is documented but not invoked when the router is included via app.include_router(). Move the lifespan context manager to the FastAPI() app initialization instead.


61-66: Avoid catching bare Exception.

Per coding guidelines, never write except Exception as e. Identify specific exceptions that launcher.start_session might raise (e.g., RuntimeError, ConnectionError) and handle them explicitly, or document why a broad catch is necessary for this WIP stage.


76-78: Inconsistent URL path naming: /session/ vs /sessions/.

This DELETE endpoint uses singular /session/{session_id} while other endpoints use plural /sessions/. This inconsistency may confuse API consumers.

♻️ Suggested fix
 `@router.delete`(
-    "/session/{session_id}",
+    "/sessions/{session_id}",
     summary="Close the agent session and remove it from call",
 )
agents-core/vision_agents/core/agents/agent_launcher.py (2)

192-201: Session removed before task cancellation completes.

The session is popped from _sessions before the task is cancelled. If cancellation is slow or fails, other code may assume the session is gone while its task is still running. Consider cancelling first, then removing.

♻️ Suggested reordering
     async def close_session(self, session_id: str, wait: bool = False) -> None:
-        session = self._sessions.pop(session_id, None)
+        session = self._sessions.get(session_id)
         if session is None:
             raise SessionNotFoundError(f"Session with id {session_id} not found")
 
         if wait:
             await cancel_and_wait(session.task)
         else:
             session.task.cancel()
+        
+        self._sessions.pop(session_id, None)

112-117: Bug: asyncio.as_completed yields awaitables, not completed tasks.

The loop iterates over awaitables from as_completed but calls .done() and .exception() on them without awaiting first. After as_completed yields, you must await the result to get the actual completed task/result.

🐛 Proposed fix using asyncio.gather
         coros = [cancel_and_wait(s.task) for s in self._sessions.values()]
-        async for result in cast(AsyncIterator[Task], asyncio.as_completed(coros)):
-            if result.done() and not result.cancelled() and result.exception():
-                logger.error(f"Failed to cancel the agent task: {result.exception()}")
+        results = await asyncio.gather(*coros, return_exceptions=True)
+        for result in results:
+            if isinstance(result, Exception) and not isinstance(result, asyncio.CancelledError):
+                logger.error(f"Failed to cancel the agent task: {result}")
🧹 Nitpick comments (1)
agents-core/vision_agents/core/runner/runner.py (1)

134-140: Avoid hasattr checks; prefer explicit type checks or try/except.

Per coding guidelines, avoid using hasattr. Consider using a protocol/interface check, or handle AttributeError explicitly if the edge type is uncertain.

♻️ Suggested approach using try/except
                 # Open demo UI by default
-                if (
-                    not no_demo
-                    and hasattr(agent, "edge")
-                    and hasattr(agent.edge, "open_demo_for_agent")
-                ):
-                    logger.info("🌐 Opening demo UI...")
-                    await agent.edge.open_demo_for_agent(agent, call_type, call_id)
+                if not no_demo:
+                    try:
+                        logger.info("🌐 Opening demo UI...")
+                        await agent.edge.open_demo_for_agent(agent, call_type, call_id)
+                    except AttributeError:
+                        logger.debug("Edge does not support open_demo_for_agent")
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e4a1ac9 and fac9abe.

⛔ Files ignored due to path filters (2)
  • examples/05_agent_server_example/uv.lock is excluded by !**/*.lock
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • agents-core/pyproject.toml
  • agents-core/vision_agents/core/__init__.py
  • agents-core/vision_agents/core/agents/agent_launcher.py
  • agents-core/vision_agents/core/agents/agents.py
  • agents-core/vision_agents/core/cli/cli_runner.py
  • agents-core/vision_agents/core/runner/__init__.py
  • agents-core/vision_agents/core/runner/http/__init__.py
  • agents-core/vision_agents/core/runner/http/api.py
  • agents-core/vision_agents/core/runner/http/models.py
  • agents-core/vision_agents/core/runner/runner.py
  • agents-core/vision_agents/core/utils/logging.py
  • examples/05_agent_server_example/README.md
  • examples/05_agent_server_example/__init__.py
  • examples/05_agent_server_example/agent_server_example.py
  • examples/05_agent_server_example/pyproject.toml
✅ Files skipped from review due to trivial changes (1)
  • examples/05_agent_server_example/init.py
🚧 Files skipped from review as they are similar to previous changes (6)
  • examples/05_agent_server_example/README.md
  • agents-core/vision_agents/core/runner/http/models.py
  • agents-core/vision_agents/core/cli/cli_runner.py
  • examples/05_agent_server_example/agent_server_example.py
  • agents-core/vision_agents/core/init.py
  • agents-core/pyproject.toml
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/python.mdc)

**/*.py: Never adjust sys.path in Python code
Never write except Exception as e - use specific exception handling
Avoid using getattr, hasattr, delattr and setattr; prefer normal attribute access in Python
Docstrings should follow the Google style guide for docstrings

Files:

  • agents-core/vision_agents/core/runner/http/api.py
  • agents-core/vision_agents/core/runner/__init__.py
  • agents-core/vision_agents/core/agents/agent_launcher.py
  • agents-core/vision_agents/core/agents/agents.py
  • agents-core/vision_agents/core/runner/runner.py
  • agents-core/vision_agents/core/utils/logging.py
🧠 Learnings (1)
📚 Learning: 2025-11-24T17:04:43.030Z
Learnt from: CR
Repo: GetStream/Vision-Agents PR: 0
File: .cursor/rules/python.mdc:0-0
Timestamp: 2025-11-24T17:04:43.030Z
Learning: Use uv for dependency management on this project, not python -m or system package managers

Applied to files:

  • examples/05_agent_server_example/pyproject.toml
🧬 Code graph analysis (5)
agents-core/vision_agents/core/runner/http/api.py (2)
agents-core/vision_agents/core/agents/agent_launcher.py (8)
  • SessionNotFoundError (26-26)
  • start (98-105)
  • start_session (164-190)
  • id (42-43)
  • close_session (192-201)
  • get_session (203-208)
  • warmed_up (142-143)
  • running (146-147)
agents-core/vision_agents/core/runner/http/models.py (3)
  • GetAgentSessionResponse (30-36)
  • JoinCallRequest (8-13)
  • JoinCallResponse (16-22)
agents-core/vision_agents/core/runner/__init__.py (1)
agents-core/vision_agents/core/runner/runner.py (1)
  • Runner (41-358)
agents-core/vision_agents/core/agents/agent_launcher.py (1)
agents-core/vision_agents/core/runner/http/api.py (2)
  • join_call (52-73)
  • close_session (80-96)
agents-core/vision_agents/core/agents/agents.py (1)
agents-core/vision_agents/core/agents/agent_launcher.py (1)
  • id (42-43)
agents-core/vision_agents/core/runner/runner.py (2)
agents-core/vision_agents/core/agents/agent_launcher.py (6)
  • AgentLauncher (53-284)
  • start (98-105)
  • launch (149-162)
  • start_session (164-190)
  • wait (45-49)
  • stop (107-118)
plugins/getstream/vision_agents/plugins/getstream/stream_edge_transport.py (1)
  • open_demo_for_agent (428-434)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: unit / Validate extra dependencies in "agents-core/pyproject.toml"
  • GitHub Check: unit / Mypy
  • GitHub Check: unit / Ruff
  • GitHub Check: unit / Test "not integration"
  • GitHub Check: unit / Mypy
  • GitHub Check: unit / Validate extra dependencies in "agents-core/pyproject.toml"
  • GitHub Check: unit / Ruff
  • GitHub Check: unit / Test "not integration"
🔇 Additional comments (19)
examples/05_agent_server_example/pyproject.toml (2)

7-15: Dependencies look appropriate for the example project.

The dependency set covers:

  • Environment configuration (python-dotenv)
  • Required vision-agents plugins and core
  • Profiling capability (pyinstrument)

This aligns well with an HTTP agent server demonstration.


17-22: Editable sources correctly configured for monorepo development.

The [tool.uv.sources] section properly references local packages via relative paths with editable = true, enabling seamless local development. This follows the project's convention of using uv for dependency management, as per learnings.

agents-core/vision_agents/core/agents/agents.py (2)

151-151: LGTM!

The UUID-based agent identifier is properly initialized during construction, providing a stable identity for session management throughout the agent lifecycle.


259-262: LGTM!

The read-only property cleanly exposes the internal _id field, aligning with the AgentSession.id property in agent_launcher.py that delegates to self.agent.id.

agents-core/vision_agents/core/utils/logging.py (3)

20-34: LGTM!

The extracted _get_colored_formatter helper consolidates formatter creation with a sensible color mapping, reducing duplication between SDK and FastAPI logger configuration.


36-56: LGTM!

The refactored configure_sdk_logger properly delegates to the new helper while preserving the existing handler-check and level-guard behavior.


59-96: LGTM!

The new configure_fastapi_loggers function provides consistent formatting for FastAPI/Uvicorn logs, with appropriate access log formatting that includes client address and request details.

agents-core/vision_agents/core/agents/agent_launcher.py (4)

26-50: LGTM!

The SessionNotFoundError and AgentSession dataclass provide a clean abstraction for session lifecycle management. The finished property and wait() method offer convenient ways to check and await session completion.


141-147: LGTM!

The warmed_up and running properties provide clean read-only access to launcher state, used by the HTTP API's /ready endpoint.


164-190: LGTM!

The start_session method orchestrates agent launch, optional video override, and task scheduling with proper cleanup callbacks. The closure capturing agent_id_ via default argument is a solid pattern to avoid late-binding issues.


203-208: LGTM!

The get_session method provides straightforward lookup with appropriate error handling.

agents-core/vision_agents/core/runner/http/api.py (4)

21-29: LGTM!

The lifespan context manager properly handles launcher lifecycle with try/finally ensuring stop() is called even on errors.


45-73: LGTM!

The join_call endpoint correctly creates sessions and returns appropriate response data. The 201 status code is appropriate for resource creation.


99-120: LGTM!

The beacon endpoint gracefully handles already-cleaned-up sessions by returning success, which is appropriate for fire-and-forget sendBeacon requests.


153-171: LGTM!

The health and readiness endpoints provide essential observability. The readiness check correctly validates both warmed_up and running states before accepting requests.

agents-core/vision_agents/core/runner/runner.py (3)

63-82: LGTM!

The Runner.__init__ provides flexible configuration with sensible defaults. The _fast_api_bypass flag enables advanced users to supply fully customized FastAPI instances.


170-217: LGTM!

The serve method properly configures CORS, logging, and uvicorn with log_config=None to prevent double-logging conflicts with the custom logger configuration.


244-358: LGTM!

The CLI implementation with Click provides a clean interface for both run and serve commands, with comprehensive options for debugging and configuration.

agents-core/vision_agents/core/runner/__init__.py (1)

1-1: LGTM!

The Runner as Runner pattern is a clean, explicit re-export—type checkers and IDEs will thank you for the clarity. This aligns well with the package-level exposure described in the summary.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@agents-core/vision_agents/core/runner/http/api.py`:
- Around line 155-166: Update the OpenAPI summary and the function docstring for
get_session_metrics to accurately describe that this endpoint returns metrics
for a running agent session (e.g., "Get metrics for a running agent session" or
similar); modify the summary parameter on the `@router.get` decorator and replace
the current docstring text in the get_session_metrics function to mention
metrics rather than general session info.
♻️ Duplicate comments (5)
agents-core/vision_agents/core/agents/agent_launcher.py (2)

193-203: Session removed before cancellation completes.

Popping the session from _sessions before cancellation finishes may lead to orphaned running tasks if cancellation is slow or fails. Other code might assume the session doesn't exist while it's still active.

♻️ Suggested improvement - cancel first, then remove
     async def close_session(self, session_id: str, wait: bool = False) -> None:
-        session = self._sessions.pop(session_id, None)
+        session = self._sessions.get(session_id)
         if session is None:
             raise SessionNotFoundError(f"Session with id {session_id} not found")
 
         logger.info(f"Closing agent session with id {session.id}")
         if wait:
             await cancel_and_wait(session.task)
         else:
             session.task.cancel()
+        # Remove after cancellation initiated
+        self._sessions.pop(session_id, None)

112-117: asyncio.as_completed yields awaitables, not completed results.

The items yielded by asyncio.as_completed must be awaited before inspecting. Calling result.done() and result.exception() on an unawaited coroutine won't behave as expected.

🐛 Proposed fix
-        coros = [cancel_and_wait(s.task) for s in self._sessions.values()]
-        async for result in cast(AsyncIterator[Task], asyncio.as_completed(coros)):
-            if result.done() and not result.cancelled() and result.exception():
-                logger.error(f"Failed to cancel the agent task: {result.exception()}")
+        coros = [cancel_and_wait(s.task) for s in self._sessions.values()]
+        results = await asyncio.gather(*coros, return_exceptions=True)
+        for result in results:
+            if isinstance(result, Exception) and not isinstance(result, asyncio.CancelledError):
+                logger.error(f"Failed to cancel the agent task: {result}")
agents-core/vision_agents/core/runner/http/api.py (3)

34-34: lifespan on APIRouter will not execute when router is mounted.

FastAPI only invokes lifespan context managers defined on the main FastAPI() app, not on included routers. Move the lifespan to the app initialization in your runner/main module.


63-68: Avoid catching bare Exception.

Per coding guidelines, never write except Exception as e. Identify the specific exceptions that launcher.start_session may raise and handle them explicitly.


78-80: Inconsistent URL path: /session/ vs /sessions/.

The DELETE endpoint uses singular /session/{session_id} while all other endpoints use plural /sessions/. Standardize to /sessions/{session_id} for consistency.

🧹 Nitpick comments (3)
agents-core/vision_agents/core/runner/http/models.py (1)

40-42: Docstring appears to be copy-pasted.

The docstring "Information about an active agent session" is identical to GetAgentSessionResponse. Consider updating it to reflect this model's specific purpose, e.g., "Metrics information for an active agent session."

agents-core/vision_agents/core/agents/agent_launcher.py (1)

63-64: Incomplete Coroutine type hint.

The join_call parameter uses Coroutine without type parameters. For completeness and type checker compatibility:

-        join_call: Callable[["Agent", str, str], Coroutine],
+        join_call: Callable[["Agent", str, str], Coroutine[Any, Any, None]],
agents-core/vision_agents/core/runner/http/api.py (1)

206-216: Use AgentLauncherDependency for consistency.

Other endpoints use the pre-defined AgentLauncherDependency, but this one uses Depends(_get_launcher) directly. Consider using the alias for consistency.

Additionally, HTTP 503 (Service Unavailable) is often more semantically appropriate for readiness probe failures than 400 (Bad Request).

🔧 Suggested fix
 `@router.get`("/ready")
-async def ready(launcher: AgentLauncher = Depends(_get_launcher)) -> Response:
+async def ready(launcher: AgentLauncher = AgentLauncherDependency) -> Response:
     """
     Check if the server is ready to spawn new agents.
     """
     if launcher.warmed_up and launcher.running:
         return Response(status_code=200)
     else:
         raise HTTPException(
-            status_code=400, detail="Server is not ready to accept requests"
+            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
+            detail="Server is not ready to accept requests",
         )
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between fac9abe and ed04598.

📒 Files selected for processing (6)
  • agents-core/vision_agents/core/agents/agent_launcher.py
  • agents-core/vision_agents/core/agents/agents.py
  • agents-core/vision_agents/core/observability/agent.py
  • agents-core/vision_agents/core/runner/http/api.py
  • agents-core/vision_agents/core/runner/http/models.py
  • tests/test_observability.py
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/python.mdc)

**/*.py: Never adjust sys.path in Python code
Never write except Exception as e - use specific exception handling
Avoid using getattr, hasattr, delattr and setattr; prefer normal attribute access in Python
Docstrings should follow the Google style guide for docstrings

Files:

  • agents-core/vision_agents/core/agents/agents.py
  • agents-core/vision_agents/core/observability/agent.py
  • tests/test_observability.py
  • agents-core/vision_agents/core/runner/http/api.py
  • agents-core/vision_agents/core/runner/http/models.py
  • agents-core/vision_agents/core/agents/agent_launcher.py
**/*test*.py

📄 CodeRabbit inference engine (.cursor/rules/python.mdc)

**/*test*.py: Never mock in tests; use pytest for testing
Mark integration tests with @pytest.mark.integration decorator
@pytest.mark.asyncio is not needed - it is automatic

Files:

  • tests/test_observability.py
🧬 Code graph analysis (3)
agents-core/vision_agents/core/agents/agents.py (2)
agents-core/vision_agents/core/observability/collector.py (1)
  • MetricsCollector (51-529)
agents-core/vision_agents/core/agents/agent_launcher.py (1)
  • id (42-43)
tests/test_observability.py (1)
agents-core/vision_agents/core/observability/agent.py (2)
  • AgentMetrics (52-169)
  • to_dict (149-169)
agents-core/vision_agents/core/agents/agent_launcher.py (3)
agents-core/vision_agents/core/agents/agents.py (2)
  • id (262-263)
  • set_video_track_override_path (911-919)
agents-core/vision_agents/core/runner/http/api.py (2)
  • join_call (54-75)
  • close_session (82-98)
agents-core/vision_agents/core/utils/utils.py (2)
  • cancel_and_wait (105-121)
  • await_or_run (92-102)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: unit / Test "not integration"
  • GitHub Check: unit / Mypy
  • GitHub Check: unit / Mypy
  • GitHub Check: unit / Test "not integration"
🔇 Additional comments (13)
agents-core/vision_agents/core/observability/agent.py (1)

149-169: LGTM! Clean implementation for metric serialization.

The method correctly converts the dataclass fields to a dictionary of metric values. The use of dataclasses.asdict() preserves the _Metric instances (since they're not dataclasses themselves), allowing the subsequent .value() calls to work correctly.

The docstring follows Google style guide conventions.

tests/test_observability.py (1)

421-437: LGTM! Good test coverage for the new to_dict method.

The tests appropriately cover:

  1. Extracting all fields (default behavior)
  2. Extracting a subset of fields
  3. Error handling for unknown fields

These tests avoid mocking and test the actual implementation directly, which aligns well with testing best practices. As per coding guidelines, no mocking is used here.

agents-core/vision_agents/core/agents/agents.py (2)

152-152: LGTM! Agent instance identifier initialization.

The unique _id provides a stable identifier for session tracking, distinct from the mutable agent_user.id. Good design choice.


259-263: LGTM! MetricsCollector and id property.

The MetricsCollector initialization enables per-agent metrics collection via event subscriptions. The id property correctly exposes the internal _id field as read-only.

agents-core/vision_agents/core/agents/agent_launcher.py (3)

26-26: LGTM! Clean exception definition.

A dedicated exception for session lookup failures provides clear error semantics.


29-50: LGTM! Well-structured session dataclass.

The AgentSession dataclass cleanly encapsulates session state with appropriate properties for checking completion status and exposing the agent's id.


164-191: LGTM! Session lifecycle management.

The start_session method properly:

  • Launches the agent
  • Sets up video track override if provided
  • Creates a managed task with cleanup callback
  • Tracks the session in _sessions

The done callback correctly captures agent.id at definition time.

agents-core/vision_agents/core/runner/http/models.py (1)

11-13: Remove or utilize the user_id field.

The user_id field is defined as required in JoinCallRequest (line 13) but is not passed to launcher.start_session() in the API handler (lines 53-74 of api.py). The field does not appear to be used elsewhere in the HTTP module. Either remove this field if it's not needed, or pass it to the launcher if it's required for session initialization.

agents-core/vision_agents/core/runner/http/api.py (5)

1-21: LGTM!

Imports are well-organized and the __all__ export is appropriately scoped to expose only the router.


37-44: LGTM!

Clean dependency injection pattern with a reusable AgentLauncherDependency.


101-122: LGTM!

The beacon endpoint correctly handles the fire-and-forget semantics by returning success even when the session is already gone. The warning log is appropriate for observability.


125-152: LGTM!

Clean implementation with proper specific exception handling for SessionNotFoundError.


198-203: LGTM!

Simple and effective health check endpoint.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@dangusev dangusev force-pushed the feature/agents-http-server branch from ed04598 to 3674e7e Compare January 16, 2026 11:34
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
agents-core/vision_agents/core/cli/cli_runner.py (1)

103-128: start_session already launches an agent—current flow spawns two.

You warm an agent and optionally open the demo UI on it, then start_session() creates a second agent to actually join the call.

🐛 Suggested fix
-                # Create the agent
-                agent = await launcher.launch()
-                if video_track_override:
-                    agent.set_video_track_override_path(video_track_override)
-
-                logger.info("✅ Agent warmed up and ready")
-
-                # Open demo UI by default
-                if (
-                    not no_demo
-                    and hasattr(agent, "edge")
-                    and hasattr(agent.edge, "open_demo_for_agent")
-                ):
-                    logger.info("🌐 Opening demo UI...")
-                    await agent.edge.open_demo_for_agent(agent, call_type, call_id)
-
-                # Join call if join_call function is provided
                 logger.info(f"📞 Joining call: {call_type}/{call_id}")
                 session = await launcher.start_session(
                     call_id, call_type, video_track_override_path=video_track_override
                 )
+                agent = session.agent
+
+                logger.info("✅ Agent warmed up and ready")
+
+                # Open demo UI by default
+                if (
+                    not no_demo
+                    and hasattr(agent, "edge")
+                    and hasattr(agent.edge, "open_demo_for_agent")
+                ):
+                    logger.info("🌐 Opening demo UI...")
+                    await agent.edge.open_demo_for_agent(agent, call_type, call_id)
+
                 await session.wait()
🤖 Fix all issues with AI agents
In `@agents-core/vision_agents/core/runner/runner.py`:
- Around line 125-147: The code currently calls await self._launcher.launch() to
create an agent and then calls await self._launcher.start_session(...), but
start_session already creates/launches its own agent, causing a double-spawn;
remove the explicit launch and use the agent returned by start_session (or
modify start_session to accept an existing agent) so the warmed/demo agent is
the one in-session. Concretely: keep await self._launcher.start() for warm-up,
delete/skip await self._launcher.launch(), call session = await
self._launcher.start_session(call_id, call_type, video_track_override_path=...),
then obtain the actual agent from the session (e.g. session.agent or
session.get_agent()) and call open_demo_for_agent on that agent (or update
start_session signature to accept the pre-warmed agent if needed); ensure
references to agent.edge.open_demo_for_agent and later session.wait() use the
session-provided agent.

In `@examples/05_agent_server_example/README.md`:
- Line 1: Replace the "TODO" in examples/05_agent_server_example/README.md with
a minimal usage README that covers: a short setup section (e.g., install
dependencies with npm/yarn and build steps), a clear list of required
environment variables with example values (names and purpose) for the agent
server in this example (mention the example directory name
05_agent_server_example and any expected keys like API keys, PORT, or DB_URL),
and a basic run command that users can copy (e.g., how to start the example with
npm run start or node <entry-file> and how to set env vars inline). Keep it
concise so someone can clone the repo, set the env vars, install deps, and run
the example.
♻️ Duplicate comments (8)
examples/05_agent_server_example/pyproject.toml (1)

4-4: Relax the Python requirement to match core package support.

Line 4 likely over-restricts the example; align it with the core package’s supported range.

♻️ Proposed change
-requires-python = ">=3.13"
+requires-python = ">=3.10"
agents-core/vision_agents/core/agents/agent_launcher.py (2)

107-116: stop() misuses asyncio.as_completed.

as_completed returns awaitables from a sync iterator; the current async for + .done() path won’t work as intended and can skip cancellation failures.

🐛 Proposed fix
-        coros = [cancel_and_wait(s.task) for s in self._sessions.values()]
-        async for result in cast(AsyncIterator[Task], asyncio.as_completed(coros)):
-            if result.done() and not result.cancelled() and result.exception():
-                logger.error(f"Failed to cancel the agent task: {result.exception()}")
+        coros = [cancel_and_wait(s.task) for s in self._sessions.values()]
+        results = await asyncio.gather(*coros, return_exceptions=True)
+        for result in results:
+            if isinstance(result, Exception) and not isinstance(
+                result, asyncio.CancelledError
+            ):
+                logger.error(f"Failed to cancel the agent task: {result}")

193-203: Cancel before removing session from registry.

Popping first can leave a running task untracked if cancellation is slow or fails.

🛠️ Suggested adjustment
-        session = self._sessions.pop(session_id, None)
+        session = self._sessions.get(session_id)
         if session is None:
             raise SessionNotFoundError(f"Session with id {session_id} not found")
 
         logger.info(f"Closing agent session with id {session.id}")
-        if wait:
-            await cancel_and_wait(session.task)
-        else:
-            session.task.cancel()
+        try:
+            if wait:
+                await cancel_and_wait(session.task)
+            else:
+                session.task.cancel()
+        finally:
+            self._sessions.pop(session_id, None)
agents-core/vision_agents/core/runner/http/api.py (4)

34-34: Router lifespan won’t run when mounted.

APIRouter(lifespan=...) isn’t executed when included into a FastAPI app; move lifespan to the app.

🛠️ Minimal change here
-router = APIRouter(lifespan=lifespan)
+router = APIRouter()

59-68: Avoid broad except Exception in join_call.

Catch specific launcher/session errors or let FastAPI’s default 500 handler surface unexpected failures. As per coding guidelines, ...


78-80: Use consistent plural resource path.

🛠️ Suggested fix
 `@router.delete`(
-    "/session/{session_id}",
+    "/sessions/{session_id}",
     summary="Close the agent session and remove it from call",
 )

155-166: Metrics endpoint summary/docstring mismatch.

📝 Suggested fix
 `@router.get`(
     "/sessions/{session_id}/metrics",
     response_model=GetAgentSessionMetricsResponse,
-    summary="Get info about a running agent session",
+    summary="Get metrics for a running agent session",
 )
@@
-    """
-    Get info about a running agent session.
-    """
+    """Get metrics for a running agent session."""
agents-core/vision_agents/core/runner/runner.py (1)

150-152: Don’t catch bare Exception in run().

Prefer explicit exception types or let unexpected errors propagate. Based on learnings, ...

🧹 Nitpick comments (3)
agents-core/vision_agents/core/runner/http/models.py (1)

3-6: Type config payloads for clearer API contracts.

Untyped dict weakens OpenAPI and validation. Consider a typed mapping (even dict[str, Any]) plus a Field description.

♻️ Suggested refactor
 from datetime import datetime
+from typing import Any
 
 from pydantic import BaseModel, Field
@@
 class JoinCallResponse(BaseModel):
@@
-    config: dict  # TODO: Make it a type
+    config: dict[str, Any] = Field(..., description="Agent configuration")
@@
 class GetAgentSessionResponse(BaseModel):
@@
-    config: dict  # TODO: Make it a type
+    config: dict[str, Any] = Field(..., description="Agent configuration")

Also applies to: 19-22, 34-37

agents-core/vision_agents/core/runner/runner.py (2)

20-21: Replace getattr log-level lookup with explicit mapping.

Avoid dynamic attribute access; a small map keeps it explicit and typed. As per coding guidelines, ...

♻️ Suggested refactor
 logger = logging.getLogger(__name__)
+LOG_LEVELS = {
+    "DEBUG": logging.DEBUG,
+    "INFO": logging.INFO,
+    "WARNING": logging.WARNING,
+    "ERROR": logging.ERROR,
+    "CRITICAL": logging.CRITICAL,
+}
@@
-        numeric_level = getattr(logging, log_level.upper(), logging.INFO)
+        numeric_level = LOG_LEVELS.get(log_level.upper(), logging.INFO)
@@
-        configure_sdk_logger(
-            level=getattr(logging, agents_log_level.upper(), logging.INFO)
-        )
+        configure_sdk_logger(
+            level=LOG_LEVELS.get(agents_log_level.upper(), logging.INFO)
+        )
         configure_fastapi_loggers(
-            level=getattr(logging, http_log_level.upper(), logging.INFO)
+            level=LOG_LEVELS.get(http_log_level.upper(), logging.INFO)
         )

Also applies to: 108-109, 205-209


134-138: Avoid hasattr checks; use EAFP access instead.

This keeps the control flow explicit and aligns with the no-hasattr guideline. As per coding guidelines, ...

♻️ Suggested refactor
-                if (
-                    not no_demo
-                    and hasattr(agent, "edge")
-                    and hasattr(agent.edge, "open_demo_for_agent")
-                ):
-                    logger.info("🌐 Opening demo UI...")
-                    await agent.edge.open_demo_for_agent(agent, call_type, call_id)
+                if not no_demo:
+                    try:
+                        logger.info("🌐 Opening demo UI...")
+                        await agent.edge.open_demo_for_agent(agent, call_type, call_id)
+                    except AttributeError:
+                        pass
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ed04598 and 3674e7e.

⛔ Files ignored due to path filters (2)
  • examples/05_agent_server_example/uv.lock is excluded by !**/*.lock
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • agents-core/pyproject.toml
  • agents-core/vision_agents/core/__init__.py
  • agents-core/vision_agents/core/agents/agent_launcher.py
  • agents-core/vision_agents/core/agents/agents.py
  • agents-core/vision_agents/core/cli/cli_runner.py
  • agents-core/vision_agents/core/observability/agent.py
  • agents-core/vision_agents/core/runner/__init__.py
  • agents-core/vision_agents/core/runner/http/__init__.py
  • agents-core/vision_agents/core/runner/http/api.py
  • agents-core/vision_agents/core/runner/http/models.py
  • agents-core/vision_agents/core/runner/runner.py
  • agents-core/vision_agents/core/utils/logging.py
  • examples/05_agent_server_example/README.md
  • examples/05_agent_server_example/__init__.py
  • examples/05_agent_server_example/agent_server_example.py
  • examples/05_agent_server_example/pyproject.toml
  • tests/test_observability.py
✅ Files skipped from review due to trivial changes (1)
  • examples/05_agent_server_example/init.py
🚧 Files skipped from review as they are similar to previous changes (7)
  • agents-core/vision_agents/core/init.py
  • agents-core/vision_agents/core/observability/agent.py
  • agents-core/vision_agents/core/agents/agents.py
  • examples/05_agent_server_example/agent_server_example.py
  • agents-core/pyproject.toml
  • agents-core/vision_agents/core/runner/init.py
  • agents-core/vision_agents/core/utils/logging.py
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/python.mdc)

**/*.py: Never adjust sys.path in Python code
Never write except Exception as e - use specific exception handling
Avoid using getattr, hasattr, delattr and setattr; prefer normal attribute access in Python
Docstrings should follow the Google style guide for docstrings

Files:

  • agents-core/vision_agents/core/cli/cli_runner.py
  • agents-core/vision_agents/core/runner/runner.py
  • tests/test_observability.py
  • agents-core/vision_agents/core/runner/http/models.py
  • agents-core/vision_agents/core/runner/http/api.py
  • agents-core/vision_agents/core/agents/agent_launcher.py
**/*test*.py

📄 CodeRabbit inference engine (.cursor/rules/python.mdc)

**/*test*.py: Never mock in tests; use pytest for testing
Mark integration tests with @pytest.mark.integration decorator
@pytest.mark.asyncio is not needed - it is automatic

Files:

  • tests/test_observability.py
🧠 Learnings (2)
📚 Learning: 2025-11-24T17:04:43.030Z
Learnt from: CR
Repo: GetStream/Vision-Agents PR: 0
File: .cursor/rules/python.mdc:0-0
Timestamp: 2025-11-24T17:04:43.030Z
Learning: Use uv for dependency management on this project, not python -m or system package managers

Applied to files:

  • examples/05_agent_server_example/pyproject.toml
📚 Learning: 2025-11-24T17:04:43.030Z
Learnt from: CR
Repo: GetStream/Vision-Agents PR: 0
File: .cursor/rules/python.mdc:0-0
Timestamp: 2025-11-24T17:04:43.030Z
Learning: Applies to **/*.py : Never write `except Exception as e` - use specific exception handling

Applied to files:

  • agents-core/vision_agents/core/runner/runner.py
🧬 Code graph analysis (5)
agents-core/vision_agents/core/cli/cli_runner.py (1)
agents-core/vision_agents/core/agents/agent_launcher.py (2)
  • start_session (164-191)
  • wait (45-49)
tests/test_observability.py (1)
agents-core/vision_agents/core/observability/agent.py (2)
  • AgentMetrics (52-169)
  • to_dict (149-169)
agents-core/vision_agents/core/runner/http/models.py (2)
agents-core/vision_agents/core/observability/agent.py (1)
  • description (12-13)
agents-core/vision_agents/core/agents/agents.py (1)
  • metrics (1403-1404)
agents-core/vision_agents/core/runner/http/api.py (1)
agents-core/vision_agents/core/agents/agent_launcher.py (5)
  • SessionNotFoundError (26-26)
  • id (42-43)
  • close_session (193-203)
  • get_session (205-210)
  • running (146-147)
agents-core/vision_agents/core/agents/agent_launcher.py (2)
agents-core/vision_agents/core/runner/http/api.py (2)
  • join_call (54-75)
  • close_session (82-98)
agents-core/vision_agents/core/utils/utils.py (2)
  • cancel_and_wait (105-121)
  • await_or_run (92-102)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: unit / Validate extra dependencies in "agents-core/pyproject.toml"
  • GitHub Check: unit / Test "not integration"
  • GitHub Check: unit / Ruff
  • GitHub Check: unit / Mypy
  • GitHub Check: unit / Mypy
  • GitHub Check: unit / Validate extra dependencies in "agents-core/pyproject.toml"
  • GitHub Check: unit / Test "not integration"
  • GitHub Check: unit / Ruff
🔇 Additional comments (9)
tests/test_observability.py (4)

8-8: LGTM!

Standard library import added to support dataclasses.fields() usage in the new test class.


422-426: LGTM!

Test correctly verifies that to_dict() returns all dataclass fields as keys. The use of dataclasses.fields() to dynamically get expected field names ensures the test stays in sync with the implementation.


428-432: LGTM!

Test correctly verifies that the fields parameter filters the output to only the specified metrics.


434-437: LGTM!

Test correctly verifies that ValueError is raised with the expected message when an unknown field is requested.

agents-core/vision_agents/core/runner/http/models.py (3)

8-13: JoinCallRequest schema looks solid.

Clear required fields and sensible defaults.


25-28: ErrorResponse is concise and consistent.


40-53: Metrics response schema is well-structured.

agents-core/vision_agents/core/agents/agent_launcher.py (2)

29-49: AgentSession wrapper is clean and minimal.


164-191: Session start lifecycle is coherent.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@agents-core/vision_agents/core/agents/agent_launcher.py`:
- Around line 196-207: The close_session method currently pops from
self._sessions and silently returns when the session is missing; change it to
raise SessionNotFoundError instead of returning so callers (e.g., the HTTP
handler expecting a 404) get the correct error. Specifically, in
agent_launcher.py inside close_session(session_id: str, wait: bool = False),
replace the early return on session is None with raising
SessionNotFoundError(session_id) (or the project’s SessionNotFoundError
constructor), and keep the existing logging and cancel/wait logic for existing
sessions (using self._sessions, session.task, and session.id as already
referenced).

In `@agents-core/vision_agents/core/runner/runner.py`:
- Around line 70-77: The Runner class docstring is stale: the __init__ Args
lists a non-existent run_options parameter; update the Runner.__init__ docstring
to match the actual constructor signature (remove the run_options entry and any
references to it) and ensure the Args section only documents the real parameters
(e.g., launcher and serve_options) and their types/behavior so the docstring for
Runner.__init__ is accurate.
- Around line 211-214: In _create_fastapi_app, app.state.options is incorrectly
set from self._serve_options instead of the method parameter; update the
assignment in the _create_fastapi_app function to use the local options
parameter (replace uses of self._serve_options when setting app.state.options)
so the FastAPI app receives the passed-in ServeOptions instance and not the
instance variable.
♻️ Duplicate comments (6)
agents-core/vision_agents/core/runner/runner.py (2)

157-159: Avoid catching bare Exception.

Per coding guidelines, use specific exception handling. Identify the exceptions that launcher.start_session and launcher.launch may raise (e.g., RuntimeError, asyncio.CancelledError, or custom exceptions) and handle them explicitly.


132-154: start_session already creates an agent—avoid double-spawn.

The flow calls self._launcher.launch() (line 136), then self._launcher.start_session() (line 151), which internally launches another agent. The warmed/demo agent is discarded. Use the session's agent for demo UI instead.

agents-core/vision_agents/core/runner/http/api.py (3)

66-71: Avoid catching bare Exception.

Per coding guidelines, use specific exception handling. Catch the specific exceptions that launcher.start_session may raise.


81-84: Inconsistent URL path: /session/ vs /sessions/.

The DELETE endpoint uses singular /session/{session_id} while all other session endpoints use plural /sessions/. Standardize to /sessions/{session_id} for consistency.

📝 Suggested fix
 `@router.delete`(
-    "/session/{session_id}",
+    "/sessions/{session_id}",
     summary="Close the agent session and remove it from call",
     dependencies=[Depends(can_close_session)],
 )

159-171: Summary and docstring don't mention metrics.

Both the summary and docstring describe "Get info about a running agent session" rather than metrics. Update to accurately describe the endpoint's purpose.

📝 Suggested fix
 `@router.get`(
     "/sessions/{session_id}/metrics",
     response_model=GetAgentSessionMetricsResponse,
-    summary="Get info about a running agent session",
+    summary="Get metrics for a running agent session",
     dependencies=[Depends(can_view_metrics)],
 )
 async def get_session_metrics(
     session_id: str,
     session: Optional[AgentSession] = Depends(get_session),
 ) -> GetAgentSessionMetricsResponse:
-    """
-    Get info about a running agent session.
-    """
+    """Get metrics for a running agent session."""
agents-core/vision_agents/core/agents/agent_launcher.py (1)

114-117: Critical bug: Incorrect use of asyncio.as_completed and result inspection.

Several issues compound here:

  1. asyncio.as_completed returns a regular iterator of awaitables, not an AsyncIterator - the cast is incorrect.
  2. The result yielded by as_completed is an awaitable that must be awaited to get the actual result.
  3. cancel_and_wait returns None, so calling .done(), .cancelled(), .exception() on the awaited result will fail.
🐛 Proposed fix using `asyncio.gather`
         coros = [cancel_and_wait(s.task) for s in self._sessions.values()]
-        async for result in cast(AsyncIterator[Task], asyncio.as_completed(coros)):
-            if result.done() and not result.cancelled() and result.exception():
-                logger.error(f"Failed to cancel the agent task: {result.exception()}")
+        results = await asyncio.gather(*coros, return_exceptions=True)
+        for result in results:
+            if isinstance(result, Exception) and not isinstance(result, asyncio.CancelledError):
+                logger.error(f"Failed to cancel the agent task: {result}")

You can also remove the unused AsyncIterator import from line 10 after this fix.

🧹 Nitpick comments (6)
agents-core/vision_agents/core/runner/http/dependencies.py (1)

25-33: Consider guarding against missing app.state attributes.

If get_launcher or get_options is called before the FastAPI app is fully initialized (e.g., during testing or misconfiguration), accessing request.app.state.launcher or request.app.state.options will raise AttributeError. A defensive check or clearer error message would improve debuggability.

♻️ Optional defensive check
 def get_launcher(request: Request) -> AgentLauncher:
     """
     Get an agent launcher from the FastAPI app
     """
+    if not hasattr(request.app.state, "launcher"):
+        raise RuntimeError("AgentLauncher not initialized in app.state")
     return request.app.state.launcher
agents-core/vision_agents/core/runner/runner.py (1)

114-116: Prefer logging.getLevelName() over getattr.

Per coding guidelines, avoid getattr. Use logging.getLevelName() or a dictionary mapping for log level resolution.

♻️ Suggested fix
-        numeric_level = getattr(logging, log_level.upper(), logging.INFO)
+        numeric_level = logging.getLevelName(log_level.upper())
+        if not isinstance(numeric_level, int):
+            numeric_level = logging.INFO

Based on coding guidelines, avoid using getattr.

agents-core/vision_agents/core/runner/http/api.py (1)

209-219: Consider HTTP 503 for not-ready state.

When the server isn't ready to accept requests, HTTP 503 (Service Unavailable) is more semantically appropriate than 400 (Bad Request). Load balancers and orchestrators typically interpret 503 as a transient condition.

♻️ Suggested change
     if launcher.warmed_up and launcher.running:
         return Response(status_code=200)
     else:
         raise HTTPException(
-            status_code=400, detail="Server is not ready to accept requests"
+            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
+            detail="Server is not ready to accept requests"
         )
agents-core/vision_agents/core/runner/http/options.py (1)

15-55: Add model_config to allow arbitrary types in Pydantic validation.

FastAPI instances cannot be validated by Pydantic v2 without arbitrary_types_allowed=True. While the documented feature (passing a custom fast_api instance) currently doesn't manifest in the codebase, it will fail if actually used.

♻️ Suggested fix
+from pydantic import ConfigDict
+
 class ServeOptions(pydantic_settings.BaseSettings):
     """
     A collection of configuration options for the "serve" mode.
     ...
     """
+    model_config = ConfigDict(arbitrary_types_allowed=True)
+
     fast_api: Optional[FastAPI] = None
agents-core/vision_agents/core/agents/agent_launcher.py (2)

46-50: Consider adding a return type annotation to wait().

The method awaits a task but lacks a return type hint.

✏️ Suggested fix
-    async def wait(self):
+    async def wait(self) -> None:
         """
         Wait for the session task to finish running.
         """
         return await self.task

65-65: Incomplete Coroutine type parameters on join_call.

The Coroutine generic requires three type parameters [T_co, T_contra, V_co]. Currently unspecified, which may cause type checker warnings.

✏️ Suggested fix
-        join_call: Callable[["Agent", str, str], Coroutine],
+        join_call: Callable[["Agent", str, str], Coroutine[Any, Any, None]],
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3674e7e and e87c30a.

📒 Files selected for processing (7)
  • agents-core/vision_agents/core/__init__.py
  • agents-core/vision_agents/core/agents/agent_launcher.py
  • agents-core/vision_agents/core/runner/__init__.py
  • agents-core/vision_agents/core/runner/http/api.py
  • agents-core/vision_agents/core/runner/http/dependencies.py
  • agents-core/vision_agents/core/runner/http/options.py
  • agents-core/vision_agents/core/runner/runner.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • agents-core/vision_agents/core/runner/init.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/python.mdc)

**/*.py: Never adjust sys.path in Python code
Never write except Exception as e - use specific exception handling
Avoid using getattr, hasattr, delattr and setattr; prefer normal attribute access in Python
Docstrings should follow the Google style guide for docstrings

Files:

  • agents-core/vision_agents/core/__init__.py
  • agents-core/vision_agents/core/runner/http/options.py
  • agents-core/vision_agents/core/runner/http/api.py
  • agents-core/vision_agents/core/runner/http/dependencies.py
  • agents-core/vision_agents/core/runner/runner.py
  • agents-core/vision_agents/core/agents/agent_launcher.py
🧠 Learnings (1)
📚 Learning: 2025-11-24T17:04:43.030Z
Learnt from: CR
Repo: GetStream/Vision-Agents PR: 0
File: .cursor/rules/python.mdc:0-0
Timestamp: 2025-11-24T17:04:43.030Z
Learning: Applies to **/*.py : Never write `except Exception as e` - use specific exception handling

Applied to files:

  • agents-core/vision_agents/core/runner/runner.py
🧬 Code graph analysis (5)
agents-core/vision_agents/core/__init__.py (6)
agents-core/vision_agents/core/agents/agent_launcher.py (2)
  • AgentLauncher (54-287)
  • AgentSession (30-50)
agents-core/vision_agents/core/runner/runner.py (2)
  • cli (234-348)
  • Runner (43-348)
agents-core/vision_agents/core/cli/cli_runner.py (1)
  • cli (25-152)
dev.py (1)
  • cli (50-53)
agents-core/vision_agents/core/edge/types.py (1)
  • User (15-18)
agents-core/vision_agents/core/runner/http/options.py (1)
  • ServeOptions (15-55)
agents-core/vision_agents/core/runner/http/api.py (3)
agents-core/vision_agents/core/agents/agent_launcher.py (10)
  • AgentLauncher (54-287)
  • AgentSession (30-50)
  • SessionNotFoundError (26-26)
  • get_session (209-211)
  • start (99-106)
  • stop (108-119)
  • start_session (165-194)
  • id (43-44)
  • warmed_up (143-144)
  • running (147-148)
agents-core/vision_agents/core/runner/http/dependencies.py (6)
  • can_start_session (13-13)
  • can_view_metrics (22-22)
  • can_view_session (19-19)
  • get_current_user (9-10)
  • get_launcher (25-29)
  • get_session (36-39)
agents-core/vision_agents/core/runner/http/models.py (2)
  • GetAgentSessionMetricsResponse (40-53)
  • GetAgentSessionResponse (31-37)
agents-core/vision_agents/core/runner/http/dependencies.py (1)
agents-core/vision_agents/core/agents/agent_launcher.py (3)
  • AgentLauncher (54-287)
  • AgentSession (30-50)
  • get_session (209-211)
agents-core/vision_agents/core/runner/runner.py (6)
agents-core/vision_agents/core/agents/agent_launcher.py (6)
  • AgentLauncher (54-287)
  • start (99-106)
  • launch (150-163)
  • start_session (165-194)
  • wait (46-50)
  • stop (108-119)
agents-core/vision_agents/core/runner/http/api.py (2)
  • lifespan (34-41)
  • start_session (55-78)
agents-core/vision_agents/core/runner/http/dependencies.py (5)
  • can_close_session (16-16)
  • can_start_session (13-13)
  • can_view_metrics (22-22)
  • can_view_session (19-19)
  • get_current_user (9-10)
agents-core/vision_agents/core/runner/http/options.py (1)
  • ServeOptions (15-55)
agents-core/vision_agents/core/edge/sfu_events.py (1)
  • level (496-500)
plugins/getstream/vision_agents/plugins/getstream/stream_edge_transport.py (1)
  • open_demo_for_agent (428-434)
agents-core/vision_agents/core/agents/agent_launcher.py (3)
agents-core/vision_agents/core/utils/utils.py (2)
  • cancel_and_wait (105-121)
  • await_or_run (92-102)
agents-core/vision_agents/core/runner/http/api.py (2)
  • start_session (55-78)
  • close_session (86-102)
agents-core/vision_agents/core/runner/http/dependencies.py (1)
  • get_session (36-39)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: unit / Test "not integration"
  • GitHub Check: unit / Mypy
  • GitHub Check: unit / Mypy
  • GitHub Check: unit / Test "not integration"
🔇 Additional comments (10)
agents-core/vision_agents/core/__init__.py (1)

1-15: LGTM!

The public API surface is well-defined. Exports are cleanly organized, and __all__ correctly enumerates all symbols intended for public consumption.

agents-core/vision_agents/core/runner/http/options.py (1)

7-12: LGTM!

These no-op stubs serve as sensible defaults for permission callbacks, allowing all operations when no custom authorization is configured.

agents-core/vision_agents/core/runner/http/dependencies.py (1)

36-39: LGTM!

The get_session dependency cleanly composes with get_launcher, properly returning Optional[AgentSession] to allow callers to handle missing sessions.

agents-core/vision_agents/core/runner/runner.py (1)

234-348: LGTM!

The CLI is well-structured with clear option definitions and proper delegation to run() and serve() methods.

agents-core/vision_agents/core/runner/http/api.py (2)

33-41: LGTM!

The lifespan context manager properly orchestrates launcher startup and shutdown, ensuring clean resource management.


130-156: LGTM!

The get_session_info endpoint correctly handles the optional session dependency and returns a well-structured response.

agents-core/vision_agents/core/agents/agent_launcher.py (4)

26-26: LGTM!

The SessionNotFoundError exception is cleanly defined for session lookup failures.


142-148: LGTM!

The warmed_up and running properties provide clean read-only access to the launcher's state.


165-194: LGTM!

The session lifecycle is well-managed: agent is launched, task is created with a cleanup callback, and session is properly tracked. The done callback correctly captures agent.id by value using the default argument pattern.


209-211: LGTM!

Simple and correct session lookup. The TODO comment for testing is noted.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@agents-core/vision_agents/core/runner/http/api.py`:
- Around line 81-96: The DELETE endpoint always returns 204 because
AgentLauncher.close_session silently returns when a session does not exist;
update behavior so missing sessions produce 404: either modify
AgentLauncher.close_session to raise a clear exception (e.g., SessionNotFound)
when session_id isn't found, or change the router.close_session handler to check
the result of launcher.close_session (e.g., have it return a boolean) and raise
fastapi.HTTPException(status_code=404) when false/None; reference the
close_session endpoint function and the AgentLauncher.close_session method to
locate the changes and ensure non-existent sessions result in a 404 response.

In `@agents-core/vision_agents/core/runner/http/models.py`:
- Around line 39-40: Update the class docstring for
GetAgentSessionMetricsResponse to accurately reflect its purpose: replace
"Information about an active agent session." with "Metrics information for an
active agent session." so the documentation matches the model's intent.

In `@agents-core/vision_agents/core/runner/runner.py`:
- Around line 139-142: The conditional uses hasattr checks for agent.edge and
agent.edge.open_demo_for_agent; replace this with direct attribute access
guarded by a try/except or an explicit type/interface check: try to call or
reference agent.edge.open_demo_for_agent and catch AttributeError (or check
isinstance(agent.edge, ExpectedEdgeClass) or implement an Edge protocol) so the
code relies on the declared interface instead of hasattr. Update the block
around no_demo, agent, agent.edge and open_demo_for_agent to either perform a
try/except AttributeError when accessing agent.edge.open_demo_for_agent or
validate agent.edge against the concrete Edge class/protocol before calling
open_demo_for_agent.

In `@tests/test_agents/test_runner.py`:
- Line 3: Replace the unittest.mock MagicMock usage for stream_edge_mock with a
concrete fake or simple stub class: remove the import "from unittest.mock import
MagicMock", implement a lightweight FakeStreamEdge (or similar) inside the test
that implements the expected interface/attributes used by the tests, and
instantiate that fake in place of MagicMock wherever stream_edge_mock was used
(also replacing usages around the earlier occurrences noted at the other
occurrence spots). Ensure the fake provides the minimal behavior the tests rely
on (methods/attributes referenced in test_runner.py) so you can delete the
MagicMock import and any mock-specific assertions.
♻️ Duplicate comments (8)
agents-core/vision_agents/core/runner/http/api.py (2)

66-71: Avoid catching bare Exception.

Per coding guidelines, never write except Exception as e. Identify the specific exceptions that launcher.start_session might raise and handle them explicitly. Based on learnings, this is a violation of project coding standards.


147-159: Copy-paste artifact: summary and docstring don't mention metrics.

Both the summary (line 150) and the docstring (lines 157-159) describe "Get info about a running agent session" rather than metrics. Update them to reflect the actual purpose of this endpoint.

agents-core/vision_agents/core/agents/agent_launcher.py (2)

197-201: API contract mismatch: close_session silently returns instead of raising an error.

This was flagged in a prior review. The HTTP API may expect an exception for non-existent sessions to return a proper 404 response. Currently, all close requests return success regardless of session existence.


111-114: asyncio.as_completed usage issue persists.

The loop iterates over awaitables from asyncio.as_completed but calls .done(), .cancelled(), and .exception() on them. After as_completed yields and you await, result.done() is always True. Additionally, calling .exception() on a cancelled task raises CancelledError.

🐛 Proposed fix using asyncio.gather
         coros = [cancel_and_wait(s.task) for s in self._sessions.values()]
-        async for result in cast(AsyncIterator[Task], asyncio.as_completed(coros)):
-            if result.done() and not result.cancelled() and result.exception():
-                logger.error(f"Failed to cancel the agent task: {result.exception()}")
+        results = await asyncio.gather(*coros, return_exceptions=True)
+        for result in results:
+            if isinstance(result, Exception) and not isinstance(result, asyncio.CancelledError):
+                logger.error(f"Failed to cancel the agent task: {result}")
agents-core/vision_agents/core/runner/runner.py (4)

155-157: Avoid catching bare Exception.

Per coding guidelines, never write except Exception as e. Identify specific exceptions that the agent session flow might raise and handle them explicitly. Based on learnings, this is a violation of project coding standards.


133-152: start_session already creates an agent—avoid double-spawn.

launch() at line 134 creates an agent, then start_session() at line 149 launches a second one. The warmed/demo agent from launch() is not the one in-session. Either remove the explicit launch() call and use the agent from session.agent, or modify start_session to accept an existing agent.


68-75: Stale docstring: run_options parameter doesn't exist.

The docstring mentions run_options: instance of RunOptions but this parameter isn't present in the constructor signature.


209-212: Inconsistent: uses self._serve_options instead of options parameter.

_create_fastapi_app accepts an options parameter but line 212 assigns self._serve_options to app.state.options, bypassing the parameter.

🧹 Nitpick comments (7)
agents-core/vision_agents/core/runner/http/models.py (1)

30-36: Consider adding Field descriptions for consistency.

GetAgentSessionResponse lacks Field() descriptions while GetAgentSessionMetricsResponse and other models use them. For API documentation consistency, consider adding descriptions.

♻️ Optional enhancement
 class GetAgentSessionResponse(BaseModel):
     """Information about an active agent session."""

-    session_id: str
-    call_id: str
-    config: dict  # TODO: Make it a type
-    session_started_at: datetime
+    session_id: str = Field(..., description="The ID of the agent session")
+    call_id: str = Field(..., description="The ID of the call")
+    config: dict = Field(..., description="Session configuration")  # TODO: Make it a type
+    session_started_at: datetime = Field(..., description="Date and time in UTC at which the session was started")
agents-core/vision_agents/core/runner/http/api.py (1)

197-207: Consider using 503 Service Unavailable instead of 400.

A 400 Bad Request implies the client sent an invalid request. When the server isn't ready, 503 Service Unavailable is more semantically appropriate for readiness probes and aligns with Kubernetes conventions.

♻️ Suggested fix
 `@router.get`("/ready")
 async def ready(launcher: AgentLauncher = Depends(get_launcher)) -> Response:
     """
     Check if the server is ready to spawn new agents.
     """
     if launcher.ready:
         return Response(status_code=200)
     else:
         raise HTTPException(
-            status_code=400, detail="Server is not ready to accept requests"
+            status_code=status.HTTP_503_SERVICE_UNAVAILABLE, 
+            detail="Server is not ready to accept requests"
         )
agents-core/vision_agents/core/agents/agent_launcher.py (2)

43-47: Missing return type annotation on wait() method.

The wait() method should have a return type annotation for clarity. Since it awaits the task, the return type depends on what the task returns.

♻️ Suggested fix
-    async def wait(self):
+    async def wait(self) -> None:
         """
         Wait for the session task to finish running.
         """
         return await self.task

61-62: Incomplete Coroutine type hint.

Coroutine without type parameters is incomplete. For better type checking, specify Coroutine[Any, Any, None] (assuming the coroutine returns None).

♻️ Suggested fix
-        join_call: Callable[["Agent", str, str], Coroutine],
+        join_call: Callable[["Agent", str, str], Coroutine[Any, Any, None]],
agents-core/vision_agents/core/runner/runner.py (1)

182-193: Incomplete docstring: Args lack descriptions.

Per coding guidelines, docstrings should follow the Google style guide. The Args section lists parameters without descriptions.

📝 Suggested improvement
         """
         Start the HTTP server that spawns agents to the calls.

         Args:
-            host:
-            port:
-            agents_log_level:
-            http_log_level:
-
-        Returns:
-
+            host: Server hostname to bind to.
+            port: Server port to bind to.
+            agents_log_level: Logging level for agent-related logs.
+            http_log_level: Logging level for FastAPI and uvicorn.
         """
tests/test_agents/test_runner.py (2)

83-100: Tests bypass LifespanManager, skipping lifespan startup/shutdown.

Tests like test_start_session_success create their own AsyncClient without LifespanManager. This means launcher.start() and launcher.stop() from the lifespan won't be called, and the launcher won't be warmed up. Consider whether this is intentional for isolation or if it could mask lifespan-related bugs.

♻️ Consider using LifespanManager for full integration
     async def test_start_session_success(self, agent_launcher) -> None:
         runner = Runner(launcher=agent_launcher)

-        async with AsyncClient(
-            transport=ASGITransport(app=runner.fast_api),
-            base_url="http://test",
-        ) as client:
+        async with LifespanManager(runner.fast_api):
+            async with AsyncClient(
+                transport=ASGITransport(app=runner.fast_api),
+                base_url="http://test",
+            ) as client:

34-35: Method signature may not match the abstract base class.

on_warmed_up in the base Warmable class takes resource: T as a parameter. Using *_ catches it but obscures the expected signature. Consider matching the base signature for clarity.

♻️ Match the Warmable signature
-    async def on_warmed_up(self, *_) -> None:
+    def on_warmed_up(self, resource: bool) -> None:
         self.warmed_up = True

Note: Also check if on_warmed_up should be async or sync based on the base class.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1b55281 and eed1daa.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • agents-core/vision_agents/core/agents/agent_launcher.py
  • agents-core/vision_agents/core/runner/http/api.py
  • agents-core/vision_agents/core/runner/http/models.py
  • agents-core/vision_agents/core/runner/runner.py
  • pyproject.toml
  • tests/test_agents/test_runner.py
🧰 Additional context used
📓 Path-based instructions (2)
**/*test*.py

📄 CodeRabbit inference engine (.cursor/rules/python.mdc)

**/*test*.py: Never mock in tests; use pytest for testing
Mark integration tests with @pytest.mark.integration decorator
@pytest.mark.asyncio is not needed - it is automatic

Files:

  • tests/test_agents/test_runner.py
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/python.mdc)

**/*.py: Never adjust sys.path in Python code
Never write except Exception as e - use specific exception handling
Avoid using getattr, hasattr, delattr and setattr; prefer normal attribute access in Python
Docstrings should follow the Google style guide for docstrings

Files:

  • tests/test_agents/test_runner.py
  • agents-core/vision_agents/core/agents/agent_launcher.py
  • agents-core/vision_agents/core/runner/http/api.py
  • agents-core/vision_agents/core/runner/runner.py
  • agents-core/vision_agents/core/runner/http/models.py
🧠 Learnings (1)
📚 Learning: 2025-11-24T17:04:43.030Z
Learnt from: CR
Repo: GetStream/Vision-Agents PR: 0
File: .cursor/rules/python.mdc:0-0
Timestamp: 2025-11-24T17:04:43.030Z
Learning: Applies to **/*.py : Never write `except Exception as e` - use specific exception handling

Applied to files:

  • agents-core/vision_agents/core/runner/runner.py
🧬 Code graph analysis (3)
tests/test_agents/test_runner.py (4)
agents-core/vision_agents/core/agents/agent_launcher.py (3)
  • AgentLauncher (51-286)
  • warmup (118-137)
  • warmed_up (140-141)
agents-core/vision_agents/core/runner/runner.py (1)
  • Runner (41-346)
agents-core/vision_agents/core/llm/llm.py (2)
  • LLM (50-396)
  • LLMResponseEvent (39-43)
agents-core/vision_agents/core/warmup.py (1)
  • Warmable (47-107)
agents-core/vision_agents/core/agents/agent_launcher.py (3)
agents-core/vision_agents/core/agents/agents.py (1)
  • id (262-263)
agents-core/vision_agents/core/utils/utils.py (2)
  • cancel_and_wait (105-121)
  • await_or_run (92-102)
agents-core/vision_agents/core/runner/http/dependencies.py (1)
  • get_session (36-39)
agents-core/vision_agents/core/runner/http/models.py (2)
agents-core/vision_agents/core/observability/agent.py (1)
  • description (12-13)
agents-core/vision_agents/core/agents/agents.py (1)
  • metrics (1403-1404)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: unit / Mypy
  • GitHub Check: unit / Ruff
  • GitHub Check: unit / Test "not integration"
  • GitHub Check: unit / Validate extra dependencies in "agents-core/pyproject.toml"
  • GitHub Check: unit / Mypy
  • GitHub Check: unit / Validate extra dependencies in "agents-core/pyproject.toml"
  • GitHub Check: unit / Test "not integration"
  • GitHub Check: unit / Ruff
🔇 Additional comments (5)
agents-core/vision_agents/core/agents/agent_launcher.py (1)

166-195: LGTM!

The start_session implementation is well-structured. The done callback correctly captures agent.id using the default argument pattern to avoid late-binding closure issues.

tests/test_agents/test_runner.py (1)

74-82: LGTM!

The test class structure and the health/ready endpoint tests are well-organized and appropriately use the shared fixtures.

pyproject.toml (3)

77-87: LGTM!

These exclusion patterns appropriately filter out build artifacts, caches, and test directories from the workspace. The globs are standard fare for Python projects.


121-123: LGTM!

A mere whisper of whitespace—formatting tidied, nothing altered in substance.


109-109: Sensible addition for testing the new HTTP server.

asgi-lifespan>=2.1.0 is well-suited for testing FastAPI lifespan events, which aligns with the agent HTTP server introduced in this PR.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@agents-core/vision_agents/core/runner/runner.py`:
- Around line 109-110: The use of getattr to resolve the logging level should be
replaced by an explicit mapping: create a LOG_LEVELS dict mapping strings like
"DEBUG","INFO","WARNING","ERROR","CRITICAL" to logging.DEBUG,... then compute
numeric_level = LOG_LEVELS.get(log_level.upper(), logging.INFO) (or validate and
log/warn if log_level is invalid) and pass numeric_level into
configure_sdk_logger; update the references around the numeric_level variable
and the call to configure_sdk_logger to use this validated mapping instead of
getattr.

In `@tests/test_agents/test_runner.py`:
- Around line 80-81: Add the "integration" pytest marker to the HTTP lifecycle
test by decorating the TestRunnerServe class or its test_health method with
`@pytest.mark.integration` and ensure pytest is imported; specifically update the
TestRunnerServe class or async def test_health(...) in
tests/test_agents/test_runner.py to include the `@pytest.mark.integration`
decorator so the FastAPI + lifespan + HTTP test is classified as an integration
test.
♻️ Duplicate comments (6)
agents-core/vision_agents/core/runner/runner.py (5)

64-71: Remove non-existent run_options from Runner.__init__ docstring.

The parameter isn’t in the signature, so this is misleading.

📝 Suggested fix
         Args:
             launcher: instance of `AgentLauncher`
             serve_options: instance of `ServeOptions` to configure behavior in `serve` mode.
-            run_options: instance of `RunOptions` to configure behavior in `run` mode.

125-131: start_session already spawns an agent—avoid double-spawn.

You’re launching an agent and then starting a session that likely creates another one. Use the session’s agent instead.

🐛 Suggested fix
                 # Start the agent launcher.
                 await self._launcher.start()
-
-                # Create the agent
-                agent = await self._launcher.launch()
-
-                logger.info("✅ Agent warmed up and ready")
...
-                # Join call if join_call function is provided
                 logger.info(f"📞 Joining call: {call_type}/{call_id}")
                 session = await self._launcher.start_session(
                     call_id, call_type, video_track_override_path=video_track_override
                 )
+                agent = session.agent
+
+                logger.info("✅ Agent warmed up and ready")
                 await session.wait()

Also applies to: 143-147


134-141: Avoid hasattr checks; use direct access guarded by AttributeError.

This avoids the banned hasattr usage while keeping behavior safe.

♻️ Suggested fix
-                if (
-                    not no_demo
-                    and hasattr(agent, "edge")
-                    and hasattr(agent.edge, "open_demo_for_agent")
-                ):
-                    logger.info("🌐 Opening demo UI...")
-                    await agent.edge.open_demo_for_agent(agent, call_type, call_id)
+                if not no_demo:
+                    try:
+                        open_demo = agent.edge.open_demo_for_agent
+                    except AttributeError:
+                        pass
+                    else:
+                        logger.info("🌐 Opening demo UI...")
+                        await open_demo(agent, call_type, call_id)

As per coding guidelines.


149-152: Replace broad except Exception with specific exceptions.

Catching everything hides unexpected failures and violates the project rule.

Based on learnings.


205-208: Use the options parameter when wiring app.state.options.

The current assignment ignores the provided parameter, which can break custom options.

🐛 Suggested fix
     def _create_fastapi_app(self, options: ServeOptions) -> FastAPI:
         app = FastAPI(lifespan=lifespan)
         app.state.launcher = self._launcher
-        app.state.options = self._serve_options
+        app.state.options = options
tests/test_agents/test_runner.py (1)

4-4: Replace MagicMock with a lightweight fake edge.

Mocking is disallowed in tests per guidelines; use a small fake instead.

♻️ Suggested fix
-from unittest.mock import MagicMock
...
+class FakeStreamEdge:
+    def __init__(self):
+        self.events = EventManager()
...
     async def create_agent(**kwargs) -> Agent:
-        stream_edge_mock = MagicMock()
-        stream_edge_mock.events = EventManager()
+        stream_edge = FakeStreamEdge()
...
         return Agent(
             llm=DummyLLM(),
             tts=DummyTTS(),
-            edge=stream_edge_mock,
+            edge=stream_edge,
             agent_user=User(name="test"),
         )

As per coding guidelines.

Also applies to: 43-49

🧹 Nitpick comments (1)
tests/test_agents/test_runner.py (1)

53-55: Avoid long sleep in join_call stub.

If join_call is awaited during session startup, this adds 10s per test. Consider a near-zero sleep (or immediate return) unless the delay is essential.

♻️ Suggested fix
     async def join_call(*args, **kwargs):
-        await asyncio.sleep(10)
+        await asyncio.sleep(0)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between eed1daa and 13c09bb.

📒 Files selected for processing (2)
  • agents-core/vision_agents/core/runner/runner.py
  • tests/test_agents/test_runner.py
🧰 Additional context used
📓 Path-based instructions (2)
**/*test*.py

📄 CodeRabbit inference engine (.cursor/rules/python.mdc)

**/*test*.py: Never mock in tests; use pytest for testing
Mark integration tests with @pytest.mark.integration decorator
@pytest.mark.asyncio is not needed - it is automatic

Files:

  • tests/test_agents/test_runner.py
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/python.mdc)

**/*.py: Never adjust sys.path in Python code
Never write except Exception as e - use specific exception handling
Avoid using getattr, hasattr, delattr and setattr; prefer normal attribute access in Python
Docstrings should follow the Google style guide for docstrings

Files:

  • tests/test_agents/test_runner.py
  • agents-core/vision_agents/core/runner/runner.py
🧠 Learnings (2)
📚 Learning: 2025-11-24T17:04:43.030Z
Learnt from: CR
Repo: GetStream/Vision-Agents PR: 0
File: .cursor/rules/python.mdc:0-0
Timestamp: 2025-11-24T17:04:43.030Z
Learning: Applies to **/*test*.py : Never mock in tests; use pytest for testing

Applied to files:

  • tests/test_agents/test_runner.py
📚 Learning: 2025-11-24T17:04:43.030Z
Learnt from: CR
Repo: GetStream/Vision-Agents PR: 0
File: .cursor/rules/python.mdc:0-0
Timestamp: 2025-11-24T17:04:43.030Z
Learning: Applies to **/*.py : Never write `except Exception as e` - use specific exception handling

Applied to files:

  • agents-core/vision_agents/core/runner/runner.py
🧬 Code graph analysis (1)
agents-core/vision_agents/core/runner/runner.py (3)
agents-core/vision_agents/core/runner/http/api.py (2)
  • lifespan (34-41)
  • start_session (55-78)
agents-core/vision_agents/core/runner/http/dependencies.py (5)
  • can_close_session (16-16)
  • can_start_session (13-13)
  • can_view_metrics (22-22)
  • can_view_session (19-19)
  • get_current_user (9-10)
agents-core/vision_agents/core/runner/http/options.py (1)
  • ServeOptions (15-55)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: unit / Ruff
  • GitHub Check: unit / Mypy
  • GitHub Check: unit / Validate extra dependencies in "agents-core/pyproject.toml"
  • GitHub Check: unit / Test "not integration"
  • GitHub Check: unit / Ruff
  • GitHub Check: unit / Test "not integration"
  • GitHub Check: unit / Validate extra dependencies in "agents-core/pyproject.toml"
  • GitHub Check: unit / Mypy

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@agents-core/vision_agents/core/runner/runner.py`:
- Around line 46-55: The doc example incorrectly calls launcher.cli() implying
AgentLauncher exposes cli(), but the CLI entrypoint is provided by Runner.cli();
update the example to construct and call Runner(...).cli() (or show that Runner
is created from AgentLauncher if that's intended) so it references Runner and
its cli() method (e.g., use Runner(...) and call .cli() instead of
AgentLauncher(...).cli()), ensuring the example matches the Runner.cli()
implementation.
♻️ Duplicate comments (8)
agents-core/vision_agents/core/runner/runner.py (5)

108-109: Avoid getattr for log level mapping; use an explicit map.

getattr(logging, ...) violates the no-getattr guideline and allows unexpected attributes. Prefer a fixed mapping shared by run() and serve(). As per coding guidelines.

♻️ Proposed refactor
+LOG_LEVELS = {
+    "CRITICAL": logging.CRITICAL,
+    "ERROR": logging.ERROR,
+    "WARNING": logging.WARNING,
+    "INFO": logging.INFO,
+    "DEBUG": logging.DEBUG,
+}
...
-        numeric_level = getattr(logging, log_level.upper(), logging.INFO)
+        numeric_level = LOG_LEVELS.get(log_level.upper(), logging.INFO)
...
-        configure_sdk_logger(
-            level=getattr(logging, agents_log_level.upper(), logging.INFO)
-        )
+        configure_sdk_logger(
+            level=LOG_LEVELS.get(agents_log_level.upper(), logging.INFO)
+        )
-        configure_fastapi_loggers(
-            level=getattr(logging, http_log_level.upper(), logging.INFO)
-        )
+        configure_fastapi_loggers(
+            level=LOG_LEVELS.get(http_log_level.upper(), logging.INFO)
+        )

Also applies to: 190-195


124-147: start_session already launches an agent — avoid double-spawn.

You launch an agent and then call start_session() (which launches again), so the warmed agent isn’t the one in-session. This can leak resources and confuse demo UI behavior.

🐛 Suggested fix
-                # Create the agent
-                agent = await self._launcher.launch()
-
-                logger.info("✅ Agent warmed up and ready")
-
-                # Open demo UI by default
-                if (
-                    not no_demo
-                    and hasattr(agent, "edge")
-                    and hasattr(agent.edge, "open_demo_for_agent")
-                ):
-                    logger.info("🌐 Opening demo UI...")
-                    await agent.edge.open_demo_for_agent(agent, call_type, call_id)
-
-                # Join call if join_call function is provided
                 logger.info(f"📞 Joining call: {call_type}/{call_id}")
                 session = await self._launcher.start_session(
                     call_id, call_type, video_track_override_path=video_track_override
                 )
+                agent = session.agent
+
+                logger.info("✅ Agent warmed up and ready")
+
+                # Open demo UI by default
+                if (
+                    not no_demo
+                    and hasattr(agent, "edge")
+                    and hasattr(agent.edge, "open_demo_for_agent")
+                ):
+                    logger.info("🌐 Opening demo UI...")
+                    await agent.edge.open_demo_for_agent(agent, call_type, call_id)
+
                 await session.wait()

133-140: Avoid hasattr; use direct access with AttributeError guard.

Guidelines disallow hasattr. Prefer direct access with a narrow try/except AttributeError (or a typed interface). As per coding guidelines.


148-152: Replace except Exception as e with specific exceptions.

Catching Exception violates the guideline and can swallow unexpected failures. Handle known exceptions explicitly and let others bubble. As per coding guidelines.


204-207: Use the options parameter when setting app.state.options.

_create_fastapi_app receives options but assigns self._serve_options, which ignores the passed value and can surprise callers.

🐛 Suggested fix
-        app.state.options = self._serve_options
+        app.state.options = options
agents-core/vision_agents/core/runner/http/api.py (2)

62-71: Avoid except Exception as e; handle specific launcher errors.

The broad catch violates the guideline and can mask programming errors. Use specific exceptions from AgentLauncher and re-raise unexpected ones. As per coding guidelines.


158-170: Metrics endpoint summary/docstring should mention metrics.

The summary and docstring still say “info about a running agent session,” which is misleading for /metrics.

✏️ Suggested doc fix
 `@router.get`(
     "/sessions/{session_id}/metrics",
     response_model=GetAgentSessionMetricsResponse,
-    summary="Get info about a running agent session",
+    summary="Get metrics for a running agent session",
 )
 ...
-    """
-    Get info about a running agent session.
-    """
+    """Get metrics for a running agent session."""
agents-core/vision_agents/core/agents/agent_launcher.py (1)

111-114: asyncio.as_completed yields awaitables; current stop() loop is incorrect.

result is an awaitable/task wrapper, not the awaited outcome. Calling .done() / .exception() without awaiting is invalid and can mask failures.

🐛 Suggested fix
-        coros = [cancel_and_wait(s.task) for s in self._sessions.values()]
-        async for result in cast(AsyncIterator[Task], asyncio.as_completed(coros)):
-            if result.done() and not result.cancelled() and result.exception():
-                logger.error(f"Failed to cancel the agent task: {result.exception()}")
+        coros = [cancel_and_wait(s.task) for s in self._sessions.values()]
+        results = await asyncio.gather(*coros, return_exceptions=True)
+        for result in results:
+            if isinstance(result, Exception) and not isinstance(result, asyncio.CancelledError):
+                logger.error(f"Failed to cancel the agent task: {result}")
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 13c09bb and d461e12.

📒 Files selected for processing (5)
  • agents-core/vision_agents/core/agents/agent_launcher.py
  • agents-core/vision_agents/core/runner/http/api.py
  • agents-core/vision_agents/core/runner/http/models.py
  • agents-core/vision_agents/core/runner/runner.py
  • tests/test_agents/test_runner.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_agents/test_runner.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/python.mdc)

**/*.py: Never adjust sys.path in Python code
Never write except Exception as e - use specific exception handling
Avoid using getattr, hasattr, delattr and setattr; prefer normal attribute access in Python
Docstrings should follow the Google style guide for docstrings

Files:

  • agents-core/vision_agents/core/runner/http/api.py
  • agents-core/vision_agents/core/runner/http/models.py
  • agents-core/vision_agents/core/agents/agent_launcher.py
  • agents-core/vision_agents/core/runner/runner.py
🧠 Learnings (2)
📚 Learning: 2025-11-24T17:04:43.030Z
Learnt from: CR
Repo: GetStream/Vision-Agents PR: 0
File: .cursor/rules/python.mdc:0-0
Timestamp: 2025-11-24T17:04:43.030Z
Learning: Applies to **/*.py : Never write `except Exception as e` - use specific exception handling

Applied to files:

  • agents-core/vision_agents/core/runner/runner.py
📚 Learning: 2025-11-24T17:04:43.030Z
Learnt from: CR
Repo: GetStream/Vision-Agents PR: 0
File: .cursor/rules/python.mdc:0-0
Timestamp: 2025-11-24T17:04:43.030Z
Learning: Applies to **/*.py : Avoid using getattr, hasattr, delattr and setattr; prefer normal attribute access in Python

Applied to files:

  • agents-core/vision_agents/core/runner/runner.py
🧬 Code graph analysis (1)
agents-core/vision_agents/core/runner/runner.py (5)
agents-core/vision_agents/core/agents/agent_launcher.py (6)
  • AgentLauncher (51-299)
  • start (96-103)
  • launch (151-164)
  • start_session (166-195)
  • wait (43-47)
  • stop (105-116)
agents-core/vision_agents/core/utils/logging.py (2)
  • configure_fastapi_loggers (59-95)
  • configure_sdk_logger (36-56)
agents-core/vision_agents/core/runner/http/api.py (2)
  • lifespan (34-41)
  • start_session (55-78)
agents-core/vision_agents/core/runner/http/dependencies.py (5)
  • can_close_session (16-16)
  • can_start_session (13-13)
  • can_view_metrics (22-22)
  • can_view_session (19-19)
  • get_current_user (9-10)
agents-core/vision_agents/core/runner/http/options.py (1)
  • ServeOptions (15-55)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: unit / Validate extra dependencies in "agents-core/pyproject.toml"
  • GitHub Check: unit / Test "not integration"
  • GitHub Check: unit / Ruff
  • GitHub Check: unit / Mypy
  • GitHub Check: unit / Test "not integration"
  • GitHub Check: unit / Validate extra dependencies in "agents-core/pyproject.toml"
  • GitHub Check: unit / Ruff
  • GitHub Check: unit / Mypy

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@Nash0x7E2 Nash0x7E2 changed the title WIP: Agent HTTP server Agent HTTP server Jan 16, 2026
@dangusev dangusev force-pushed the feature/agents-http-server branch from 00bd7c6 to a51d7e5 Compare January 19, 2026 13:16
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@examples/08_agent_server_example/README.md`:
- Around line 20-25: Fix markdownlint issues in README.md: indent nested list
items under the requirements by two spaces, change the plaintext .env code fence
to include the language tag "dotenv", convert the bold pseudo-headings
"**Example: JWT Authentication with Database Lookup**" and "**Granular
Permissions**" into real Markdown headings (e.g., "#### Example: JWT
Authentication with Database Lookup" and "#### Granular Permissions"), and turn
the raw Swagger UI URL "http://127.0.0.1:8000/docs" into a markdown link or
autolink (e.g., [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs)); apply
the same fixes to the other noted occurrences (lines referenced) so all
instances are lint-clean.
♻️ Duplicate comments (5)
agents-core/vision_agents/core/runner/runner.py (5)

109-111: Replace getattr log-level lookups with an explicit mapping.

This violates the project’s Python guidelines and is already noted in prior review. Prefer a dict-based mapping for clarity and validation. As per coding guidelines, avoid getattr in Python.

♻️ Proposed fix
+LOG_LEVELS = {
+    "CRITICAL": logging.CRITICAL,
+    "ERROR": logging.ERROR,
+    "WARNING": logging.WARNING,
+    "INFO": logging.INFO,
+    "DEBUG": logging.DEBUG,
+}
 ...
-        numeric_level = getattr(logging, log_level.upper(), logging.INFO)
+        numeric_level = LOG_LEVELS.get(log_level.upper(), logging.INFO)
 ...
-        configure_sdk_logger(
-            level=getattr(logging, agents_log_level.upper(), logging.INFO)
-        )
+        configure_sdk_logger(
+            level=LOG_LEVELS.get(agents_log_level.upper(), logging.INFO)
+        )
-        configure_fastapi_loggers(
-            level=getattr(logging, http_log_level.upper(), logging.INFO)
-        )
+        configure_fastapi_loggers(
+            level=LOG_LEVELS.get(http_log_level.upper(), logging.INFO)
+        )

Run the following to confirm all getattr(logging, ...) uses are removed after the refactor:

#!/bin/bash
rg -n "getattr\\(logging" agents-core/vision_agents/core/runner/runner.py

Also applies to: 192-197


130-148: Avoid double-spawn: start_session() already launches an agent.

The flow launches an agent and then starts a session that creates another agent, so the warmed agent is discarded. Use the session’s agent (or pass the warmed agent into start_session). This mirrors prior review feedback.

🐛 Suggested fix
-                # Create the agent
-                agent = await self._launcher.launch()
-
-                logger.info("✅ Agent warmed up and ready")
-
-                # Open demo UI by default
-                if (
-                    not no_demo
-                    and hasattr(agent, "edge")
-                    and hasattr(agent.edge, "open_demo_for_agent")
-                ):
-                    logger.info("🌐 Opening demo UI...")
-                    await agent.edge.open_demo_for_agent(agent, call_type, call_id)
-
                 # Join call if join_call function is provided
                 logger.info(f"📞 Joining call: {call_type}/{call_id}")
                 session = await self._launcher.start_session(
                     call_id, call_type, video_track_override_path=video_track_override
                 )
+                agent = session.agent
+
+                logger.info("✅ Agent warmed up and ready")
+
+                # Open demo UI by default
+                if (
+                    not no_demo
+                    and hasattr(agent, "edge")
+                    and hasattr(agent.edge, "open_demo_for_agent")
+                ):
+                    logger.info("🌐 Opening demo UI...")
+                    await agent.edge.open_demo_for_agent(agent, call_type, call_id)
+
                 await session.wait()

136-140: Avoid hasattr checks; use explicit access or a protocol.

Project guidelines discourage hasattr. Use a guarded attribute access or an interface check instead. As per coding guidelines, avoid hasattr in Python.

♻️ Suggested alternative
-                if (
-                    not no_demo
-                    and hasattr(agent, "edge")
-                    and hasattr(agent.edge, "open_demo_for_agent")
-                ):
-                    logger.info("🌐 Opening demo UI...")
-                    await agent.edge.open_demo_for_agent(agent, call_type, call_id)
+                if not no_demo:
+                    try:
+                        open_demo = agent.edge.open_demo_for_agent
+                    except AttributeError:
+                        pass
+                    else:
+                        logger.info("🌐 Opening demo UI...")
+                        await open_demo(agent, call_type, call_id)

Verify there are no remaining hasattr usages in this module:

#!/bin/bash
rg -n "hasattr\\(" agents-core/vision_agents/core/runner/runner.py

152-153: Avoid except Exception as e; handle specific errors.

The project rules prohibit a blanket Exception catch. Please enumerate the explicit exception types that start, launch, or start_session can raise and handle them, letting unknown errors propagate. As per coding guidelines, avoid except Exception as e.

Identify the concrete exceptions to catch by inspecting the launcher/session code:

#!/bin/bash
rg -n "raise " agents-core/vision_agents/core/agents/agent_launcher.py
rg -n "Exception" agents-core/vision_agents/core/agents/agent_launcher.py

206-210: Use the options parameter when setting app.state.options.

_create_fastapi_app() takes options but stores self._serve_options instead, which ignores the caller’s argument. This was noted earlier and should be aligned with the method signature.

🐛 Suggested fix
-        app.state.options = self._serve_options
+        app.state.options = options

@dangusev dangusev merged commit 19df9fc into main Jan 19, 2026
10 checks passed
@dangusev dangusev deleted the feature/agents-http-server branch January 19, 2026 14:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants