Agno Client Sunset

Catalog of the legacy idk.services.agnoidk.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 as PipelinePhaseFailed either 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

  1. Rate limiting now applies to prompt_builder + edit_repository. The default AGNO_RATE_LIMIT_PER_MIN=0 keeps the limiter disabled, so there is no behaviour change on the current deployment. When the limiter is enabled (rollout note in docs/runbooks/qap_rollout.md), discovery-pipeline and improvement-pipeline traffic counts against the same budget as QAP workflow calls.

  2. Prometheus metrics now cover every Agno call. agno_request_duration_seconds and agno_rate_limit_hits_total populate for the prompt_builder and edit_repository calls too. Grafana dashboards at docs/dashboards/qap.json will show this traffic automatically.

  3. Deterministic X-Request-ID header is now optional but available. Callers that want to dedupe upstream pass request_id=... to call_agent. Default keeps the legacy no-header behaviour to avoid surprising the platform.

  4. Connection reuse. The legacy helper opened a fresh requests.Session per call. AgnoClient reuses one session per instance via the urllib3.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_KEY settings — AgnoClient.from_settings() resolves them identically to the legacy helper.
  • Both X-API-Key and Authorization: Bearer headers 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.