Skip to content

feat(relay): add shared-room agent delegation#60

Merged
zikolach merged 11 commits into
mainfrom
feat/shared-room-agent-delegation
May 17, 2026
Merged

feat(relay): add shared-room agent delegation#60
zikolach merged 11 commits into
mainfrom
feat/shared-room-agent-delegation

Conversation

@zikolach
Copy link
Copy Markdown
Owner

Summary

  • add OpenSpec change add-shared-room-agent-delegation
  • add shared-room delegation domain helpers, task cards, lifecycle/state, peer trust, eligibility, loop prevention, and task-scoped approval grant helpers
  • wire opt-in delegation handling into Discord, Slack, and Telegram shared-room runtimes
  • add claim-to-prompt handoff, terminal task reporting, stale in-flight task handling, config/diagnostics, docs, and tests
  • keep approval-gate execution as an integration seam pending add-relay-approval-gates

Validation

  • npm run typecheck
  • npm test
  • npm run openspec:validate
  • openspec validate add-shared-room-agent-delegation --strict

Copilot AI review requested due to automatic review settings May 16, 2026 10:17
@zikolach zikolach force-pushed the feat/shared-room-agent-delegation branch from 2c4fdbf to 336b73a Compare May 16, 2026 10:20
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds opt-in shared-room agent delegation so machine bots can create visible task cards, claim delegated work, persist task state/audit, and report lifecycle results across Telegram, Discord, and Slack.

Changes:

  • Adds delegation domain, runtime, approval, command/card, config, diagnostics, state, and adapter integration.
  • Adds OpenSpec requirements/design/tasks and user/admin documentation.
  • Adds unit and runtime tests for parsing, lifecycle, config, state, diagnostics, and Discord adapter/runtime behavior.

Reviewed changes

Copilot reviewed 46 out of 46 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
vitest.config.ts Configures Vitest serialization/timeouts.
extensions/relay/core/agent-delegation.ts Adds delegation task model, lifecycle, trust, eligibility, redaction, and rendering helpers.
extensions/relay/core/agent-delegation-runtime.ts Adds shared ingress evaluation and prompt handoff helpers.
extensions/relay/core/agent-delegation-approval.ts Adds task/session scoped approval grant helpers.
extensions/relay/core/types.ts Extends relay config/state types for delegation.
extensions/relay/core/index.ts Exports new delegation helpers.
extensions/relay/commands/delegation.ts Adds delegation command/action parsing and task card rendering.
extensions/relay/commands/remote.ts Adds delegation help text.
extensions/relay/state/tunnel-store.ts Persists delegation tasks and audit history.
extensions/relay/state/schema.ts Extends relay store schema.
extensions/relay/state/migration.ts Normalizes delegation state fields.
extensions/relay/config/schema.ts Adds delegation config schema types.
extensions/relay/config/loader.ts Resolves and validates delegation config.
extensions/relay/config/tunnel-config.ts Wires delegation into legacy runtime config.
extensions/relay/config/diagnostics.ts Reports delegation readiness/warnings.
extensions/relay/adapters/discord/adapter.ts Allows bot-authored Discord messages when delegation is enabled.
extensions/relay/adapters/discord/runtime.ts Handles Discord delegation messages/actions and task completion reporting.
extensions/relay/adapters/slack/runtime.ts Handles Slack delegation messages/actions and task completion reporting.
extensions/relay/adapters/telegram/runtime.ts Handles Telegram delegation commands and task completion reporting.
README.md Documents delegation commands and behavior.
docs/adapters.md Updates adapter/shared-room delegation notes.
docs/config.md Documents delegation configuration.
docs/shared-room-parity.md Updates platform parity for delegation/shared rooms.
docs/testing.md Adds manual delegation smoke checks.
openspec/changes/add-shared-room-agent-delegation/.openspec.yaml Adds OpenSpec metadata.
openspec/changes/add-shared-room-agent-delegation/proposal.md Defines proposal rationale and impact.
openspec/changes/add-shared-room-agent-delegation/design.md Defines design goals, decisions, risks, and migration plan.
openspec/changes/add-shared-room-agent-delegation/tasks.md Tracks implementation tasks.
openspec/changes/add-shared-room-agent-delegation/specs/relay-agent-delegation/spec.md Adds delegation requirements.
openspec/changes/add-shared-room-agent-delegation/specs/shared-room-machine-bots/spec.md Adds shared-room bot routing requirements.
openspec/changes/add-shared-room-agent-delegation/specs/messenger-relay-sessions/spec.md Adds delegated prompt delivery requirements.
openspec/changes/add-shared-room-agent-delegation/specs/relay-configuration/spec.md Adds delegation configuration requirements.
openspec/changes/add-shared-room-agent-delegation/specs/relay-broker-topology/spec.md Adds delegation broker ownership requirements.
tests/relay/agent-delegation.test.ts Tests delegation domain helpers.
tests/relay/agent-delegation-runtime.test.ts Tests runtime ingress helpers.
tests/relay/agent-delegation-approval.test.ts Tests approval grant helpers.
tests/relay/delegation-commands.test.ts Tests command parsing and card rendering.
tests/relay/config-loader.test.ts Tests delegation config loading/validation.
tests/relay/config-diagnostics.test.ts Tests delegation diagnostics.
tests/state-store.test.ts Tests delegation state migration/audit/stale handling.
tests/discord-runtime.test.ts Tests Discord delegation runtime flow.
tests/discord-adapter.test.ts Tests Discord bot-message delegation ingress.
tests/relay/binding-authority.test.ts Updates test state fixtures.
tests/relay/requester-file-delivery.test.ts Updates test state fixtures.
tests/relay/local-disconnect.test.ts Stabilizes env isolation for tests.
tests/integration.test.ts Stabilizes config path env for integration tests.
Comments suppressed due to low confidence (3)

extensions/relay/adapters/discord/runtime.ts:231

  • When delegation parsing returns an ignored result such as not-delegation or not-eligible, this falls through to the normal shared-room routing path. Because the adapter now admits bot-authored Discord messages whenever delegation is enabled, ordinary bot output can be treated as a regular authorized shared-room prompt in guilds with broad allow settings, violating the requirement that bot-authored non-delegation output remain inert.
    }

extensions/relay/adapters/slack/runtime.ts:398

  • When delegation parsing returns an ignored result such as not-delegation or not-eligible, this falls through to the normal Slack routing path. The live fallback now normalizes bot-authored Slack messages for delegation, so ordinary peer-bot output can be routed as a regular channel prompt when user allow-lists are broad, instead of remaining inert.
      await this.handlePairing(message, pairingCode);
      return;

extensions/relay/adapters/telegram/runtime.ts:2207

  • After reporting a completed delegation task, this continues into the normal Telegram output path. Delegated task results can therefore be delivered to the route's existing active output binding in addition to the originating task room, which breaks the task-scoped output boundary for delegated work.
    );

Comment thread extensions/relay/core/agent-delegation-runtime.ts Outdated
Comment thread extensions/relay/core/agent-delegation-runtime.ts
Comment thread extensions/relay/core/agent-delegation.ts Outdated
Comment thread extensions/relay/state/tunnel-store.ts Outdated
Comment thread extensions/relay/adapters/discord/runtime.ts Outdated
Comment thread extensions/relay/adapters/slack/runtime.ts Outdated
Comment thread extensions/relay/adapters/slack/runtime.ts Outdated
Comment thread extensions/relay/adapters/slack/runtime.ts
Comment thread extensions/relay/config/tunnel-config.ts Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 48 out of 48 changed files in this pull request and generated 15 comments.

Comment thread extensions/relay/core/agent-delegation-runtime.ts Outdated
Comment thread extensions/relay/core/agent-delegation-runtime.ts Outdated
Comment thread extensions/relay/state/tunnel-store.ts Outdated
Comment thread extensions/relay/adapters/telegram/runtime.ts Outdated
Comment thread extensions/relay/adapters/discord/runtime.ts
Comment thread extensions/relay/adapters/discord/runtime.ts
Comment thread extensions/relay/adapters/slack/runtime.ts
Comment thread extensions/relay/adapters/telegram/runtime.ts
Comment thread extensions/relay/config/loader.ts
Comment thread extensions/relay/core/agent-delegation-runtime.ts
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 48 out of 48 changed files in this pull request and generated 6 comments.

