microsoft/vscode/agent-host-chat-contributions
Claude Code and agent skill by microsoft/vscode
- vendor
Checking that the SKILL.md file is still in the repository.
Build and review cross-cutting agent-host chat behavior through lifecycle contributions. Use when adding turn lifecycle side effects, prompt or context injection, restored-history transformation, protocol-action observation, or when reviewing changes that add code to AgentSideEffects or AgentService.
Install
npx vskill@latest install microsoft/vscode/agent-host-chat-contributionsSKILL.md
Agent Host Chat Contributions
Use the contribution model for a self-contained behavior that crosses an agent-host chat lifecycle boundary. Read these files before changing the model:
src/vs/platform/agentHost/common/agentHostChatContributionsService.tssrc/vs/platform/agentHost/node/agentHostChatContributionsService.tssrc/vs/platform/agentHost/node/chatContributions/TODO.mdsrc/vs/platform/agentHost/node/chatContributions/builtInChatContributions.ts
The rule that prevents the old failure mode
New cross-cutting features go in contributions, not in `AgentSideEffects` or `AgentService`.
AgentSideEffects has shed well over 700 lines by extracting seventeen behaviors. Do not put another lifecycle side effect, outgoing-prompt addition, restored-turn enrichment, admission check, or action observer back into either large orchestration class. AgentSideEffects dispatches the lifecycle and retains routing and send mechanics; AgentService owns session lifecycle and invokes hydration and memento eviction. See agentSideEffects.ts and agentService.ts.
This is also a review rule: code added to either file needs a specific reason that it is routing, a correctness invariant, provider-shaped behavior, or a dispatcher call site. "It was convenient to add here" is not a reason.
Decide before you code
Write a contribution when the behavior answers yes to this test:
- Does it react to a lifecycle moment the hooks already name — a turn request asking to proceed, a completed turn, a dispatched action, a turn about to be sent, a restored turn list, or a chat being restored?
- Can an explicitly registered, dependency-injected unit own it without changing protocol routing or provider mapping?
- Does it compose with other behavior in a defined order?
Examples are admission gating, local-command interception, checkpoint and changeset work, queue draining, GitHub-reference attachment, title handling, persisted usage, session input-needed aggregation, session flag persistence, chat drafts, a chat's own config, worktree announcements, unread state, rich-link guidance, and chat-surface guidance. Their directories under node/chatContributions/ are the reference implementations.
Do not use a contribution for the following:
- Subagent signal routing and buffering in
node/agentSideEffects.tsare routing fabric and correctness invariants. - Turn-id remapping in
node/agentSideEffects.tsis also routing fabric. - The three SDK event mappers and three attachment serializers are provider-shaped.
- Provider-shaped overlays applied during restore, such as seeding a draft's model from
IAgent-supplied session metadata in_doRestoreSession. Moving one would push provider metadata intoIHydrationContextfor one consumer.
If the answer is "this must route every signal correctly before state changes" or "this differs by SDK," it is not a contribution. Open work and standing caveats are tracked in node/chatContributions/TODO.md.
Keep the hook surface small
IAgentHostChatContribution has seven hooks in common/agentHostChatContributionsService.ts. Only add another method when no existing payload can express the behavior.
The set was once reduced from six to four:
onTurnConsumablebecameonTurnEndwithTurnEndReason.kind === 'localCommand'.onUserMessagebecameonOutgoingTurn.
Prefer adding a discriminant or payload to an existing type, such as a new TurnEndReason variant, over adding a hook. A proposed single-purpose hook with one caller and one implementer is the smell that produced both removals. Do not create an abstraction merely to move one direct call.
The three hooks added since clear a specific bar: no existing payload could express them because they fire at a different moment or from a different source, not merely with different data.
onHydrateChatrestores host-owned chat state before a chat enters the catalog.onHydrateTurnsruns lazily, after a provider returns turns, so reusing it would leave a restored peer chat untitled until it was opened.onIncomingRequestgates a turn before it reaches a provider, and can reject it. No observer hook can refuse.onDidDispatchActionobserves server-dispatched and rejected actions, whichonDidApplyClientActionstructurally cannot see.
Adding a { kind: ... } discriminant to an existing hook was considered and rejected for onHydrateChat: the discriminant guidance covers payload variants within one moment, the way TurnEndReason discriminates outcomes of a single turn ending.
Anatomy and registration
Give every contribution its own directory under node/chatContributions/<feature>/. It owns its implementation, helpers, and tests as it grows. A contribution must have:
static readonly id, unique across registrations.- An explicit
order, even when it currently appears independent. - A constructor whose first parameter is
IAgentHostChatContributionContext, followed by injected services. - A single registration in
node/chatContributions/builtInChatContributions.ts.
This is the complete shape of the existing Markdown plan rich-link contribution, adapted from markdownPlanRichLinks/markdownPlanRichLinksContribution.ts:
Add its constructor to the one built-in list with contributions.registerContribution(MarkdownPlanRichLinksContribution). That list is activated by activateAgentHostContributions in node/agentHostContributions.ts; do not add a competing registration site. The dispatcher constructs each contribution through IInstantiationService, rejects duplicate ids, and disposes it on unregistration.
The seven hooks
| Hook | Fires when | Must not assume | Existing example | |
|---|---|---|---|---|
onIncomingRequest | A turn request asks to proceed to a provider, before any side effect. Synchronous, and fails closed. | It decides admission, so a throw rejects rather than being skipped. Return accept, handled, or reject; the first non-accept wins and later contributions are not consulted. | localCommand 50, turnAdmission 100 | |
onTurnEnd | Any terminal outcome a started turn can reach, plus a request refused before its turn started. | Discriminate rejected from error: the former has no started turn to finalize and no checkpoint to capture. Do not throw. It does not fire for an agent-emitted terminal action arriving with no active turn, because the reducer no-ops for those. | checkpointAndChangeset, queueDrain, githubReferences, sessionTitle, markUnread | |
onDidApplyClientAction | A client-dispatched action was applied to host state. | It never sees server-dispatched or rejected actions. Anything it sees definitely reduced. | queueDrain tracks queued senders; sessionTitle persists user title changes. | |
onDidDispatchAction | Any action was dispatched and its outcome is known, from any origin. | It also delivers rejected actions that never reduced — check rejectionReason. Implementing this and onDidApplyClientAction together sees every client action twice. | sessionInputNeeded, persistedTurnUsage, chatConfig, sessionFlags | |
onOutgoingTurn | Awaited after admission and provider lookup, before the turn is sent. | Rejected turns and noAgent failures never reach it. IOutgoingTurn carries the full Message; contributions can replace its text in order and add instructions, but cannot replace its already-committed attachments, model, agent, origin, or metadata. Prefer the message-text channel for context injection: changing instructions invalidates the provider prompt cache and increases user cost. Do not bypass the send path. | markdownPlanRichLinks, chatSurface, sessionTitle, sideChat | |
onHydrateTurns | A provider has returned the complete restored turn list for a chat. | It is not limited to default chats and must return a list for the next stage. Do not assume its input is pristine provider output because earlier contributions may have transformed it. | turnDelegation, persistedTurnUsage, worktreeAnnouncement, sideChat | |
onHydrateChat | A chat is being restored, eagerly, before it enters the session catalog. | It runs with no provider — it is a metadata-only read — and before the chat exists, so a chat memento taken here would outlive a failed registration. | sessionTitle, chatDraft, chatConfig |
The call sites are in node/agentSideEffects.ts, node/agentHostTurnStarter.ts, and node/agentService.ts.
Ordering is behavior
order is load-bearing. The dispatcher sorts lower values first; registration order breaks ties only. Ordering is per hook, so the 100-series can be reused for hydration independently of outgoing turns. See node/agentHostChatContributionsService.ts.
Use explicit orders to preserve a required sequence:
LocalCommandContributionis 50 andTurnAdmissionContributionis 100 ononIncomingRequest. That order is load-bearing: local commands were always intercepted before the read-only guard, so/renamestill works in a read-only or archived chat.CheckpointAndChangesetContributionruns at 100 so it captures a checkpoint before later changeset-related work.QueueDrainContributionis 200 andSessionInputNeededContributionis 200.GitHubReferencesContributionandChatSurfaceContributionare 300 where applicable.SessionTitleContributionis 400,MarkUnreadContributionandSideChatContributionare 500,ChatDraftContributionis 600,ChatConfigContributionis 650, andSessionFlagsContributionis 700.
markUnread and sideChat both declare 500 and both implement onTurnEnd, so their relative sequence rests on registration order. Give a new contribution a distinct order rather than joining that tie.
A silent reordering regression already happened. The built-in-sequence regression tests in test/node/chatContributions.test.ts protect the original turn-end, outgoing-turn, and hydration sequences. Add or update those tests when ordering changes intentionally.
Depend on services, not a growing context
Never add callbacks or services to IAgentHostChatContributionContext. It is only contribution identity plus centrally evicted mementos. Coordinate through @Injectable services.
IAgentHostChatContributionHost is intentionally the narrow bridge to AgentSideEffects: it has only hostLaunchKind and sendTurnMessage. It grew to seven members before being cut back to two by promoting collaborators to real services:
IAgentHostSessionTitleControllerIAgentHostProviderServiceIAgentHostTelemetryReporterIAgentHostTurnTrackerIAgentHostToolCallTrackerIAgentHostLocalCommands
IAgentHostWorktreeIsolation is injectable too. "It isn't injectable" was a wrong conclusion caused by service-registration order and later disproved. Trace registration from node/agentHostServices.ts through node/agentHostBootstrap.ts and createAgentServiceComposition in node/agentServiceComposition.ts. Register a real service before the instantiation path constructs contributions; do not smuggle it through the context or host bridge.
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files. Certified 7 Sep 2026.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryChecking.github/skills/agent-host-chat-contributions/SKILL.md
Popularity
- Repository stars
- 191,449
- Repository forks
- 42,039
- 7-day trend score
- 61
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/microsoft/vscode/agent-host-chat-contributions)