Skip to content

[console] Agent-to-agent connections have no UI — the pump is invisible #182

Closed

Description

@serge-ivo

The "pump" (agent-to-agent connections, agent_connections, migration 0056) has no console UI at all. Creating a connection is a raw API call:

POST /v1/instances/:id/connections { eventType, targetInstanceId, action, config }

The delivery outbox added in migration 0058 is API-only too:

GET  /v1/instances/:id/connections/deliveries?status=pending|delivered|dead
POST /v1/instances/:id/connections/deliveries/:did/replay

So the reliability work — retry, dead-lettering, replay, routing filters, trace correlation — is invisible to the person who needs it. A chain that stalls is discoverable only by curl.

This is the difference between the chain being a product feature and being an internal capability. The lead chain (Lead Finder -> Website Builder -> Outreach) cannot be assembled by a user today.

Scope

  • Connections list/create/delete on the instance Settings surface: source event, target instance, action, and the routing filter.
  • Filter editor reusing the [{field,op,value}] vocabulary (the same predicate language as the filter step) with create-time validation surfaced inline.
  • Delivery view: pending / delivered / dead with attempt count, next attempt, last error, and a Replay action for dead letters.
  • Show the correlation trace id and link a delivery to the child run it started.

Acceptance

  • A user can wire Lead Finder -> Website Builder -> Outreach entirely from the console.
  • A dead letter is visible with its reason and can be replayed in one click.
  • An invalid filter is rejected at create time with a readable message.
  • Owner-scoped throughout.

Related: #19 covers the same gap for triggers; this is the connections half.

Activity

  1. serge-ivo commented on Aug 6, 2026

    @serge-ivo
    ContributorAuthor

    Code audit update: this is partially shipped, but still not complete. Current Settings > Teamwork UI now supports listing/creating/deleting connections, a simple routing filter, dead-letter listing, and replay. The remaining gap from this issue is broader delivery observability: pending/delivered/dead views, trace/run links, and a fuller operator UX for debugging message flow. Keeping open.

  2. serge-ivo commented on Aug 6, 2026

    @serge-ivo
    ContributorAuthor

    Shipped in e16af12.

    First, a correction to the ticket. "No console UI at all" was stale — the Teamwork card on the instance Settings surface (store/console/src/tabs/TeamworkSection.tsx, commits 802d764 / f5f521a / dfc5c91) already did connections list/create/delete, a one-clause filter editor, and dead-letter replay. What was actually missing was everything that makes the pump legible, plus one bug that made the headline use case unreachable.

    The bug. The filter editor sent every value as a raw string. The server compares gt/gte/lt/lte numerically and rejects a string at create time, and eq is strict (===), so a text-typed "4" never equals a numeric 4 in the payload. "Only Sydney leads rated 4+" — the canonical example the routing filter exists for — was therefore impossible to create from the console, and the near-miss variants created fine and then silently dropped every event. Values are now typed (numbers, true/false/null), with quoting ("2000") as the escape hatch for a postcode that must stay text. The editor also takes several clauses with an all/any toggle; one AND-ed clause could not express the two-clause example either.

    What shipped

    • Connection rows state their own health, derived from the deliveries they produced: N undelivered / N retrying / N delivered. The negative is the point: deliverEvent writes an outbox row only for a payload that passes the filter, so a filtered connection with zero deliveries has never once fired — surfaced as "nothing has matched this filter yet". That is the failure mode where a chain is stopped and every row still reads "enabled".
    • Delivery log across every status, not just dead. Pending rows say when the next attempt lands and how many are left ("2 attempts failed, retrying in 15m · 3 left"); delivered rows admit when they only succeeded on a retry; an account-wide headline says "N events never arrived" before you expand anything. Replay stays one click on a dead letter.
    • Rows carry the source agent (the listing is account-wide) and are labelled "via trigger" when they came from a trigger — the outbox is shared (Unify trigger retries onto the connection-delivery outbox (do not build a second mechanism) #17), and a stuck trigger is not the same problem as a stuck connection.
    • Trace correlation: a delivery's trace id expands inline into the run it started, reading /v1/instances/:id/trace?trace_id=. The emitting run rides onto the delivery and chain.link is logged under the parent trace, so this is what joins two agents' runs into one story.
    • API: GET …/connections/deliveries now returns source and sourceInstanceId. Without them an account-wide row from a shared outbox is unreadable.

    Tests. The rules that turn a row into a sentence are pure and live in store/console/src/lib/teamwork.ts — 41 unit tests in teamwork.test.ts (filter parsing/summarising, literal typing, status grouping, countdown, connection health). Suite: 205 files / 2903 tests green, pnpm typecheck green, 47/47 console e2e green including the 375px no-overflow guard. One new e2e covers the acceptance path end to end: filter summary on the row, dead letter with its reason, replay in one click.

    Deliberately left out

    • No connection enable/disable toggle. There is no PATCH route; enabled is only ever set to 1 at create. The UI reads and renders the flag ("disabled — it routes nothing") so the column is honest, but wiring a toggle means a new route, which belongs in its own change.
    • No config.params editor. buildConnectionConfig supports the field and it is unit-tested, but nothing in the UI writes it yet — a static-params form needs a schema to key against, and guessing one would be worse than the API call.
    • No standalone trace/run viewer. The console has no trace route at all, so a delivery expands its own trace inline rather than deep-linking somewhere that does not exist. A proper trace surface is its own ticket.
    • The log is capped at the 200 most recent deliveries. "Nothing has matched this filter yet" is therefore a statement about the recent log, not all history — worded accordingly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    connectorsConnector + tool frameworkfrontendFrontend / UI work

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions