Skip to content

Conversation

@uzaxirr
Copy link
Contributor

@uzaxirr uzaxirr commented Dec 4, 2025

Summary

Added the AgentOsClient class which is just a http client can be used to connect to remote agno agents.
(If applicable, issue number: #____)

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Improvement
  • Model update
  • Other:

Checklist

  • Code complies with style guidelines
  • Ran format/validation scripts (./scripts/format.sh and ./scripts/validate.sh)
  • Self-review completed
  • Documentation updated (comments, docstrings)
  • Examples and guides: Relevant cookbook examples have been included or updated (if applicable)
  • Tested in clean environment
  • Tests added/updated (if applicable)

Additional Notes

Add any important context (deployment instructions, screenshots, security considerations, etc.)

@uzaxirr uzaxirr self-assigned this Dec 4, 2025
Copilot AI review requested due to automatic review settings December 4, 2025 20:13
@uzaxirr uzaxirr requested a review from a team as a code owner December 4, 2025 20:13
Copilot finished reviewing on behalf of uzaxirr December 4, 2025 20:16
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a comprehensive HTTP client (AgentOSClient) for interacting with remote AgentOS instances, along with extensive unit tests and cookbook examples demonstrating its usage.

Key Changes:

  • New AgentOSClient class providing async HTTP client for all AgentOS API operations
  • Comprehensive unit tests with 100+ test cases covering initialization, HTTP methods, and all API operations
  • Five cookbook examples demonstrating client usage patterns (basic connection, agent runs, memory operations, session management, knowledge search)

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
libs/agno/agno/os/client.py Core client implementation with 2259 lines providing async methods for all AgentOS operations (discovery, runs, sessions, memory, knowledge, evals)
libs/agno/agno/os/__init__.py Exports AgentOSClient alongside existing AgentOS class
libs/agno/tests/unit/os/test_client.py Comprehensive unit tests (666 lines) covering all client functionality with mocked HTTP calls
libs/agno/tests/unit/os/__init__.py Package initialization for unit tests
cookbook/agent_os/client/*.py Five working examples demonstrating client usage patterns
cookbook/agent_os/client/README.md Detailed documentation with API reference and usage examples
cookbook/agent_os/client/__init__.py Package initialization for cookbook examples

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Raises:
HTTPStatusError: On HTTP errors
"""
import json
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

[nitpick] The json module is imported inside 10 different methods. Consider moving this import to the top of the file to improve code organization and avoid repeated import overhead. While Python caches module imports, having it at the module level is clearer and follows common Python conventions.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

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

Can be top level import

if table:
params["table"] = table

endpoint = "/memories?" + "&".join(f"{k}={v}" for k, v in params.items())
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

Query parameters are concatenated directly without URL encoding. This could cause issues if parameter values contain special characters (e.g., spaces, ampersands, equals signs). Consider using urllib.parse.urlencode() or httpx's built-in parameter support instead of manual string concatenation. For example:

# Instead of:
endpoint = "/memories?" + "&".join(f"{k}={v}" for k, v in params.items())
# Use:
from urllib.parse import urlencode
endpoint = f"/memories?{urlencode(params)}"
# Or let httpx handle it:
response = await self._http_client.get(url, params=params, headers=headers)

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

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

+1

VectorSearchResult,
)
from agno.os.routers.memory.schemas import (
UserMemoryCreateSchema,
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

Import of 'UserMemoryCreateSchema' is not used.

Suggested change
UserMemoryCreateSchema,

Copilot uses AI. Check for mistakes.
Comment on lines 55 to 75
from agno.os.schema import (
AgentResponse,
AgentSessionDetailSchema,
AgentSummaryResponse,
ConfigResponse,
CreateSessionRequest,
DeleteSessionRequest,
Model,
PaginatedResponse,
RunSchema,
SessionSchema,
TeamResponse,
TeamRunSchema,
TeamSessionDetailSchema,
TeamSummaryResponse,
UpdateSessionRequest,
WorkflowResponse,
WorkflowRunSchema,
WorkflowSessionDetailSchema,
WorkflowSummaryResponse,
)
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

Import of 'CreateSessionRequest' is not used.
Import of 'DeleteSessionRequest' is not used.
Import of 'UpdateSessionRequest' is not used.

Copilot uses AI. Check for mistakes.
WorkflowSummaryResponse,
)
try:
from httpx import AsyncClient, HTTPStatusError
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

Import of 'HTTPStatusError' is not used.

Suggested change
from httpx import AsyncClient, HTTPStatusError
from httpx import AsyncClient

Copilot uses AI. Check for mistakes.
self,
base_url: str,
api_key: Optional[str] = None,
timeout: float = 300.0,
Copy link
Contributor

Choose a reason for hiding this comment

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

300 seconds? that's a lot probably. Also okay to have int here, we use that everywhere for seconds I think, cool to keep consistency

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"""Exit async context manager and cleanup resources."""
await self.close()

async def connect(self) -> "AgentOSClient":
Copy link
Contributor

Choose a reason for hiding this comment

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

We need the instance to call this, so no need to return the instance itself

Copy link
Contributor Author

Choose a reason for hiding this comment

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

self._http_client = AsyncClient(timeout=self.timeout)
return self

async def close(self) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

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

I think OK to not carry sync methods here, but not sure anymore... @dirkbrnd what do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

We'll just need sync/async for the connection/client pieces. I can explain later, but we'll need sync client for some of the methods.

async for line in response.aiter_lines():
yield line

async def _post_empty(self, endpoint: str) -> bool:
Copy link
Contributor

Choose a reason for hiding this comment

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

These methods look a bit too similar, we can probably simplify and just have get, post, delete?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

data = await self._get("/agents")
return [AgentSummaryResponse.model_validate(item) for item in data]

async def get_agent(self, agent_id: str) -> AgentResponse:
Copy link
Contributor

Choose a reason for hiding this comment

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

  • let's keep the run_agent and run_team methods together with these ones?
  • missing workflow endpoints i think

Copy link
Contributor Author

Choose a reason for hiding this comment

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

which workflow endpoint am i missing over here? can you please specify?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Comment on lines 1352 to 1365
if agent_id:
payload["agent_id"] = agent_id
if team_id:
payload["team_id"] = team_id
if model_id:
payload["model_id"] = model_id
if model_provider:
payload["model_provider"] = model_provider
if expected_output:
payload["expected_output"] = expected_output
if expected_tool_calls:
payload["expected_tool_calls"] = expected_tool_calls
if num_iterations != 1:
payload["num_iterations"] = num_iterations
Copy link
Contributor

Choose a reason for hiding this comment

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

payload can take Nones, right? I think no need to do this

If we do it, we will basically force ourselves to maintain this separately from the schema class itself. That's a bit of a smell and will make updates here a bit more risky (we will at some point forget about one of the places to update)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

a remote AgentOS instance and perform basic operations.

Prerequisites:
1. Start an AgentOS server:
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe make a helper file in this directory to spin up an AgentOS that would work for all the cookbooks

self.base_url = base_url.rstrip("/")
self.api_key = api_key or getenv("AGNO_API_KEY")
self.timeout = timeout
self._http_client: Optional[AsyncClient] = None
Copy link
Contributor

Choose a reason for hiding this comment

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

We'll need a sync and async clients. We'll use them selectively, so no need to duplicate all the methods, but we'll need both, and then also enter, etc for setting up the connection

Raises:
HTTPStatusError: On HTTP errors (4xx, 5xx)
"""
if not self._http_client:
Copy link
Contributor

Choose a reason for hiding this comment

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

Rather make a _get_client() that returns or creates the client. You can use it above in the connect method as well


async def __aenter__(self) -> "AgentOSClient":
"""Enter async context manager."""
self._http_client = AsyncClient(timeout=self.timeout)
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't you call connect here?

data["context"] = json.dumps(context)

response_data = await self._post_form_data(endpoint, data)
return RunSchema.model_validate(response_data)
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be RunOutput?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

RunSchema is the API response schema. and i see that RunOutput is the internal agent run output class. Now since AgentOSclient is just an API client
that receives JSON responses and parses them, so imo
RunSchema is correct for deserializing the API response.

Please correct me if im wrong and we should use RunOutput here

Copy link
Contributor

Choose a reason for hiding this comment

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

/agents/{agent_id}/runs does not return RunSchema... RunSchema is used by the sessions endpoint when returning a list of stored runs.
The runs endpoint returns either a run output object or a stream of run events. And this client should do the same

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants