Skip to content

Let a coworker be pinned to the top of the Agents screen - #650

Open
asasemahmed wants to merge 1 commit into
CopilotKit:mainfrom
asasemahmed:feat/agent-pin
Open

asasemahmed wants to merge 1 commit into
CopilotKit:mainfrom
asasemahmed:feat/agent-pin

Conversation

@asasemahmed

Copy link
Copy Markdown
Contributor

What this changes

The other half of #323, after #647. A coworker's Manage tab has a Pin button beside Hide, and
pinned coworkers move into a Pinned section at the top of /agents. With this, the three things
#323 asks for (pin, hide, duplicate) all exist.

It mirrors hiding exactly:

  • Storage is a pinned_at column beside hidden_at in agent_preferences (migration
    0047_agent_pinning, one ADD COLUMN). The row is per person and per coworker, so pinning
    changes nothing for anyone else.
  • setPinned has the same shape as setHidden: the same accessibility check, then an upsert that
    sets only its own column. Pinning a hidden coworker keeps it hidden, and it comes back pinned
    when it is unhidden.
  • POST /api/agents/:id/pin and /unpin record bot.pinned / bot.unpinned, beside
    bot.hidden / bot.unhidden.
  • The profile carries pinned: boolean next to hidden.

On the screen, a pinned coworker appears once, in Pinned, rather than also in its roster. When
pinning empties a roster, that roster says "Your agents are all pinned above." instead of "You
don't have any agents created.", which would be false. The Pinned section renders nothing while
loading, on failure, or when nothing is pinned, so the existing loading and error states are
unchanged.

Not included: the limit on pinned coworkers the issue suggests. Nothing here needs one, and it is
easy to add if you want it.

Where it runs

  • New state that outlives a request? agent_preferences.pinned_at, in Postgres.
  • What happens on the second replica? The same row is read and written by whichever
    replica answers; nothing is held in process.
  • Anything serialised? Two concurrent pins of the same coworker by the same person meet on
    the (user_id, agent_id) primary key: the upsert makes the second an update of the first
    rather than a conflict. Each statement sets only its own column, so a pin and a hide racing
    each other both survive.
  • Anything fanned out to a browser? No. The mutation invalidates agentKeys.all, as Hide
    does.
  • New listener, port, or schedule? No.

Boundary and audit

  • Every acting call still goes through the gateway: pinning is not an acting call; it is the
    same kind of personal list preference as hiding.
  • New refusals and new failures each write a row: pin and unpin record bot.pinned /
    bot.unpinned on success. A refused pin (a coworker the caller cannot see) writes nothing,
    as a refused hide does, and answers 404.
  • Nothing new is trusted from the client: the agent id is checked against what the caller may
    see before anything is written.

Changelog

  • An entry in CHANGELOG.md under Unreleased, and pin added to the /agents row in the
    README.

Proof

  • Store: agent-profile-store.integration.test.ts gains "stores pinning per user, independently
    of hiding" (pin, hide while pinned, re-pin while hidden, unhide, unpin; both columns end null)
    and "refuses to pin a coworker the caller cannot see". The file passes against Postgres: 25 pass.
  • Routes and audit: agent-routes.test.ts (the lifecycle and DTO tests now include pin and
    unpin) and bot-lifecycle-audit.test.ts (bot.pinned, bot.unpinned).
  • App: agent-api-path.test.ts drives the new mutation through the real routes for every awkward
    id, and agent-roster-error.test.tsx gains three tests for the Pinned section, including the
    "all pinned" sentence. The other agent-list fixtures gain pinned: false.
  • bunx drizzle-kit check passes, and the drift probe (drizzle-kit generate) writes nothing.
  • bun run typecheck, biome lint, biome format: clean. 119 tests pass across the five files
    above.
  • Manually, on a local stack with the migration applied: pinned Knowledge from its Manage tab. The
    button turned to Unpin, /agents read Pinned, Your agents, Explore agents with Knowledge only
    under Pinned, and the trail held bot.pinned for knowledge. Unpinning it removed the section
    and put it back under Explore agents.

A coworker's Manage tab has a Pin button beside Hide, and pinned
coworkers move into a Pinned section at the top of /agents. Pinning is
personal, like hiding: it is a pinned_at column next to hidden_at in
agent_preferences, and each write sets only its own column, so pinning
a hidden coworker keeps it hidden and it comes back pinned when it is
unhidden.

POST /api/agents/:id/pin and /unpin record bot.pinned and bot.unpinned
on the trail, as hiding does.

Refs CopilotKit#323

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant