Skip to content

fix(agents): persist the catalog the availability probe already fetched - #735

Merged
kaizhou-lab merged 1 commit into
mainfrom
fix/probe-persists-session-catalog
Jul 31, 2026
Merged

fix(agents): persist the catalog the availability probe already fetched#735
kaizhou-lab merged 1 commit into
mainfrom
fix/probe-persists-session-catalog

Conversation

@kaizhou-lab

Copy link
Copy Markdown
Contributor

Problem

The agent picker shows no modes / models / config options for agents that have never been opened in a conversation — including ones the availability check reports online.

The availability probe is not a shallow liveness check. It deliberately opens a real session/new so an auth-gated agent surfaces auth_required instead of silently looking healthy. That response carries exactly the catalog the picker needs — and the probe threw it away:

// custom_agent_probe.rs
let outcome = match protocol.new_session(...).await {
    Ok(_) => ProbeOutcome::Ok,   // ← modes / models / configOptions discarded

The four catalog columns were filled only when a live conversation opened a session and emit_snapshot_events broadcast to CatalogForwarder. So the data was fetched, proven, and dropped; the picker stayed empty until first use.

Measured on one dev machine: 27 of 39 ACP rows had config_options / available_modes / available_models NULL, several of them probing online. Verified the data is really there — a live probe of kilo returns a full configOptions array (model / effort / mode with all options) while its stored config_options is NULL.

Fix

Keep the response and persist it through the same channel a live session uses.

  • catalog_partial_from_session_new (catalog_forwarder.rs) projects modes / models / config options into an AgentHandshake directly. It sits beside catalog_partial_from_event, and a parity test feeds one session state through both routes and asserts the blobs are byte-identical — both write the same agent_metadata columns, so which route ran last must not change what the picker reads.
    available_commands has no counterpart here: it arrives as a session/update notification, never in the session/new response.
  • try_connect_custom_agent_with_catalog returns that partial alongside the verdict. The existing try_connect_custom_agent signature is unchanged, so the try-connect endpoint and test-on-save are untouched, and no SDK type leaks into aionui-api-types.
  • The availability probe sends it via registry.catalog_sender(). The registry parameter was already threaded into run_probe and unused (_registry).

Safety

Additive by construction:

Case Behaviour
Agent advertises nothing Projection returns None — nothing is sent
A real session already filled the catalog apply_handshake skips None fields, so a probe never blanks it
Agent not logged in LIVE-verified: stakpak / poolside answer session/new with -32000 Authentication required and no payload. The Auth branch yields None, so unauthenticated agents correctly stay empty until logged in and re-probed

No schema change

The columns and the write path already exist; this only stops discarding data. Values land at runtime on the next probe — better than a seed migration, since a probe reads the catalog of the version actually installed on the user's machine rather than pinning one capture.

Tests

  • probe_projection_matches_event_projection (new) — probe route vs event route produce identical blobs for the same session state; also pins that an empty/absent catalog produces no write
  • catalog_partial_covers_session_fields — unchanged, still pins the event route

84 targeted tests green (catalog / availability / probe / registry). cargo clippy and cargo fmt clean. Full cargo nextest intentionally not run.

The agent picker showed no modes / models / config options for agents that
had never been opened in a conversation, even ones the availability check
reported `online`.

The probe is not a shallow liveness check: it opens a real `session/new` so an
auth-gated agent surfaces `auth_required` instead of silently looking healthy.
That response carries the agent's advertised modes, models and config options —
and the probe discarded all of it:

    Ok(_) => ProbeOutcome::Ok,

The columns were then filled only when a live conversation opened a session and
`emit_snapshot_events` broadcast to `CatalogForwarder`. So the data was fetched,
proven, and thrown away, and the picker stayed empty until first use. On this
machine that left 27 of 39 ACP rows blank while probing online.

Keep the response and persist it through the same channel the live session uses:

- `catalog_partial_from_session_new` projects modes / models / config options
  into an `AgentHandshake` directly. It lives beside `catalog_partial_from_event`
  and a parity test feeds one session state through both routes and asserts the
  blobs are byte-identical — they write the same columns, so which route ran
  last must not change what the picker reads. `available_commands` has no
  counterpart: it arrives as a session/update notification, never in the
  `session/new` response.
- `try_connect_custom_agent_with_catalog` returns that partial alongside the
  verdict. The existing `try_connect_custom_agent` signature is unchanged, so
  the try-connect endpoint and test-on-save are unaffected, and no SDK type
  leaks into `aionui-api-types`.
- The availability probe sends it via `registry.catalog_sender()` — the
  `registry` parameter was already threaded in and unused.

Additive by construction: `apply_handshake` skips `None` fields, so a probe can
never blank a catalog a real session filled in; an agent advertising nothing
sends nothing; and an auth-gated probe (LIVE: stakpak / poolside answer
`session/new` with `-32000 Authentication required` and no payload) yields
`None`, so unauthenticated agents correctly stay empty until they are logged in
and re-probed.

No schema change: the columns and the write path already exist. Values land at
runtime on the next probe, which is better than a seed — a probe reads the
catalog of the version actually installed on the user's machine.
@kaizhou-lab
kaizhou-lab merged commit 2438095 into main Jul 31, 2026
6 checks passed
@kaizhou-lab
kaizhou-lab deleted the fix/probe-persists-session-catalog branch July 31, 2026 09:43
piorpua pushed a commit that referenced this pull request Jul 31, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.1.56](v0.1.55...v0.1.56)
(2026-07-31)


### Features

* **project:** hide OS-junk and VCS-internal noise from listings
([#727](#727))
([6197117](6197117))


### Bug Fixes

* **agents:** persist the catalog the availability probe already fetched
([#735](#735))
([2438095](2438095))
* **ai-agent:** token usage for the direct-CLI backends (claude / codex)
([#733](#733))
([93a24f4](93a24f4))
* **conversation:** request plaintext thinking from claude, drop blank
thought cards ([#731](#731))
([9cd4fb9](9cd4fb9))
* **project/monitor:** attribute watched-subdir events to parent so tree
reflects dir delete/rename
([#734](#734))
([8494a1f](8494a1f))
* **session:** settle cancelled workflows and stop per-turn pump state
leaking across turns
([#732](#732))
([b8a4a88](b8a4a88))
* **team:** derive team capability from probed MCP transports, not a
stored veto ([#725](#725))
([d60fc62](d60fc62))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
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.

1 participant