Repository navigation
Conversation
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.
There was a problem hiding this comment.
🔵 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 rungh pr ready --undo.
Click "Ready for review" or rungh pr readyto 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
left a comment
There was a problem hiding this comment.
🤖 #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:
- A
--no-pingsconnector still asks the feed for agent events.connectActorTypesasks for agent events wheneverAllowedAgents()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 aspings_not_heard. Counting Ping agents only whenpolicy.HearsPingsis set (main's policy.go) would avoid that. - 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.
TestOneAgentsDailyCapSpansItsPingAndItsProjectspasses 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. - SKILL.md's
--allow-agent-in-pingrow says "Restart the connector after changing it". With #936 there can be several. It should say the one that answers Pings (the onebasecamp connect statusreports as hearing them).
A rebase would also pick up main's HearsPings test fixtures. Not yet exercised live with two agents in one Ping.
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-agentincluded (#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 agentreturned the other agent'schat.line.createdand itsboost.created. With--actor-types personthey are absent, which is why a connector with no--allow-agentnever sees them.basecamp chat line <url>read that line as the connecting agent:bucket.typeCircle,creator.personable_typeAgent.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 postresolves 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
agentsbesideparticipantsin a Ping's entry:"pings": {"<ping-id>": {"participants": [...], "agents": [...]}}. Setup writes no key when there is no entry, as before.--allow-agentstill 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 isagent_not_allowedin every project and every other Ping.not_addressed, even in a direct Ping. Its role isparticipantandowneris false, so it is never the owner's word, and a participant's request can't open a pull request or change the connector.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.actor_types=agent,person) when any agent is allowed, anywhere or in a Ping.--allow-agentor a Ping entry, as a participant anywhere.--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 showlists a Ping's agents.connect setup --help, the basecamp-connectSKILL.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), andsubagent.md(a Ping participant may be an agent) say so.Older CLIs:
setup.Parserefuses unknown keys, so a CLI without this refuses a connect.json with a Pingagentsentry and runs nothing. Admission's own reader ignores the key, so it can't widen anything.Why this design
--allow-in-ping, rather than letting--allow-agentreach Pings. Turning on--allow-agentfor a project shouldn't open every Ping the two agents happen to share.--allow-agentas 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 laneagent_not_allowed, poll laneuntrusted_performer, no reads), an agent let into another Ping, an--allow-agentagent 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, andVerifyPingsfor 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.TestConnectFeedAsksForAgentsOnlyWhenOneIsAllowedcovers the Ping case.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 bymatchPingAgent:Decidetakesleast(gate role, author role), and the gate already sets participant, so changing either one alone can't make it an operator's.bin/cipasses locally.Not done here
--allow-agent-in-ping <ping>:<agent>and restart the connector, then have that agent post@<this agent> ...withbasecamp pings post.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-agentincluded.--allow-agent-in-ping <ping-id>:<agent-id>and--disallow-agent-in-ping, per Ping;--allow-agentstill reaches no Ping.connect.jsonPing entries gain anagentskey besideparticipants. Older CLIs refuse the key and run nothing.Written for commit 50a98da. Summary will update on new commits.