Skip to content

Let an agent give the connector work in one group Ping - #923

Open
robzolkos wants to merge 1 commit into
mainfrom
agent-mentions-in-ping
Open

robzolkos wants to merge 1 commit into
mainfrom
agent-mentions-in-ping

Conversation

@robzolkos

@robzolkos robzolkos commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Another agent can now give this one work in a group Ping, when the operator lets it into that one Ping. Today every agent's line in a Ping is discarded at the gate, --allow-agent included (#914).

Does Basecamp deliver another agent's Ping line?

Yes, so this is a CLI change, not a bc3 one. Checked live on 2026-10-20, as an Agent identity, in a group Ping that holds three people, the connecting agent and another person's agent (an Agent person a member added to the Ping):

  • basecamp events poll --buckets <ping> --actor-types agent returned the other agent's chat.line.created and its boost.created. With --actor-types person they are absent, which is why a connector with no --allow-agent never sees them.
  • basecamp chat line <url> read that line as the connecting agent: bucket.type Circle, creator.personable_type Agent.
  • basecamp pings show <ping> lists the other agent among the Ping's people, so agents can be members of a group Ping.

bc3 master (Chats::LinesController, AgentPrincipals#ensure_agent_principal_in_a_project_or_ping) lets an agent create a rich-text line in an active Ping it is in. Nothing there refuses a line that carries a mention. Not checked live: an agent actually posting a line that @mentions another agent in a Ping. basecamp pings post resolves mentions among the Ping's people from the Ping read, which agents may make. The pingable-people listing (/circles/people) is refused to agents, and that is the "Basecamp refuses an agent's mention in Campfire" the skill describes.

What changes

basecamp connect setup -P <agent> --allow-agent-in-ping <ping-id>:<agent-id>      # repeatable
basecamp connect setup -P <agent> --disallow-agent-in-ping <ping-id>:<agent-id>
  • connect.json gains agents beside participants in a Ping's entry: "pings": {"<ping-id>": {"participants": [...], "agents": [...]}}. Setup writes no key when there is no entry, as before.
  • Per Ping, explicit. --allow-agent still reaches no Ping. An agent is heard in a Ping only when that Ping's entry names it, and only there. An agent named only in a Ping entry is agent_not_allowed in every project and every other Ping.
  • Mention only, as a participant. The gate opens the mentioned rule alone. In the Ping read, the line must @mention this agent, by Person id, and be written by the same agent that performed it, read back as an Agent. Without a mention it is not_addressed, even in a direct Ping. Its role is participant and owner is false, so it is never the owner's word, and a participant's request can't open a pull request or change the connector.
  • The same loop caps. An agent's Ping mention carries the agent caps. The thread cap counts every allowed agent's mentions in the Ping's conversation (ping:<id>), and the daily cap counts one agent across its projects and its Pings. The caps' agent list is now every allowed agent (Policy.AllowedAgents), so a Ping's agents are counted.
  • The feed asks for agents (actor_types=agent,person) when any agent is allowed, anywhere or in a Ping.
  • Validate refuses a Ping agent that is the agent itself, not a Person id, the operator or someone the allowlist names. It still refuses any allowed agent, from --allow-agent or a Ping entry, as a participant anywhere.
  • Setup reads the Ping as the agent, as for --allow-in-ping. It refuses a Ping the agent can't read, an agent that isn't among its people, a person (people use --allow-in-ping), and the agent itself. A recorded agent that has since left, or whose Ping can't be read, is a warning.
  • connect show lists a Ping's agents. connect setup --help, the basecamp-connect SKILL.md (setup table, the "Another agent in one Ping" section, and the owner-only list, rewritten as "every --allow* and --disallow* flag" to stay inside the 14,000-character compaction budget at 13,992), and subagent.md (a Ping participant may be an agent) say so.

Older CLIs: setup.Parse refuses unknown keys, so a CLI without this refuses a connect.json with a Ping agents entry and runs nothing. Admission's own reader ignores the key, so it can't widen anything.

Why this design

  • A ping-scoped allow that mirrors --allow-in-ping, rather than letting --allow-agent reach Pings. Turning on --allow-agent for a project shouldn't open every Ping the two agents happen to share.
  • The Ping allow doesn't require --allow-agent as well. An agent can be heard in one Ping and nowhere else, the same as a person let into one Ping.

Tests, and trying to break it

  • admission/ping_agents_test.go: a mention is admitted on both lanes as a participant, with caps, in operator and project trust. These cases are discarded: no mention (group and direct Ping), a mention of someone else, an agent with no entry (push lane agent_not_allowed, poll lane untrusted_performer, no reads), an agent let into another Ping, an --allow-agent agent with no entry (no_route), the Ping agent in a served project, in an unserved project, and in a served project keyed as a Ping, a bucket the Ping read says is no Ping, a boost in the Ping, words written by the operator, by a second Ping agent, by this agent or by a non-Agent, and a line performed for the operator. Two agents in one Ping trip the thread cap together at N+1, and it resets after the window. One agent's daily cap spans its project and its Ping. Validate refusals, AllowedAgents, and parsing.
  • setup/pings_test.go: apply, disallow, entries shared with participants, round trip, Validate refusals, and VerifyPings for agents.
  • TestConnectSetupLetsAnAgentIntoOnePing: the command allows, shows, keeps and disallows. It refuses a person, the agent itself, an agent not in the Ping, a Ping the agent isn't in, the operator, malformed ids, and an agent passed to --allow-in-ping, each checked to fail for its own reason, and a refused run writes nothing. TestConnectFeedAsksForAgentsOnlyWhenOneIsAllowed covers the Ping case.
  • Mutation check: 16 single-line mutations in the gate, matchPingAgent, the caps' agent list, Validate, AllowedAgents, the feed filter, setup's agent check and apply. 15 of 16 were caught. The miss is the agent's role returned by matchPingAgent: Decide takes least(gate role, author role), and the gate already sets participant, so changing either one alone can't make it an operator's.
  • bin/ci passes locally.

Not done here

  • Not run live end to end: another agent @mentioning this one in a Ping with a connector on this branch. To try it, run --allow-agent-in-ping <ping>:<agent> and restart the connector, then have that agent post @<this agent> ... with basecamp pings post.
  • For a two-way conversation, the other agent's connector needs the same opt-in for this one.

Summary by cubic

Lets another agent give the connector work in a group Ping, when the operator lets it into that specific Ping. Previously every agent's line in a Ping was discarded at the gate, --allow-agent included.

  • Adds --allow-agent-in-ping <ping-id>:<agent-id> and --disallow-agent-in-ping, per Ping; --allow-agent still reaches no Ping.
  • The allowed agent is heard only by @mention in words it wrote itself, as a participant request, never the owner's word.
  • Agent caps apply: the thread cap counts all allowed agents' mentions in a Ping's conversation, and the daily cap spans one agent across projects and Pings.
  • connect.json Ping entries gain an agents key beside participants. Older CLIs refuse the key and run nothing.
  • Feed asks for agent events when any agent is allowed anywhere, including a Ping. Setup validates that the agent is in the Ping and is an Agent.
  • Verified against Basecamp: Basecamp delivers another agent's Ping lines, so this is a CLI-only change. End‑to‑end live testing with two agents conversing is not yet done.

Written for commit 50a98da. Summary will update on new commits.

View guided diff Turn on auto-fix

connect setup --allow-agent-in-ping <ping-id>:<agent-id> lets another agent
wake this one in that Ping alone, by @mention, as a participant's request held
to the agent caps. --allow-agent still reaches no Ping.
@robzolkos
robzolkos requested a review from a team as a code owner October 20, 2026 10:43
Copilot AI balanced review requested due to automatic review settings October 20, 2026 10:43
@github-actions github-actions Bot added commands CLI command implementations tests Tests (unit and e2e) skills Agent skills labels Oct 20, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The authorization expansion warrants human review, especially because the live agent-to-agent Ping flow remains unverified end to end.

No open issues 🎉

What changed in this PR

Extends connector Ping support so explicitly allowed agents can submit mention-only requests in a specific Ping, with participant permissions and existing agent caps.

Changes:

  • Adds per-Ping agent configuration, setup flags, validation, and display.
  • Updates admission and feed filtering while preserving project/Ping isolation.
  • Adds tests and documents the authorization rules.

[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

File Description
skills/​basecamp-connect/​subagent.md Clarifies agent participants in Pings.
skills/​basecamp-connect/​SKILL.md Documents scoped agent access and setup.
internal/​connector/​setup/​pings_test.go Tests configuration and agent verification.
internal/​connector/​setup/​file.go Updates Ping configuration comments.
internal/​connector/​setup/​checks.go Verifies allowed agents belong to the Ping.
internal/​connector/​setup/​apply.go Applies per-Ping agent additions and removals.
internal/​connector/​handoff.go Updates handoff documentation for agent participants.
internal/​connector/​admission/​verdict.go Checks agent authorship, mentions, and caps.
internal/​connector/​admission/​policy.go Adds Ping agents and policy validation.
internal/​connector/​admission/​ping_agents_test.go Tests admission boundaries and shared caps.
internal/​connector/​admission/​gate.go Gates agent events by Ping-specific permission.
internal/​connector/​admission/​doc.go Documents scoped agent admission.
internal/​commands/​connect.go Adds setup flags, help, and agent display.
internal/​commands/​connect_setup_test.go Tests setup commands and feed filtering.
internal/​commands/​connect_setup_guided.go Recognizes new policy flags.
internal/​commands/​connect_run.go Requests agent events when allowed in Pings.

Review effort: Balanced

@jeremy jeremy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 #936 landed on main (8dd1cfd), so I checked this PR against it. It merges cleanly, and the connector tests plus internal/commands -run 'Ping|Connect|Feed' pass on the combined tree. The two fit together: #936's pings_not_heard discard runs on the Ping read before this PR's agent path, so an allowed agent's Ping mention is answered only by the connector that hears Pings. Three loose ends, none blocking:

  1. A --no-pings connector still asks the feed for agent events. connectActorTypes asks for agent events whenever AllowedAgents() is non-empty, and that now includes Ping-only agents. On a connector that doesn't hear Pings, each such line costs a Ping read and is then discarded as pings_not_heard. Counting Ping agents only when policy.HearsPings is set (main's policy.go) would avoid that.
  2. The description says the daily cap "counts one agent across its projects and its Pings". Since #936, that's only true on the connector that hears Pings. A project-only connector keeps its own count, since each connector has its own ledger. TestOneAgentsDailyCapSpansItsPingAndItsProjects passes because it runs on one policy. Worth rewording, so nobody reads it as a cap shared across connectors. This is from reading the code; I haven't tested it across two connectors.
  3. SKILL.md's --allow-agent-in-ping row says "Restart the connector after changing it". With #936 there can be several. It should say the one that answers Pings (the one basecamp connect status reports as hearing them).

A rebase would also pick up main's HearsPings test fixtures. Not yet exercised live with two agents in one Ping.

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

commands CLI command implementations skills Agent skills tests Tests (unit and e2e)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants