Agno Client Sunset¶
Catalog of the legacy idk.services.agno → idk.services.agno_client
migration completed alongside the QAP hardening PR set. Two parallel HTTP
clients had been living side-by-side: the new AgnoClient (QAP / workflows
endpoints, with retries + rate limiter + Prometheus instrumentation) and
the legacy agno.call_agent helper (one-shot requests.post, no retries,
no instrumentation). This document records what changed and why.
What got deleted¶
| Removed | Replaced by |
|---|---|
idk/services/agno.py (98 lines) |
The AgnoClient class in idk/services/agno_client.py |
agno.call_agent(agent_id, query, ...) free function |
AgnoClient.from_settings().call_agent(agent_id, query, ...) |
agno.AgnoConnectorError |
agno_client.AgnoClientError |
AgnoConnectorError was not aliased; every catcher was updated to the new
exception class. The deletion is final — no deprecation period because
the entire codebase was migrated in the same commit.
Per-caller migration table¶
| Caller (file:line) | Before | After | Retry behaviour | Notes |
|---|---|---|---|---|
idk/sites/tasks/pipeline/steps.py:51 (Prompt Builder) |
call_agent("prompt_builder", ...) |
AgnoClient.from_settings().call_agent("prompt_builder", ...) |
New: 5x retry on 5xx, 1.5s backoff | Now rate-limited + Prometheus-traced. Safer than the legacy fire-and-forget. |
idk/services/agno_agents/edit_repository.py:91 (EditRepositoryAgent.adapt) |
call_agent(self.agent_id, ...) direct |
AgnoClient.from_settings(timeout=self.timeout).call_agent(self.agent_id, ...) |
New: 5x retry on 5xx | Agent is idempotent (re-committing same content returns existing SHA), so retries are safe. Per-instance 600s timeout preserved via from_settings(timeout=...). |
idk/sites/tasks/pipeline/quality_flow/phases/provision_dockerfile.py |
except AgnoConnectorError |
except AgnoClientError |
n/a (only catches) | Catches errors raised by EditRepositoryAgent.adapt after the migration. |
idk/sites/tasks/pipeline/deployment_flow/phases/adapt_schemas.py |
except AgnoConnectorError |
except AgnoClientError |
n/a (only catches) | Same pattern as provision_dockerfile. |
idk/sites/tests/test_provision_dockerfile.py:137 |
AgnoConnectorError("boom") |
AgnoClientError("boom") |
n/a | Test fixture. |
No third-party callers exist (the legacy module was always internal-only).
Per-caller retry decision¶
The plan called out one decision per caller: opt into the new retry
policy or pass retry_total=0 to preserve fire-and-forget semantics.
- prompt_builder (
steps.py): opted into default retries. The prompt-builder agent is a stateless LLM call; a transient 502 from the platform is exactly the case where retrying helps. Failure bubbles up asPipelinePhaseFailedeither way, so the user-visible blast radius is unchanged. - edit_repository (
agno_agents/edit_repository.py): opted into default retries. The agent's tools (read_repository,edit_repository) are documented idempotent — a retry after a successful upstream commit returns the same head SHA without writing again.
Neither caller used retry_total=0. If a future caller genuinely needs
fire-and-forget semantics, the constructor still accepts the kwarg:
client = AgnoClient.from_settings()
# Or, to disable retries for a fire-and-forget call:
client = AgnoClient("http://agno/api/v1", api_key="...", retry_total=0)
Behavioural changes worth knowing about¶
-
Rate limiting now applies to prompt_builder + edit_repository. The default
AGNO_RATE_LIMIT_PER_MIN=0keeps the limiter disabled, so there is no behaviour change on the current deployment. When the limiter is enabled (rollout note indocs/runbooks/qap_rollout.md), discovery-pipeline and improvement-pipeline traffic counts against the same budget as QAP workflow calls. -
Prometheus metrics now cover every Agno call.
agno_request_duration_secondsandagno_rate_limit_hits_totalpopulate for the prompt_builder and edit_repository calls too. Grafana dashboards atdocs/dashboards/qap.jsonwill show this traffic automatically. -
Deterministic
X-Request-IDheader is now optional but available. Callers that want to dedupe upstream passrequest_id=...tocall_agent. Default keeps the legacy no-header behaviour to avoid surprising the platform. -
Connection reuse. The legacy helper opened a fresh
requests.Sessionper call.AgnoClientreuses one session per instance via theurllib3.Retry-mounted adapter, so back-to-back calls in one task skip the TCP handshake.
What's the same¶
AGNO_INTERNAL_API_URL/AGNO_API_URL/AGNO_API_KEYsettings —AgnoClient.from_settings()resolves them identically to the legacy helper.- Both
X-API-KeyandAuthorization: Bearerheaders are still sent for backwards compatibility with platform middleware that accepts either. - Request body shape (
{"query": ..., "session_id": ..., "language"?}) unchanged. - Response unwrap order unchanged: plain string first, then
{"content": "..."}envelope, else raise. - Auto-generated session id format unchanged
(
{agent_id}-{uuid_hex[:12]}).
Tests¶
| Test file | Coverage |
|---|---|
idk/services/tests/test_agno_client.py::TestCallAgent |
6 cases: plain string, content envelope, language + session, request_id, unexpected payload raises, 429 → AgnoRateLimitError |
Existing tests for the migrated callers (test_provision_dockerfile,
test_pipeline_apply_repo_settings, etc.) continued to pass without
modification — the exception name was the only break and was updated
in the same diff.
Future work¶
None tracked. The migration is the only deliverable of Sketch B from the QAP hardening plan; once this lands and clears CI, the ticket closes.