Skip to content

Feat/agent recommended additions - #1661

Open
SrihariLegend wants to merge 2 commits into
Observal:mainfrom
SrihariLegend:feat/agent-recommended-additions
Open

SrihariLegend wants to merge 2 commits into
Observal:mainfrom
SrihariLegend:feat/agent-recommended-additions

Conversation

@SrihariLegend

@SrihariLegend SrihariLegend commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Purpose / Description

The agent detail page and observal pull are pure install today: zero recommendations. A person browsing an agent gets no signal about what else they might benefit from. Meanwhile, the insights pipeline already computes a deterministic shortlist of registry components an agent doesn't use but might benefit from (registry_offer) — and as of #1660, that analysis is now persisted instead of discarded every run.

This PR adds the first consumer of that persisted analysis: a public endpoint and a "Recommended add-ons" rail on the agent detail page that surfaces evidence-backed component suggestions derived from observed usage.

Fixes

  • Fixes #

Approach

A new read-only public endpoint + a frontend rail. No migration, no pipeline change, no schema change to existing models — it reads #1660's registry_offer column.

  1. GET /api/v1/agents/{id}/insights/recommended-additions (public, optional_current_user) — loads the latest completed insight report for the agent and returns its registry_offer component shortlist as a flat list. Unlike the full insight report (edit-access-gated, exposes private session telemetry), this surface exposes only public component referencestype, id, qualified_name, name, description, category — so it is safe for anyone who can see the agent, including anonymous browsing. Agent visibility is checked via check_listing_visibility_async; the offer entries are public registry components by construction (the recommender only returns approved+visible listings).
  2. AgentRecommendedAdditions rail component — renders the shortlist on the agent detail Overview tab, reusing the visual pattern from the existing RecommendedForYou rail (type badge, name link to component detail, description).
  3. Degrades cleanly to nothing. Empty items when: no report exists for the agent, the report predates the registry_offer column (null, from feat(insights): persist discarded run analysis #1660's no-backfill design), the offer was empty, or the feature was disabled at generation time. The rail returns null on empty/error/loading — an empty rail is worse than no rail — so agents without reports look exactly as before.

This is deliberately the cheapest, highest-value first consumer of the persisted analysis: it delivers a feature that didn't exist anywhere in the product, it reuses already-spent compute, and it validates the "persist the analysis, then let consumers read it" pattern with a real reader.

How Has This Been Tested?

make test — 122 tests pass on this branch (new + existing insights, agent-scoped routes, access, registry-match, self-learn, retention).

New tests in tests/test_agent_recommended_additions.py (6):

  • Offer entries returned correctly (type, id, qualified_name, name, with source_report_id + generated_at)
  • Empty when no report exists
  • Empty when registry_offer is null (old reports predate the column)
  • Empty when the feature was disabled at generation time (enabled=False)
  • 404 when the agent is not visible to the caller
  • Malformed entries in the persisted offer are skipped, not fatal

Reproduce with:

cd observal-server && uv run --with pytest --with pytest-asyncio --with pyyaml --with typer --with rich --with hypothesis --with pyarrow pytest ../tests/test_agent_recommended_additions.py -q

Frontend: tsc --noEmit (typecheck), eslint, and full vite build all pass clean. The rail renders nothing when there's no data, so the agent detail page is unchanged for agents without insight reports.

make lint clean.

Learning

This PR is the payoff for #1660's "persist the discarded analysis" change. #1660 stopped throwing away version_impact and registry_offer every run but shipped no consumer — this is the first reader, proving a feature can use that analysis without touching the pipeline. The key design decision was keeping the endpoint narrow: it returns only the public component shortlist, never the report's narrative or metrics, which lets it be public (anyone browsing) rather than edit-access-gated like the full insight report. The "render nothing on empty" pattern (borrowed from the existing RecommendedForYou rail) is what makes uneven report coverage acceptable — the rail fills in as reports accumulate rather than looking broken on agents without them.

Checklist

  • You have a descriptive commit message with a short title (first line, max 50 chars). — feat: evidence-backed add-ons on agent detail (45 chars)
  • You have commented your code, particularly in hard-to-understand areas
  • You have performed a self-review of your own code
  • UI changes: include screenshots of all affected screens

Summary by CodeRabbit

  • New Features
    • Added evidence-backed recommended registry components to agent detail pages.
    • Recommendations display as linked cards with component type, category, name, and description.
    • Insight reports now expose version impact, registry offers, and self-learning status.
  • Bug Fixes
    • Invalid or unavailable recommendations are safely omitted.
    • Malformed insight data no longer prevents reports from being saved.
  • Validation
    • Added structured validation for insight analysis data and recommendation responses.

The insights pipeline computes two pieces of analysis every run and then
discards them after folding them into the LLM prompt:

- version_impact: cross-user layer/config correlation analysis (ClickHouse
  queries + layer-snapshot diffs)
- registry_offer: the deterministic registry component shortlist shown to
  the model (build_signals + build_catalog)

That is spent compute being thrown away. Persist both as nullable JSON
columns on insight_reports so downstream consumers (duplicate detection,
pull-time recommendations, governance drift signals) can read them without
re-running the pipeline. Both are nullable: old reports predate the columns,
and a run may legitimately produce neither.

Also add a typed Pydantic schema (InsightAnalysisPayload) for the analytical
payload, validated non-destructively on write: a structural mismatch is logged
but never blocks the report write, so schema drift cannot break report
generation. The existing untyped JSON blobs (metrics, narrative,
aggregated_data) are unchanged — this is additive only.

This is the "unconditionally worth it" subset of the broader InsightSnapshot
refactor: it fixes real waste and types the payload without introducing a new
entity, a cutover, or immutability discipline. The full snapshot
externalization remains a separate, demand-gated decision.

Review fixes (PR Observal#1660):
- registry_offer serialization uses ``is not None`` rather than truthiness;
  CatalogOffer.__bool__ is bool(entries_by_type), so empty/disabled/failed
  offers are falsy and would have been dropped to None, losing the
  enabled / registry_has_components metadata that matters most in those cases.
- ValidationError logging uses e.errors(include_input=False) and logs only
  loc/type/msg — never the raw input, which can carry LLM-generated narrative
  or session-derived text.
- Migration docstring says "JSON" not "jsonb" (columns use sa.JSON(), matching
  the existing metrics/narrative/aggregated_data columns).

- Add version_impact + registry_offer columns (migration 023)
- Capture both in generator._run_pipeline return + _empty_report
- Persist in batch.run_single_report + non-destructive validation
- Add CatalogOffer.to_dict() to serialize the full offer
- Expose both fields on InsightReportResponse (additive)
- Tests for to_dict, payload schema, validation fallback, empty report, and
  the truthiness regression guard
The agent detail page and `observal pull` are pure install today: zero
recommendations. This adds the first consumer of the persisted registry_offer
analysis (PR #1) — a public endpoint and a "Recommended add-ons" rail on the
agent detail Overview tab that surfaces public registry components the agent
does not yet use but might benefit from, derived from observed usage.

GET /api/v1/agents/{id}/insights/recommended-additions returns the latest
completed insight report's deterministic component shortlist. Unlike the full
insight report (edit-access-gated, exposes private telemetry), this surface
exposes only public component references — type, id, qualified_name, name,
description, category — so it is safe for anyone who can see the agent,
including anonymous browsing.

Degrades cleanly to an empty list when: no report exists for the agent, the
report predates the registry_offer column (null), the offer was empty, or the
feature was disabled at generation time. The frontend rail renders nothing on
empty/error/loading — an empty rail is worse than no rail — so agents without
reports look exactly as before.

No migration, no pipeline change, no schema change to existing models: this
reads PR #1's registry_offer column through a new read-only endpoint.

- Add RecommendedAddition + RecommendedAdditionsResponse schemas
- Add public GET /agents/{id}/insights/recommended-additions endpoint
- Add useAgentRecommendedAdditions hook + insights.recommendedAdditions client
- Add AgentRecommendedAdditions rail component
- Render the rail on the agent detail Overview tab
- Tests: offer entries returned, empty on no report / null offer / disabled /
  not-visible (404), malformed entries skipped
@github-actions github-actions Bot added server Pull request touches server code tests Pull request adds or modifies tests web Pull request touches web frontend code labels Aug 3, 2026
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The insight pipeline now persists version-impact and registry-offer data. A public endpoint returns validated recommendations for visible agents. The web application fetches and displays these recommendations in the agent overview.

Changes

Insight recommendations

Layer / File(s) Summary
Analysis payload contract and persistence
observal-server/schemas/insight_analysis.py, observal-server/services/insights/*, observal-server/models/insight_report.py, observal-server/alembic/versions/023_insight_analysis_payload.py, tests/test_insight_analysis_payload.py
Adds typed payload validation, registry-offer serialization, nullable report fields, migration support, and non-blocking persistence validation.
Recommendation API and response validation
observal-server/schemas/insights.py, observal-server/api/routes/agent/insights.py, tests/test_agent_recommended_additions.py
Adds recommendation response schemas and an endpoint that checks agent visibility, reads the latest completed report, filters malformed entries, and returns normalized recommendations.
Agent recommendation presentation
web/src/lib/types/admin.ts, web/src/lib/api.ts, web/src/hooks/use-insights-api.ts, web/src/components/registry/agent-recommended-additions.tsx, web/src/pages/registry/agents/detail.tsx
Adds typed API access, cached querying, linked recommendation cards, and agent overview integration.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AgentOverview
  participant AgentRecommendedAdditions
  participant useAgentRecommendedAdditions
  participant insightsRecommendedAdditions
  participant agent_recommended_additions
  participant InsightReport

  AgentOverview->>AgentRecommendedAdditions: pass agentId
  AgentRecommendedAdditions->>useAgentRecommendedAdditions: query recommendations
  useAgentRecommendedAdditions->>insightsRecommendedAdditions: fetch recommendations
  insightsRecommendedAdditions->>agent_recommended_additions: GET agent recommendations
  agent_recommended_additions->>InsightReport: select latest completed report
  InsightReport-->>agent_recommended_additions: return registry_offer
  agent_recommended_additions-->>insightsRecommendedAdditions: return normalized items
  insightsRecommendedAdditions-->>useAgentRecommendedAdditions: return typed response
  useAgentRecommendedAdditions-->>AgentRecommendedAdditions: provide cached result
  AgentRecommendedAdditions-->>AgentOverview: render recommendation cards
Loading

Possibly related PRs

Suggested reviewers: haz3-jolt, shaannarendran

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: adding recommended additions for agents.
Description check ✅ Passed The description covers the purpose, approach, testing, learning, and checklist, but its claim that no migration or pipeline changes exist conflicts with the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
web/src/components/registry/agent-recommended-additions.tsx (1)

9-10: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Import through the frontend aggregators. Use @/hooks/use-api for useAgentRecommendedAdditions and @/lib/types for RecommendedAddition.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/registry/agent-recommended-additions.tsx` around lines 9 -
10, Update the imports in the agent recommended additions component to use the
frontend aggregator modules: import useAgentRecommendedAdditions from
"`@/hooks/use-api`" and RecommendedAddition from "`@/lib/types`", removing the
direct module imports.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@observal-server/schemas/insight_analysis.py`:
- Around line 27-30: Replace the structlog logger binding in
observal-server/schemas/insight_analysis.py lines 27-30 with the Loguru alias
optic, update the validation event at lines 83-83 to call optic.warning with
positional formatting and the sanitized errors, and update the validation
metadata logging in observal-server/services/insights/batch.py lines 269-275 to
use optic.info with positional arguments and no f-strings.

In `@web/src/hooks/use-insights-api.ts`:
- Around line 193-207: Update the report-completion invalidation flow in
useGenerateInsight to also invalidate the ["insights", "recommended-additions",
agentId] query used by useAgentRecommendedAdditions. Preserve the existing
reports invalidation and ensure the recommendation query refreshes immediately
after a report completes.
- Around line 201-205: Update the retry callback in the insights API query
options to use _count and cap retries at the configured limit of one retry for
non-404 errors. Continue returning false immediately when the error status is
404, and prevent persistent non-404 failures from retrying indefinitely.

---

Nitpick comments:
In `@web/src/components/registry/agent-recommended-additions.tsx`:
- Around line 9-10: Update the imports in the agent recommended additions
component to use the frontend aggregator modules: import
useAgentRecommendedAdditions from "`@/hooks/use-api`" and RecommendedAddition from
"`@/lib/types`", removing the direct module imports.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ce8a3885-6281-444e-87f7-536069b983bd

📥 Commits

Reviewing files that changed from the base of the PR and between 419f73e and d445a3c.

📒 Files selected for processing (15)
  • observal-server/alembic/versions/023_insight_analysis_payload.py
  • observal-server/api/routes/agent/insights.py
  • observal-server/models/insight_report.py
  • observal-server/schemas/insight_analysis.py
  • observal-server/schemas/insights.py
  • observal-server/services/insights/batch.py
  • observal-server/services/insights/generator.py
  • observal-server/services/insights/registry_match.py
  • tests/test_agent_recommended_additions.py
  • tests/test_insight_analysis_payload.py
  • web/src/components/registry/agent-recommended-additions.tsx
  • web/src/hooks/use-insights-api.ts
  • web/src/lib/api.ts
  • web/src/lib/types/admin.ts
  • web/src/pages/registry/agents/detail.tsx

Comment on lines +27 to +30
import structlog
from pydantic import BaseModel, ConfigDict, Field, ValidationError

logger = structlog.get_logger(__name__)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use the required Loguru logger for validation events.

The new validation logs use structlog or logger. Use from loguru import logger as optic and positional formatting for these events.

  • observal-server/schemas/insight_analysis.py#L27-L30: replace the structlog import and logger binding with the required Loguru alias.
  • observal-server/schemas/insight_analysis.py#L83-L83: emit the sanitized validation errors with optic.warning("insight_payload_validation_failed errors={}", safe).
  • observal-server/services/insights/batch.py#L269-L275: emit validation metadata with optic.info(...) and positional arguments.

As per coding guidelines, use Loguru via from loguru import logger as optic and pass positional arguments without f-strings.

📍 Affects 2 files
  • observal-server/schemas/insight_analysis.py#L27-L30 (this comment)
  • observal-server/schemas/insight_analysis.py#L83-L83
  • observal-server/services/insights/batch.py#L269-L275
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@observal-server/schemas/insight_analysis.py` around lines 27 - 30, Replace
the structlog logger binding in observal-server/schemas/insight_analysis.py
lines 27-30 with the Loguru alias optic, update the validation event at lines
83-83 to call optic.warning with positional formatting and the sanitized errors,
and update the validation metadata logging in
observal-server/services/insights/batch.py lines 269-275 to use optic.info with
positional arguments and no f-strings.

Source: Coding guidelines

Comment on lines +193 to +207
export function useAgentRecommendedAdditions(agentId: string | undefined) {
return useQuery({
queryKey: ["insights", "recommended-additions", agentId],
queryFn: () => insights.recommendedAdditions(agentId!),
enabled: !!agentId,
// The offer is derived from the latest completed report and changes only
// when a new report lands. Cheap to cache.
staleTime: 5 * 60_000,
retry: (_count, err: unknown) => {
// 404 = no report / agent not visible; not a retryable error.
const status = (err as { status?: number })?.status;
return status !== 404;
},
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check whether generating a new insight report invalidates the recommended-additions query cache.
set -euo pipefail

fd -t f 'use-insights-api.ts' -x cat -n {}

echo "--- invalidateQueries call sites touching insights keys ---"
rg -n -C 5 'invalidateQueries' web/src/hooks/use-insights-api.ts

Repository: Observal/Observal

Length of output: 11093


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "--- recommended-additions hook usages ---"
rg -n -C 4 'useAgentRecommendedAdditions|recommended-additions|recommendedAdditions' web/src

echo "--- report generation and completion-related usages ---"
rg -n -C 5 'useGenerateInsight|generateInsight|insight.*(complete|completed|status)|report.*(complete|completed)|status === "(pending|running)"' web/src

echo "--- API definitions for insight report operations ---"
rg -n -C 8 'recommendedAdditions|generate:|generate\(|listReports|getReport' web/src/lib web/src/hooks

Repository: Observal/Observal

Length of output: 40751


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "--- agent detail placement and lifecycle ---"
sed -n '500,635p' web/src/pages/registry/agents/detail.tsx
rg -n -C 8 'AgentRecommendedAdditions' web/src/pages/registry/agents/detail.tsx

echo "--- recommended additions component ---"
sed -n '1,180p' web/src/components/registry/agent-recommended-additions.tsx

echo "--- hook exports and duplicate implementations ---"
rg -n -C 6 'useGenerateInsight|useAgentRecommendedAdditions|useInsightReports' web/src/hooks/use-api.ts web/src/hooks

Repository: Observal/Observal

Length of output: 13608


Invalidate recommended additions when a report completes.

useGenerateInsight invalidates only ["insights", "reports", agentId], and report polling does not invalidate ["insights", "recommended-additions", agentId]. The rail can therefore show stale or empty recommendations for up to five minutes after completion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/hooks/use-insights-api.ts` around lines 193 - 207, Update the
report-completion invalidation flow in useGenerateInsight to also invalidate the
["insights", "recommended-additions", agentId] query used by
useAgentRecommendedAdditions. Preserve the existing reports invalidation and
ensure the recommendation query refreshes immediately after a report completes.

Comment on lines +201 to +205
retry: (_count, err: unknown) => {
// 404 = no report / agent not visible; not a retryable error.
const status = (err as { status?: number })?.status;
return status !== 404;
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- hook context ---'
sed -n '170,220p' web/src/hooks/use-insights-api.ts
printf '%s\n' '--- TanStack Query version and retry usage ---'
rg -n '"`@tanstack/react-query`"|retry:|useAgentRecommendedAdditions' package.json package-lock.json pnpm-lock.yaml yarn.lock web package.json 2>/dev/null | head -120
printf '%s\n' '--- related component error handling ---'
rg -n -C 5 'useAgentRecommendedAdditions|isError' web/src
printf '%s\n' '--- local documentation or type definitions for retry semantics ---'
rg -n -C 3 'retryDelay|retry.*failure|failureCount|retry.*3' . -g '*.ts' -g '*.tsx' -g '*.md' -g '*.json' | head -160

Repository: Observal/Observal

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- query client defaults ---'
cat -n web/src/lib/query-client.ts
printf '%s\n' '--- recommended additions consumer ---'
cat -n web/src/components/registry/agent-recommended-additions.tsx | sed -n '1,90p'
printf '%s\n' '--- insights API error construction ---'
rg -n -C 8 'recommendedAdditions|status:|throw new|response\.status' web/src/lib web/src/hooks/use-insights-api.ts | head -180
printf '%s\n' '--- retry callback counts in the checked-in source ---'
rg -n -C 3 'retry:.*=>|retryDelay|failureCount|retryOnMount' web/src

Repository: Observal/Observal

Length of output: 16096


🌐 Web query:

TanStack Query v5 useQuery retry option retry function failureCount custom retry official documentation

💡 Result:

In TanStack Query v5, the retry option for useQuery can be configured as a boolean, a number, or a function to determine if and when a failed query should be retried [1][2]. When providing a function to the retry option, it takes two arguments: failureCount (the number of consecutive failures, starting at 0 for the first retry) and error (the error thrown by the query function) [1][2][3]. The function must return a boolean: return true to continue retrying, or false to stop [1][2]. Example of custom retry logic: const result = useQuery({ queryKey: ['todos'], queryFn: fetchTodoList, retry: (failureCount, error) => { // Example: Do not retry on a 404 error if (error.status === 404) { return false; } // Retry up to 3 times return failureCount < 3; }, }) Key behaviors: - failureCount: Starts at 0 for the first retry attempt [1][2]. - Defaults: By default, TanStack Query retries 3 times on the client and 0 times on the server [1]. - retryDelay: You can also customize the delay between retries using the retryDelay option, which accepts a number (in milliseconds) or a function that receives the retryAttempt count and the error to calculate the delay [1][2].

Citations:


Cap retries for non-404 errors.

The callback overrides the configured default of one retry and ignores _count, so persistent failures retry indefinitely. Bound _count to preserve the query client's retry limit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/hooks/use-insights-api.ts` around lines 201 - 205, Update the retry
callback in the insights API query options to use _count and cap retries at the
configured limit of one retry for non-404 errors. Continue returning false
immediately when the error status is 404, and prevent persistent non-404
failures from retrying indefinitely.

This branch has not been deployed

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

Labels

server Pull request touches server code tests Pull request adds or modifies tests web Pull request touches web frontend code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant