| name | change-channel-runtime |
| description | Add, modify, or review Synergy Channel targets, provider lifecycle, managed Project ownership, ChannelHost Scope/Session routing, native Clarus task handling over Holos, durable result or extension delivery, Channel diagnostics and routes, or Channel account navigation. Use across packages/synergy/src/channel, adjacent Holos/Session/Agenda/server owners, generated SDK contracts, and packages/app Channel surfaces. |
Change the Channel Runtime
Trace the Contract
- Read Channels, Sessions and messages, Connections, and the nearest package
AGENTS.md files.
- Identify the provider's conversation, Project, and Task ingress capabilities independently, and classify its lifecycle as
self_connected or borrowed_transport.
- Trace target identity, provider configuration, account start/stop/status,
ChannelHost, managed ownership, Session endpoint lookup, inbox delivery, navigation projection, routes, generated SDK consumers, diagnostics, and recovery state.
- Load
change-persistence for ownership, indexes, provider-private state, or outboxes; change-server-api for routes or generated contracts; develop-frontend for account/navigation UI; add-tool for first-party Channel tools; and develop-synergy for isolated runtime verification.
GitHub Channel Provider
The github provider (packages/synergy/src/channel/provider/github/) connects a GitHub App installation as a conversation channel. It is self_connected and polls the GitHub REST API outbound — no inbound webhook.
- Conversation ingress only — synthesized repository events (
issue.opened, pull_request.opened, pull_request.synchronize, comment.created) flow through ChannelHost.conversations.receive() with chatId = "owner/repo#<number>".
- Per-thread Scope — implement
resolveConversationScope() to bind each thread to its own random-hash checkout directory (see GithubChannelWorkspace.ensure()), so sessions are isolated per issue/PR. workspaceDir is configured per account.
- Mention gating — comments only wake an agent on an explicit
@synergy-agent mention (gateGithubEvent); autoReview/autoRespond account toggles gate PR and issue events.
- Reactions —
addReaction maps the generic channel emoji vocabulary onto GitHub's reaction content set (eyes/rocket/confused/+1/-1/laugh/hooray/heart) via a comment→chatId registry populated by the poll loop; unsupported emoji are skipped.
- Agent —
github-channel-agent owns all GitHub sessions; gh, git push, and git remote are denied (the provider performs all GitHub writes with an installation token).
- Credentials —
SYNERGY_GITHUB_APP_ID / SYNERGY_GITHUB_APP_PRIVATE_KEY env-only; distinct from the user-credential src/provider/github.ts (bash GH_TOKEN injection).
Preserve these invariants when changing the provider: deterministic per-thread directory resolution, @synergy-agent mention gating, comment→chatId reaction registry, and the env-only credential boundary.
Preserve Ownership
- Keep Scope and Session creation in Channel core. A project/task-capable provider reports remote facts through
ChannelHost; it does not call Scope, Session, or model execution directly.
- Use typed
ChannelTarget identity for new chat, Project, and Task endpoints. Preserve existing Feishu legacy endpoint keys, default Home-Scope behavior, and configured project Scope routing byte-for-byte.
- Keep Project targets as ownership/navigation identity only. Discovery and Project-level events must not create a Project conversation Session or invoke a model.
- Map each external Project identity to one canonical managed Project Scope with hashed forward and reverse ownership indexes. Keep raw external IDs out of path components, reject path escape and symbolic links, keep the managed workspace non-Git, and never remove a Scope in response to remote archive.
- Map one external Task ID to one stable unattended Session in its managed Project Scope. Deliver assignments and updates through the persistent inbox with deterministic delivery keys; keep participation and deadline guidance hidden and system-authored.
Preserve Native Clarus Semantics
- Borrow the existing authenticated Holos Agent Tunnel through
HolosRuntime.getNativeTunnel(). Do not add another WebSocket, transport reconnect loop, credential owner, daemon, or Holos server change. Provider initialization may retry through Channel's bounded backoff after borrowed transport readiness.
- Validate account identity, process epoch, connection generation, event schemas, request correlation, and acknowledgement identity at the transport boundary. Dispose observers and in-flight work on account stop or transport replacement.
- Clarus emits only Project and Task ingress. Treat
clarus.project.membership.accepted only as a hint to repeat authoritative Project discovery and correlated subscription; never grant ownership from that payload or from an Assignment project_id. Keep subscription state and runtime Task events as the remaining accepted event families, and classify legacy Project message, file, system, and notary events as unknown without Session delivery.
- If Task dispatch reports missing managed ownership, allow one bounded authoritative Project refresh and one retry while preserving
ChannelHostProjectNotOwnedError and the archive guard. Do not create a Scope or Session from Assignment identity alone.
- Send
clarus.runtime.task.accept only after preflight, Session binding, assignment persistence, and deadline synchronization, immediately before first wake. Persist acceptState, stable acceptRequestID, and acceptedAt on the existing Assignment record; parse old records with acceptState: none and do not add a migration or outbox. Exact replay must validate managed ownership, the bound non-archived Session, and request/run/Project/Task/subtask/attempt identity before bypassing ChannelHost.dispatch: acknowledged replay is a full local no-op, live pending replay sends nothing, and ambiguous or orphaned persisted pending replay only resends accept with the same request ID. Let correlated runtimeTaskAccepted bypass generic in-flight suppression and settle only on matching request ID plus all five task identity fields. Late acknowledgement may upgrade ambiguous state, and acknowledged state must never regress on transport failure.
- Persist result and extension outbox records before dispatch. Only
not_dispatched may retry automatically with a fresh request ID and lineage; rejected, ambiguous, and acknowledged are terminal for automatic retry. Recovered pending records become ambiguous.
- Keep remote Project pause as display/protocol state for already accepted work. Use the standard Session Abort path for local cancellation, and keep accepted-task result, extension, and deadline behavior available while remotely paused.
Keep API and Product Projection Complete
- Add precise route schemas and OpenAPI metadata for Channel account actions, then regenerate the SDK and migrate ordinary Web calls to generated methods. Keep diagnostics downloads on the established file/blob path.
- Bound and redact durable diagnostics before persistence and export. Never expose credentials, auth headers, raw local paths, or unbounded prompt/result payloads.
- Project managed Projects once under the owning Channel account from canonical Scope/Session navigation state. Do not add a provider-specific Project store, duplicate generic Projects, or a dedicated Clarus hierarchy.
- Present provider-capability actions, account and remote Project states, semantic icons, keyboard access, localized labels, and archive-guard guidance through shared components.
- Update Channels, product connection/workspace docs, storage paths, and
packages/app/PRODUCT.md when their contracts change.
Verify
- Write the smallest failing behavioral test first. Use real temporary Scope, Storage, Session, inbox, Agenda, and filesystem state; fake only Holos/Clarus network boundaries.
- When Channel behavior depends on Scope-local subscriptions, cover both the first account connection and
ScopeRuntime.dispose() followed by ScopeRuntime.ensure(); an active account must rebind its bridges exactly once before startup recovery can terminalize pending Channel messages, so recovery-time events are delivered rather than lost.
- Run the focused Channel, Holos native tunnel, Session endpoint/navigation, Agenda guidance, tool, server route, and frontend account/navigation tests affected by the change.
- Run
bun test test/channel/ and the relevant Holos, Agenda, Session, tool, and server suites from packages/synergy; preserve Feishu compatibility coverage.
- For route changes, run
./script/generate.ts twice and confirm generated OpenAPI/SDK output is stable. Run App/UI tests, localization checks, typecheck, build, Skill validation, and bun run quality:quick as applicable.
- Exercise the protocol in an isolated second runtime with a separate
SYNERGY_HOME and explicit ports. Verify disabled and zero-Project idle behavior, discovery, one Task Session per Task ID, result/extension settlement, reconnect recovery, diagnostics download, and cleanup without using the active runtime.
Handoff
Report provider shape and lifecycle, target identity, Scope/Session ownership, durable state and recovery semantics, routes/SDK/UI wiring, focused and broad checks, isolated runtime evidence, and any environment-only limitation.