Comment thread extensions/relay/adapters/slack/runtime.ts
Comment thread extensions/relay/adapters/telegram/runtime.ts Outdated
Comment thread extensions/relay/adapters/discord/runtime.ts Outdated
Comment thread extensions/relay/adapters/slack/runtime.ts Outdated
Comment thread extensions/relay/adapters/slack/runtime.ts Outdated
Comment thread extensions/relay/core/agent-delegation-runtime.ts
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 50 out of 50 changed files in this pull request and generated 6 comments.

Comment thread extensions/relay/adapters/telegram/runtime.ts Outdated
Comment thread extensions/relay/adapters/discord/adapter.ts
Comment thread extensions/relay/core/agent-delegation-approval.ts
Comment thread extensions/relay/commands/delegation.ts Outdated
Comment thread extensions/relay/state/tunnel-store.ts Outdated
Comment thread extensions/relay/state/tunnel-store.ts
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 50 out of 50 changed files in this pull request and generated 6 comments.

Comments suppressed due to low confidence (11)

extensions/relay/adapters/discord/runtime.ts:516

  • This ignores the task returned by upsertDelegationTask, so a compare-and-set conflict can be silently lost. If a human cancels or otherwise mutates the task while prompt delivery is in flight, the store will keep the newer state but this code still renders the stale started card, misleading users and leaving the active-session map associated with a task that was not actually advanced.
    const started = transitionDelegationTask(claimedTask, { kind: "start", summary: `Started in ${route.sessionLabel}.` });
    const next = started.ok ? started.task : claimedTask;
    await this.store.upsertDelegationTask(next);
    await this.sendDelegationTaskCard(message, next);

extensions/relay/adapters/slack/runtime.ts:620

  • This ignores the task returned by upsertDelegationTask, so a concurrent cancellation or mutation during prompt handoff can be rejected by the store while the runtime still renders the stale started state. Use the returned task or tryUpsertDelegationTask result before announcing the lifecycle transition.
    const started = transitionDelegationTask(claimedTask, { kind: "start", summary: `Started in ${route.sessionLabel}.` });
    const next = started.ok ? started.task : claimedTask;
    await this.store.upsertDelegationTask(next);
    await this.sendDelegationTaskCard(message, next);

extensions/relay/adapters/telegram/runtime.ts:1148

  • This ignores the task returned by upsertDelegationTask, so if a cancellation or other mutation races with prompt handoff, the store can reject this stale started update while the runtime still posts it as if it applied. Check the returned/current task before rendering the task card.
    const started = transitionDelegationTask(claimedTask, { kind: "start", summary: `Started in ${route.sessionLabel}.` });
    const next = started.ok ? started.task : claimedTask;
    await this.store.upsertDelegationTask(next);
    await this.sendTelegramDelegationTaskCard(message, next);

extensions/relay/adapters/discord/runtime.ts:532

  • Terminal updates also ignore the task returned by upsertDelegationTask. If a user cancels/declines the task after it is read but before this write, the store rejects the stale completion via conflict detection, yet this code still posts the stale completed/failed card to the room.
    const next = transition.ok ? transition.task : task;
    await this.store.upsertDelegationTask(next);
    await this.adapter.sendText({ channel: DISCORD_CHANNEL, conversationId: next.room.conversationId, userId: "delegation" }, renderDelegationTaskCard(next, { commandPrefix: "relay task" }).text);

extensions/relay/adapters/slack/runtime.ts:636

  • Terminal updates ignore the task returned by upsertDelegationTask. A racing cancellation or other lifecycle mutation can make the store keep the newer state while this code still posts a stale completed/failed task card to the Slack room/thread.
    const next = transition.ok ? transition.task : task;
    await this.store.upsertDelegationTask(next);
    await this.adapter.sendText({ channel: SLACK_CHANNEL, conversationId: next.room.conversationId, userId: "delegation", ...(next.room.threadId ? { threadTs: next.room.threadId } : {}) } as ChannelRouteAddress, renderDelegationTaskCard(next, { commandPrefix: "relay task" }).text);

extensions/relay/adapters/telegram/runtime.ts:1167

  • Terminal updates ignore the task returned by upsertDelegationTask. If cancellation or another lifecycle mutation races with completion, the compare-and-set conflict keeps the newer stored state but this code still reports the stale completed/failed task card to Telegram.
    const next = transition.ok ? transition.task : task;
    await this.store.upsertDelegationTask(next);
    await this.api.sendPlainText(Number(next.room.conversationId), renderDelegationTaskCard(next, { commandPrefix: "/task" }).text);

extensions/relay/adapters/discord/runtime.ts:528

  • Completion lookup omits the configured runningTimeoutMs, so an active delegated turn that has exceeded the running timeout can still be marked completed/failed when it eventually finishes. Other task lookups pass the timeout and normalize expired running tasks; terminal reporting should do the same before accepting completion.
    const task = await this.store.getDelegationTask(taskId);
    if (!task) return false;
    const summary = summarizeTextDeterministically(route.notification.lastAssistantText ?? route.notification.lastFailure ?? status, 320);
    const transition = status === "completed"
      ? transitionDelegationTask(task, { kind: "complete", summary: summary || "Completed." })

extensions/relay/adapters/slack/runtime.ts:632

  • Completion lookup omits the configured runningTimeoutMs, allowing an over-time delegated task to be completed when it eventually returns instead of being normalized to expired. Pass the Slack delegation running timeout here before applying the terminal transition.
    const task = await this.store.getDelegationTask(taskId);
    if (!task) return false;
    const summary = route.notification.lastAssistantText ?? route.notification.lastFailure ?? status;
    const transition = status === "completed"
      ? transitionDelegationTask(task, { kind: "complete", summary })

extensions/relay/adapters/telegram/runtime.ts:1163

  • Completion lookup omits the configured delegation runningTimeoutMs, so a task that has exceeded its running timeout can still be completed when the Pi turn finishes. Use the same timeout-normalized lookup used by task actions before applying the terminal transition.
    const task = await this.store.getDelegationTask(taskId);
    if (!task) return false;
    const summary = route.notification.lastAssistantText ?? route.notification.lastFailure ?? status;
    const transition = status === "completed"
      ? transitionDelegationTask(task, { kind: "complete", summary })

extensions/relay/adapters/slack/runtime.ts:436

  • applySharedRoomPreRouting runs before isSlackIdentityAllowed, and it can write active channel selections for relay use <machine> <session> in a channel. That lets an unauthorized Slack user mutate shared-room routing state even though normal prompt handling rejects them later. Authorize the sender before pre-routing side effects or defer those writes until after authorization.
    const delegatedCommand = parseDelegationInvocation(message.text, { prefixes: ["relay", "pirelay"] }) ?? parseDelegationInvocation(stripLeadingSlackMentions(message.text), { prefixes: ["relay", "pirelay"] });
    const routedMessage = await this.applySharedRoomPreRouting(message);
    if (!routedMessage) {
      if (delegatedCommand) return;
      return;

extensions/relay/adapters/slack/runtime.ts:432

  • Bot-authored Slack messages are now normalized so delegation can be handled, but this new delegation parsing happens after the existing pairing branch. A bot message containing relay pair <pin> can still call handlePairing and mutate channel bindings before it is classified as a delegation/peer-bot event. Drop peer-bot messages before pairing unless they are validated delegation commands/actions.
    const delegatedCommand = parseDelegationInvocation(message.text, { prefixes: ["relay", "pirelay"] }) ?? parseDelegationInvocation(stripLeadingSlackMentions(message.text), { prefixes: ["relay", "pirelay"] });

Comment thread extensions/relay/adapters/discord/runtime.ts Outdated
Comment thread extensions/relay/adapters/slack/runtime.ts Outdated
Comment thread extensions/relay/adapters/telegram/runtime.ts Outdated
Comment thread extensions/relay/core/agent-delegation-runtime.ts
Comment thread extensions/relay/adapters/discord/runtime.ts
Comment thread extensions/relay/adapters/slack/runtime.ts
@zikolach zikolach merged commit 5cd2efe into main May 17, 2026
2 checks passed
@zikolach zikolach deleted the feat/shared-room-agent-delegation branch May 17, 2026 20:55
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.

2 participants