<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: FelixChan</title>
    <description>The latest articles on Forem by FelixChan (@_b6eedfa0c44fb8af59ed9).</description>
    <link>https://forem.com/_b6eedfa0c44fb8af59ed9</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3794217%2F40369cb0-debd-41b3-89a9-2986b95f2258.png</url>
      <title>Forem: FelixChan</title>
      <link>https://forem.com/_b6eedfa0c44fb8af59ed9</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/_b6eedfa0c44fb8af59ed9"/>
    <language>en</language>
    <item>
      <title>How I Built a Plugin and Service for Claude Code Agent Teams</title>
      <dc:creator>FelixChan</dc:creator>
      <pubDate>Thu, 26 Feb 2026 09:49:28 +0000</pubDate>
      <link>https://forem.com/_b6eedfa0c44fb8af59ed9/how-i-built-a-plugin-and-service-for-claude-code-agent-teams-1cn6</link>
      <guid>https://forem.com/_b6eedfa0c44fb8af59ed9/how-i-built-a-plugin-and-service-for-claude-code-agent-teams-1cn6</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR: What This Article Covers
&lt;/h2&gt;

&lt;p&gt;Claude Code's Agent Teams (also known as Swarm mode) allow a Team Lead Agent to orchestrate multiple Sub-Agents working in parallel. This is a powerful capability — but it raises a question: &lt;strong&gt;when you have an external work tracking system, how do you automatically connect each Sub-Agent to your workflow without the Team Lead hand-writing boilerplate in every spawn prompt?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The main goals of this article are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Introduce the Claude Code plugin ecosystem&lt;/strong&gt; — Marketplace, Plugin Manifest, Hooks, Skills, and MCP configuration form a complete extension mechanism&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Chorus as a case study&lt;/strong&gt; to show how an Agent-first task management platform can seamlessly integrate with Claude Code's multi-agent workflow through plugins&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deep dive into Sub-Agent context injection&lt;/strong&gt; — in multi-agent collaboration scenarios, ensuring each Sub-Agent automatically receives the correct working context is the key to whether a plugin can truly work in practice&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're considering building a Claude Code plugin for your own toolchain (CI/CD, project management, monitoring systems, etc.), we hope this article provides useful insights.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Claude Code Agent Teams: A Quick Look at Swarm Mode
&lt;/h2&gt;

&lt;p&gt;Agent Teams is Claude Code's multi-agent collaboration mode. The core concept is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Team Lead (main Agent)
  ├── Task tool ──&amp;gt; Sub-Agent A (frontend-worker)
  ├── Task tool ──&amp;gt; Sub-Agent B (backend-worker)
  └── Task tool ──&amp;gt; Sub-Agent C (test-runner)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Team Lead uses the &lt;code&gt;Task&lt;/code&gt; tool to spawn multiple Sub-Agents, each being an independent Agent process with its own context window, tool access, and lifecycle. Sub-Agents communicate via &lt;code&gt;SendMessage&lt;/code&gt; and collaborate through a shared filesystem.&lt;/p&gt;

&lt;p&gt;Key lifecycle events:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;When Triggered&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;additionalContext&lt;/code&gt; Target&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PreToolUse:Task&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Before&lt;/strong&gt; Team Lead calls the Task tool&lt;/td&gt;
&lt;td&gt;Team Lead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SubagentStart&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When Sub-Agent process starts (synchronous)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Sub-Agent&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TeammateIdle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When Sub-Agent goes idle (between turns)&lt;/td&gt;
&lt;td&gt;Team Lead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TaskCompleted&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When a Claude Code internal Task is marked complete&lt;/td&gt;
&lt;td&gt;Team Lead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SubagentStop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When Sub-Agent process exits&lt;/td&gt;
&lt;td&gt;Team Lead&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Note a critical distinction about where hook output goes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Most hooks (&lt;code&gt;PreToolUse:Task&lt;/code&gt;, &lt;code&gt;TeammateIdle&lt;/code&gt;, &lt;code&gt;TaskCompleted&lt;/code&gt;, &lt;code&gt;SubagentStop&lt;/code&gt;) inject &lt;code&gt;additionalContext&lt;/code&gt; into the &lt;strong&gt;Team Lead's&lt;/strong&gt; context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;SubagentStart&lt;/code&gt; is the exception&lt;/strong&gt; — its &lt;code&gt;additionalContext&lt;/code&gt; is injected directly into the &lt;strong&gt;Sub-Agent's&lt;/strong&gt; context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means &lt;code&gt;SubagentStart&lt;/code&gt; is the ideal hook for automatically providing Sub-Agents with working context (session IDs, workflow instructions, etc.) without relying on the Team Lead to hand-write boilerplate or the Sub-Agent to read files.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. What Is Chorus, and What Problem Does It Solve
&lt;/h2&gt;

&lt;p&gt;Before diving into the plugin implementation, let's briefly introduce Chorus.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Chorus-AIDLC/chorus" rel="noopener noreferrer"&gt;Chorus&lt;/a&gt; is a collaboration platform for AI Agents and humans, inspired by the &lt;a href="https://aws.amazon.com/blogs/devops/ai-driven-development-life-cycle/" rel="noopener noreferrer"&gt;AI-DLC (AI-Driven Development Lifecycle)&lt;/a&gt; methodology, implementing its core workflow from Idea to Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Idea → Proposal → [Document + Task] → Execute → Verify → Done
 ^        ^            ^                 ^          ^        ^
Human   PM Agent    PM Agent         Dev Agent   Admin    Admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core philosophy is &lt;strong&gt;Reversed Conversation&lt;/strong&gt;: AI proposes solutions, humans review and verify — rather than humans giving instructions for AI to execute.&lt;/p&gt;

&lt;p&gt;In multi-agent team scenarios, Chorus needs to solve a specific problem: &lt;strong&gt;Observability&lt;/strong&gt;. When 5 Sub-Agents are writing code simultaneously:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which Agent is working on which Task?&lt;/li&gt;
&lt;li&gt;What's each Agent's progress?&lt;/li&gt;
&lt;li&gt;Are Task status transitions (open → in_progress → to_verify → done) happening correctly?&lt;/li&gt;
&lt;li&gt;Is the Agent still alive (heartbeat)?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Chorus tracks all of this through a &lt;strong&gt;Session&lt;/strong&gt; mechanism — each working Agent owns a Session, Sessions check in to Tasks, and the UI shows in real-time who's doing what.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Chorus Looks Like in Practice
&lt;/h3&gt;

&lt;p&gt;Words are always abstract — let's look at some actual screenshots.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kanban Board — Real-time Agent Work Tracking&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fij3hspi81gasa3y80dpd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fij3hspi81gasa3y80dpd.png" alt="Task Kanban" width="800" height="577"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the core view of Chorus. Colored badges on each Task card show the Agent Sessions currently working on that Task. When a Sub-Agent calls &lt;code&gt;chorus_session_checkin_task&lt;/code&gt;, the badge appears in real-time; it disappears after &lt;code&gt;checkout&lt;/code&gt;. Task movement between columns (Open → In Progress → To Verify → Done) is driven by Agents through MCP tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Task Dependency Graph (DAG)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3kg0xqcxmyfqodaq91c9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3kg0xqcxmyfqodaq91c9.png" alt="DAG" width="800" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tasks in Chorus can declare dependencies, forming a directed acyclic graph. The PM Agent sets dependencies via &lt;code&gt;dependsOnDraftUuids&lt;/code&gt; when creating Proposals. The UI uses dagre for automatic layout. The Team Lead can use this to decide spawn order — process Tasks with no dependencies first; when upstream Tasks complete, downstream Tasks automatically become unblocked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Elaboration — Structured Requirements Clarification&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8x2rnvfmjfn3ivtkzola.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8x2rnvfmjfn3ivtkzola.png" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before an Idea becomes a Proposal, the PM Agent initiates Elaboration: structured questions about scope, technical choices, priorities, etc. Humans answer via interactive options. All Q&amp;amp;A is persisted as an audit trail on the Idea, ensuring design decisions are traceable — even verbal agreements from chat conversations get recorded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proposal — AI Proposes, Humans Review&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsc5addh2jcs5n246uand.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsc5addh2jcs5n246uand.png" alt="Proposal" width="800" height="597"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This embodies the AI-DLC core philosophy of "Reversed Conversation": the PM Agent builds on the Elaboration conclusions to create a Proposal containing PRD document drafts and Task drafts. After Admin (human) approval, drafts are automatically materialized into real Document and Task entities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Task Detail — Session Tracking&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft7wr6p06av6u4uzi6feg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft7wr6p06av6u4uzi6feg.png" alt="Task Tracking" width="800" height="598"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Task detail page shows complete work history: which Sessions have checked in to this Task, the checkin/checkout times, and the Agent's work reports. This is Chorus's observability — even with 5 Agents working simultaneously, you can clearly see what everyone is doing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pixel Office — Agent Virtual Workstations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhulcjxcrb62bf2p145ug.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhulcjxcrb62bf2p145ug.png" alt="Pixel Workspace" width="800" height="595"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a fun feature of Chorus: each active Agent Session has its own workstation in a pixel art office. Agents start a "working" animation when checked in to a Task, rest when idle, and celebrate when done. Purely visual entertainment, but you can see the team's work status at a glance.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Why Build a Claude Code Plugin
&lt;/h2&gt;

&lt;p&gt;Before the plugin, the Team Lead had to hand-write extensive boilerplate in every Sub-Agent's spawn prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;frontend-worker&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Your Chorus session UUID: ??? (Team Lead doesn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t know yet — session hasn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t been created)
    Your Chorus task UUID: task-A-uuid

    Before work:
    1. Create session: chorus_create_session(...)
    2. Checkin: chorus_session_checkin_task(sessionUuid, taskUuid)
    3. Update status: chorus_update_task(taskUuid, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;in_progress&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, sessionUuid)

    During work:
    4. Report progress: chorus_report_work(taskUuid, report, sessionUuid)

    After completion:
    5. Checkout: chorus_session_checkout_task(sessionUuid, taskUuid)
    6. Submit for verification: chorus_submit_for_verify(taskUuid, summary)
    7. Close session: chorus_close_session(sessionUuid)
  &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problems are obvious:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Session UUID can't be known in advance&lt;/strong&gt; — Sessions require MCP calls to create, but the prompt must be written before spawn&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every Sub-Agent's prompt repeats the same boilerplate&lt;/strong&gt; — 6-7 workflow steps taking up significant prompt space&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Team Lead must remember all the steps&lt;/strong&gt; — Forgot checkout? Forgot heartbeat? The Session will become stale&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session lifecycle management is complex&lt;/strong&gt; — Create, reuse, reopen, heartbeat, close — all manual&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With the plugin, all of this is automated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;frontend-worker&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Your Chorus task UUID: task-A-uuid
    Implement the frontend user form component...
  &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From 15 lines of boilerplate to 2 lines. The Team Lead only passes the task UUID — the plugin's &lt;code&gt;SubagentStart&lt;/code&gt; hook automatically injects the session UUID and complete workflow instructions directly into the Sub-Agent's context. No session files to read, no workflow boilerplate to copy.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Claude Code Plugin System Overview
&lt;/h2&gt;

&lt;p&gt;A Claude Code plugin is a directory containing these components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-plugin/
├── .claude-plugin/
│   └── plugin.json          # Plugin manifest (metadata)
├── .mcp.json                # MCP server configuration
├── hooks/
│   └── hooks.json           # Hook configuration
├── bin/                     # Hook scripts
│   ├── on-session-start.sh
│   └── on-subagent-start.sh
└── skills/
    └── my-skill/
        ├── SKILL.md         # Skill entry file
        └── references/      # Reference documents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's walk through each component.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.1 Plugin Manifest (plugin.json)
&lt;/h3&gt;

&lt;p&gt;Located at &lt;a href="https://github.com/Chorus-AIDLC/Chorus/blob/main/public/chorus-plugin/.claude-plugin/plugin.json" rel="noopener noreferrer"&gt;&lt;code&gt;.claude-plugin/plugin.json&lt;/code&gt;&lt;/a&gt;, it's the plugin's identity card:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chorus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Chorus AI-DLC collaboration platform plugin..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.1.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Chorus-AIDLC"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"homepage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/Chorus-AIDLC/chorus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AGPL-3.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"keywords"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ai-dlc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"multi-agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"session"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;plugin.json&lt;/code&gt; is optional — if omitted, Claude Code infers the plugin name from the directory name and auto-discovers components. But it's recommended to always provide one for version management and distribution.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 Marketplace
&lt;/h3&gt;

&lt;p&gt;Plugins are distributed through Marketplaces. A Marketplace is essentially a JSON manifest file (&lt;a href="https://github.com/Chorus-AIDLC/Chorus/blob/main/.claude-plugin/marketplace.json" rel="noopener noreferrer"&gt;&lt;code&gt;.claude-plugin/marketplace.json&lt;/code&gt;&lt;/a&gt;) hosted in a public GitHub repo. Chorus uses its own GitHub repository as a Marketplace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chorus-plugins"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Chorus-AIDLC"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"plugins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chorus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./public/chorus-plugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Chorus AI-DLC collaboration platform plugin..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.1.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"project-management"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ai-dlc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"collaboration"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"session"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual installation flow for the Chorus plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Add marketplace — points to the GitHub repo (containing .claude-plugin/marketplace.json)&lt;/span&gt;
/plugin marketplace add Chorus-AIDLC/chorus

&lt;span class="c"&gt;# 2. Install plugin — format: plugin-name@marketplace-name&lt;/span&gt;
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;chorus@chorus-plugins

&lt;span class="c"&gt;# 3. Optionally specify scope&lt;/span&gt;
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;chorus@chorus-plugins &lt;span class="nt"&gt;--scope&lt;/span&gt; project  &lt;span class="c"&gt;# Project-level (shared with team, committed to git)&lt;/span&gt;
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;chorus@chorus-plugins &lt;span class="nt"&gt;--scope&lt;/span&gt; &lt;span class="nb"&gt;local&lt;/span&gt;    &lt;span class="c"&gt;# Local-level (just for you)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;source&lt;/code&gt; field points to the plugin's relative path within the repo. Besides local paths, it also supports pointing to other GitHub repos (&lt;code&gt;"source": {"source": "github", "repo": "owner/repo"}&lt;/code&gt;) or Git URLs.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3 MCP Configuration (&lt;a href="https://github.com/Chorus-AIDLC/Chorus/blob/main/public/chorus-plugin/.mcp.json" rel="noopener noreferrer"&gt;.mcp.json&lt;/a&gt;)
&lt;/h3&gt;

&lt;p&gt;Plugins can bundle MCP Server configuration that takes effect automatically after installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"chorus"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${CHORUS_URL}/api/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer ${CHORUS_API_KEY}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;${CHORUS_URL}&lt;/code&gt; and &lt;code&gt;${CHORUS_API_KEY}&lt;/code&gt; are environment variables — Claude Code substitutes them at runtime. Users just need to set the environment variables, and the plugin connects to the right service.&lt;/p&gt;

&lt;p&gt;This means: &lt;strong&gt;after plugin installation, all MCP tools are automatically available&lt;/strong&gt;. Sub-Agents can access them too (provided MCP config is at the project level, not user level).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chorus's MCP Configuration&lt;/strong&gt;: Chorus exposes 50+ MCP tools via HTTP Streamable Transport, grouped by role (public tools, PM tools, Developer tools, Admin tools, Session tools). Users only need to set two environment variables &lt;code&gt;CHORUS_URL&lt;/code&gt; and &lt;code&gt;CHORUS_API_KEY&lt;/code&gt; to connect. API Keys start with the &lt;code&gt;cho_&lt;/code&gt; prefix and carry Agent role information — the server determines which tools are visible based on this.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.4 Skills
&lt;/h3&gt;

&lt;p&gt;Skills are plugin-bundled instruction sets that Claude can invoke automatically when needed, or users can trigger manually via &lt;code&gt;/skill-name&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A Skill consists of a &lt;code&gt;SKILL.md&lt;/code&gt; entry file and optional &lt;code&gt;references/&lt;/code&gt; documents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;chorus&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Chorus AI Agent collaboration platform Skill...&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;chorus&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.1.1"&lt;/span&gt;
  &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;project-management&lt;/span&gt;
  &lt;span class="na"&gt;mcp_server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;chorus&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# Chorus Skill&lt;/span&gt;

This Skill guides AI Agents on how to use Chorus MCP tools...

&lt;span class="gu"&gt;## Skill Files&lt;/span&gt;

| File | Description |
|------|-------------|
| &lt;span class="gs"&gt;**references/02-pm-workflow.md**&lt;/span&gt; | PM Agent workflow |
| &lt;span class="gs"&gt;**references/03-developer-workflow.md**&lt;/span&gt; | Developer Agent workflow |
| &lt;span class="gs"&gt;**references/06-claude-code-agent-teams.md**&lt;/span&gt; | Agent Teams integration |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Chorus's Skill System&lt;/strong&gt;: Chorus includes &lt;a href="https://github.com/Chorus-AIDLC/Chorus/tree/main/public/chorus-plugin/skills/chorus/references" rel="noopener noreferrer"&gt;7 reference documents&lt;/a&gt; (&lt;code&gt;references/00&lt;/code&gt; through &lt;code&gt;references/06&lt;/code&gt;), covering everything from public tools, PM workflow, Developer workflow, Admin workflow, to Session management and Agent Teams integration. When an Agent invokes &lt;code&gt;/chorus&lt;/code&gt; or Claude determines Chorus knowledge is needed, Skill docs are automatically loaded into context. This is essentially giving every Agent a portable operations manual — whether it's the Team Lead or a Sub-Agent, they can understand the correct workflow through Skills.&lt;/p&gt;

&lt;p&gt;Skill frontmatter supports rich configuration options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-skill&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;When&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;use&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;this&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;skill"&lt;/span&gt;
&lt;span class="na"&gt;allowed-tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Read, Grep, Glob&lt;/span&gt;     &lt;span class="c1"&gt;# Tools allowed without permission prompts&lt;/span&gt;
&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claude-opus-4-6&lt;/span&gt;              &lt;span class="c1"&gt;# Specify model&lt;/span&gt;
&lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fork&lt;/span&gt;                       &lt;span class="c1"&gt;# Run in subagent&lt;/span&gt;
&lt;span class="na"&gt;disable-model-invocation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;      &lt;span class="c1"&gt;# Only user can trigger (Claude won't auto-invoke)&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.5 Hooks
&lt;/h3&gt;

&lt;p&gt;Hooks are the core of plugins — they let you execute custom logic at key points in Claude Code's lifecycle.&lt;/p&gt;

&lt;p&gt;Configured in &lt;a href="https://github.com/Chorus-AIDLC/Chorus/blob/main/public/chorus-plugin/hooks/hooks.json" rel="noopener noreferrer"&gt;&lt;code&gt;hooks/hooks.json&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"SessionStart"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"startup|resume|compact"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${CLAUDE_PLUGIN_ROOT}/bin/on-session-start.sh"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"SubagentStart"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${CLAUDE_PLUGIN_ROOT}/bin/on-subagent-start.sh"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"SubagentStop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${CLAUDE_PLUGIN_ROOT}/bin/on-subagent-stop.sh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"async"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Hook Types
&lt;/h4&gt;

&lt;p&gt;Claude Code supports three hook execution methods:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;command&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Execute a shell command, receiving event JSON via stdin, outputting results via stdout&lt;/td&gt;
&lt;td&gt;Most scenarios&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prompt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use an LLM to evaluate decisions, model returns &lt;code&gt;{ok: true/false}&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;When intelligent judgment is needed (e.g., code review)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;agent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Spawn a subagent with tool access for verification&lt;/td&gt;
&lt;td&gt;When complex multi-step verification is needed (e.g., running tests)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All of Chorus's hooks use the &lt;code&gt;command&lt;/code&gt; type — because Chorus's hook logic is deterministic (calling APIs, reading/writing files, managing state) and doesn't require LLM judgment. &lt;code&gt;prompt&lt;/code&gt; and &lt;code&gt;agent&lt;/code&gt; are better suited for scenarios that require "understanding" code content to make decisions, such as using an &lt;code&gt;agent&lt;/code&gt; type in the &lt;code&gt;Stop&lt;/code&gt; event to automatically run tests to determine if a task is truly complete.&lt;/p&gt;

&lt;h4&gt;
  
  
  Hook Event Reference
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;When Triggered&lt;/th&gt;
&lt;th&gt;Can Block&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SessionStart&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Session start/resume/compact&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UserPromptSubmit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;User submits input&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PreToolUse&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Before tool execution&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PostToolUse&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;After tool execution&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SubagentStart&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sub-Agent starts&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SubagentStop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sub-Agent exits&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TeammateIdle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sub-Agent goes idle&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TaskCompleted&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CC Task completed&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SessionEnd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Session ends&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Hook Output Format
&lt;/h4&gt;

&lt;p&gt;Now that we know what events are available, the next question is: &lt;strong&gt;what can a hook script return to influence Claude's behavior?&lt;/strong&gt; Hooks output JSON via stdout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"systemMessage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User-visible notification message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hookSpecificOutput"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hookEventName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SubagentStart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"additionalContext"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"This text is injected into Claude's context"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"permissionDecision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"allow"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;systemMessage&lt;/code&gt;&lt;/strong&gt;: Displayed in the Claude Code UI as a notification, visible to users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;additionalContext&lt;/code&gt;&lt;/strong&gt;: Injected into the LLM's system context — &lt;strong&gt;this is the primary mechanism for hooks to influence Claude's behavior&lt;/strong&gt;. Chorus's &lt;code&gt;SessionStart&lt;/code&gt; hook uses it to inject checkin results (identity, tasks, notifications) into the Agent's context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;permissionDecision&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;allow&lt;/code&gt; / &lt;code&gt;deny&lt;/code&gt; / &lt;code&gt;ask&lt;/code&gt;, used by &lt;code&gt;PreToolUse&lt;/code&gt; to control tool execution permissions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;suppressOutput&lt;/code&gt;&lt;/strong&gt;: Set to &lt;code&gt;true&lt;/code&gt; to silence output — Chorus's &lt;code&gt;TeammateIdle&lt;/code&gt; hook uses this to avoid notification popups on every heartbeat&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Synchronous vs Asynchronous
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Synchronous hooks&lt;/strong&gt; (default): Block Claude until completion. Suited for scenarios requiring immediate effect — Chorus's &lt;code&gt;SubagentStart&lt;/code&gt; must be synchronous because it needs to create the session and write the session file before the Sub-Agent starts working&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asynchronous hooks&lt;/strong&gt; (&lt;code&gt;"async": true&lt;/code&gt;): Run in background, non-blocking. Suited for scenarios that don't affect the flow — Chorus's &lt;code&gt;SubagentStop&lt;/code&gt; (resource cleanup) and &lt;code&gt;TeammateIdle&lt;/code&gt; (heartbeat) are both asynchronous&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  What Chorus Does with Each Hook Event
&lt;/h4&gt;

&lt;p&gt;Now that we understand events, output format, and sync/async, let's see how the Chorus plugin specifically uses each hook.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;SessionStart&lt;/code&gt; — Checkin + Context Injection&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the plugin's "startup self-check". Note that the &lt;code&gt;SessionStart&lt;/code&gt; matcher is configured as &lt;code&gt;startup|resume|compact&lt;/code&gt;, meaning it fires not only on session start and resume, but &lt;strong&gt;also after context compaction&lt;/strong&gt;. When a long conversation triggers automatic compaction, previously injected Chorus context is lost along with the compressed messages — the &lt;code&gt;compact&lt;/code&gt; matcher ensures that fresh checkin information is re-injected immediately after compaction, so the Agent never "forgets" its Chorus context.&lt;/p&gt;

&lt;p&gt;Chorus does three things here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Calls the &lt;code&gt;chorus_checkin()&lt;/code&gt; MCP tool to get the current Agent's identity (role, name, persona), assigned Ideas and Tasks, and unread notifications&lt;/li&gt;
&lt;li&gt;Injects the complete checkin result into Claude's context via &lt;code&gt;additionalContext&lt;/code&gt; — the Agent knows who it is and what to do from the very first turn&lt;/li&gt;
&lt;li&gt;Scans the &lt;code&gt;.chorus/sessions/&lt;/code&gt; directory to list existing Sub-Agent session metadata — this handles the case where a Claude Code session is interrupted and resumed: previous session files may still exist, and the Team Lead needs to know which sessions are still present after recovery
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# on-session-start.sh core logic&lt;/span&gt;
&lt;span class="nv"&gt;CHECKIN_RESULT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$API&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; mcp-tool &lt;span class="s2"&gt;"chorus_checkin"&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;CONTEXT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"# Chorus Plugin — Active
Chorus is connected at &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CHORUS_URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.
## Checkin Result
&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CHECKIN_RESULT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
## Session Management — IMPORTANT
The Chorus Plugin fully automates session lifecycle...
Do NOT call chorus_create_session for sub-agents."&lt;/span&gt;

&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$API&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; hook-output &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$USER_MSG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CONTEXT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"SessionStart"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result: The Agent has complete project context and behavioral guidelines from its very first conversation turn, without the user having to manually provide anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;UserPromptSubmit&lt;/code&gt; — Lightweight Status Reminder&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Triggered on every user input, so it must be extremely fast (&amp;lt;100ms). Chorus makes &lt;strong&gt;no network calls&lt;/strong&gt; here, only local file checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# on-user-prompt.sh — pure local operation, no MCP calls&lt;/span&gt;
&lt;span class="c"&gt;# Count json files in .chorus/sessions/&lt;/span&gt;
&lt;span class="nv"&gt;CONTEXT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"[Chorus Plugin Active]
- Active sub-agent sessions (3): frontend-worker, backend-worker, test-runner"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives the Team Lead persistent status awareness: how many Sub-Agent sessions are currently running.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;PreToolUse&lt;/code&gt; — Workflow Guidance (3 Sub-Hooks)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Chorus registers 3 &lt;code&gt;PreToolUse&lt;/code&gt; hooks, each matching a different tool:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;matcher&lt;/th&gt;
&lt;th&gt;Script&lt;/th&gt;
&lt;th&gt;What Chorus Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;EnterPlanMode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/Chorus-AIDLC/Chorus/blob/main/public/chorus-plugin/bin/on-pre-enter-plan.sh" rel="noopener noreferrer"&gt;&lt;code&gt;on-pre-enter-plan.sh&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Inject Chorus Proposal workflow guidance — "Create a Proposal first, set up Task dependency DAG, submit for approval before coding"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ExitPlanMode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/Chorus-AIDLC/Chorus/blob/main/public/chorus-plugin/bin/on-pre-exit-plan.sh" rel="noopener noreferrer"&gt;&lt;code&gt;on-pre-exit-plan.sh&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Reminder check — "Confirm Proposal has been created and submitted before exiting Plan Mode"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Task&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/Chorus-AIDLC/Chorus/blob/main/public/chorus-plugin/bin/on-pre-spawn-agent.sh" rel="noopener noreferrer"&gt;&lt;code&gt;on-pre-spawn-agent.sh&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Capture Sub-Agent name/type to pending file for SubagentStart to claim&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;EnterPlanMode&lt;/code&gt; and &lt;code&gt;ExitPlanMode&lt;/code&gt; demonstrate an interesting usage: &lt;strong&gt;using hooks to guide Agents toward following a specific workflow&lt;/strong&gt;. When the Agent enters Plan Mode, Chorus automatically injects "create Proposal before coding" guidance; when exiting Plan Mode, it checks whether a Proposal exists. This isn't a hard block (&lt;code&gt;permissionDecision&lt;/code&gt; remains &lt;code&gt;allow&lt;/code&gt;), but soft guidance via &lt;code&gt;additionalContext&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;SubagentStart&lt;/code&gt; — Automatic Session Creation + Direct Context Injection&lt;/strong&gt; (Core)&lt;/p&gt;

&lt;p&gt;This is the Chorus plugin's most critical hook, detailed in Chapter 5. In brief: claim pending file → create/reuse Session → inject session UUID + workflow instructions directly into Sub-Agent's context via &lt;code&gt;additionalContext&lt;/code&gt; → store state mappings. The session file is kept minimal (just metadata for other hooks).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;SubagentStop&lt;/code&gt; — Automatic Cleanup + Task Discovery&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Runs asynchronously, doing four things: (1) batch checkout all unclosed task checkins, (2) close the Session, (3) clean up local files and state, (4) query the project for newly unblocked Tasks and notify the Team Lead via &lt;code&gt;additionalContext&lt;/code&gt; — this last step is extremely valuable, implementing &lt;strong&gt;automatic task dispatch discovery&lt;/strong&gt;: when an upstream Task completes, downstream Tasks automatically become unblocked, and the Team Lead is immediately notified to assign new work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;TeammateIdle&lt;/code&gt; — Automatic Heartbeat&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Async + &lt;code&gt;suppressOutput: true&lt;/code&gt;. Does just one thing: calls &lt;code&gt;chorus_session_heartbeat&lt;/code&gt; to keep the Session active. Chorus Sessions are automatically marked as inactive after 1 hour without a heartbeat — this hook ensures that as long as a Sub-Agent is running, its Session stays alive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;TaskCompleted&lt;/code&gt; — Metadata Bridging&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a Claude Code internal Task is marked complete, Chorus checks whether the task description contains a &lt;code&gt;chorus:task:&amp;lt;uuid&amp;gt;&lt;/code&gt; tag. If so, it automatically executes &lt;code&gt;chorus_session_checkout_task&lt;/code&gt;. This is an elegant &lt;strong&gt;metadata bridging&lt;/strong&gt; pattern — by embedding a Chorus task UUID in the CC Task description, the two systems' Task lifecycles are linked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;SessionEnd&lt;/code&gt; — Clean Up .chorus/ Directory&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When the session ends, checks whether all session files have been cleaned up and state.json is empty. If so, deletes the entire &lt;code&gt;.chorus/&lt;/code&gt; directory, leaving no leftover files.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Chorus Plugin: Complete Implementation
&lt;/h2&gt;

&lt;p&gt;Now for the main topic — how the Chorus plugin uses the above mechanisms to solve multi-agent collaboration problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1 Architecture Overview
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Team Lead calls Task tool to spawn Sub-Agent
  │
  ├─ [PreToolUse:Task] on-pre-spawn-agent.sh
  │    Write .chorus/pending/&amp;lt;name&amp;gt; file (capture agent name)
  │
  ├─ [SubagentStart] on-subagent-start.sh    ← Core
  │    Claim pending file (atomic mv, handles concurrency)
  │    Create/reuse/reopen Chorus Session (MCP call)
  │    Inject session UUID + workflow into Sub-Agent via additionalContext
  │    Write minimal session file (metadata for other hooks)
  │    Store state mappings (agent_id ↔ session_uuid)
  │
  ├─ Sub-Agent starts executing
  │    Session UUID + workflow already in context (auto-injected)
  │    Autonomously execute: checkin → in_progress → report → checkout → submit
  │
  ├─ [TeammateIdle] on-teammate-idle.sh (async)
  │    Send session heartbeat, keep session active
  │
  ├─ [TaskCompleted] on-task-completed.sh
  │    Detect chorus:task:&amp;lt;uuid&amp;gt; tag, auto checkout
  │
  └─ [SubagentStop] on-subagent-stop.sh (async)
       Batch checkout all tasks
       Close Chorus Session
       Clean up local state
       Query and display newly unblocked tasks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5.2 The &lt;code&gt;.chorus/&lt;/code&gt; Directory: The Bridge Connecting Everything
&lt;/h3&gt;

&lt;p&gt;We've mentioned "shared filesystem" multiple times — let's expand on this. The Chorus plugin maintains a &lt;code&gt;.chorus/&lt;/code&gt; directory (gitignored) at the project root, serving as the information hub between the Team Lead, Sub-Agents, and all hooks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.chorus/                              # Plugin runtime state (gitignored)
├── state.json                        # Global state KV store
├── state.json.lock                   # flock exclusive lock file
├── sessions/                         # Sub-Agent session metadata (for hook state lookup)
│   ├── frontend-worker.json
│   ├── backend-worker.json
│   └── test-runner.json
├── pending/                          # Written by PreToolUse:Task, awaiting SubagentStart claim
│   └── &amp;lt;agent-name&amp;gt;
└── claimed/                          # Files claimed by SubagentStart
    └── &amp;lt;agent-id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Core: &lt;code&gt;state.json&lt;/code&gt; — Cross-Hook State Sharing
&lt;/h4&gt;

&lt;p&gt;Each hook is an independent shell process — they don't share memory. &lt;code&gt;state.json&lt;/code&gt; is the shared state store across all hooks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"session_a0ed860"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"699f8ed4-4a98-4522-8321-662a2222a180"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"agent_for_session_699f8ed4-..."&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a0ed860"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"session_frontend-worker"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"699f8ed4-..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name_for_agent_a0ed860"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"frontend-worker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main_session_uuid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It stores four mapping relationships: &lt;code&gt;agent_id → session_uuid&lt;/code&gt;, &lt;code&gt;session_uuid → agent_id&lt;/code&gt;, &lt;code&gt;agent_name → session_uuid&lt;/code&gt;, &lt;code&gt;agent_id → agent_name&lt;/code&gt;. This way, any hook that knows one ID can look up all associated information.&lt;/p&gt;

&lt;h4&gt;
  
  
  Concurrent Write Protection: flock
&lt;/h4&gt;

&lt;p&gt;When 5 Sub-Agents spawn simultaneously, 5 &lt;code&gt;SubagentStart&lt;/code&gt; hooks execute concurrently, each writing 4 keys to &lt;code&gt;state.json&lt;/code&gt;. Without protection, the JSON file would be corrupted by concurrent writes.&lt;/p&gt;

&lt;p&gt;Chorus solves this in &lt;a href="https://github.com/Chorus-AIDLC/Chorus/blob/main/public/chorus-plugin/bin/chorus-api.sh" rel="noopener noreferrer"&gt;&lt;code&gt;chorus-api.sh&lt;/code&gt;&lt;/a&gt; using &lt;code&gt;flock&lt;/code&gt; exclusive locks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# state_set implementation in chorus-api.sh&lt;/span&gt;
state_set&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="c"&gt;# Acquire exclusive lock, 5-second timeout&lt;/span&gt;
    flock &lt;span class="nt"&gt;-w&lt;/span&gt; 5 200 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"WARN: flock timeout"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;return &lt;/span&gt;0&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="c"&gt;# Modify JSON under lock protection&lt;/span&gt;
    jq &lt;span class="nt"&gt;--arg&lt;/span&gt; k &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--arg&lt;/span&gt; v &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s1"&gt;'.[$k] = $v'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATE_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mv&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATE_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt; 200&amp;gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;STATE_FILE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.lock"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;flock -w 5 200&lt;/code&gt;: Acquire exclusive lock on file descriptor 200, wait up to 5 seconds&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;200&amp;gt;"${STATE_FILE}.lock"&lt;/code&gt;: Lock file is separate from the state file (&lt;code&gt;.lock&lt;/code&gt; suffix)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jq ... &amp;gt; $tmp &amp;amp;&amp;amp; mv $tmp&lt;/code&gt;: Write to temp file first, then atomically replace — prevents corruption if a crash happens mid-write&lt;/li&gt;
&lt;li&gt;Timeout doesn't error (&lt;code&gt;return 0&lt;/code&gt;) — better to lose one state write than block the entire hook chain&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;pending/&lt;/code&gt; → &lt;code&gt;claimed/&lt;/code&gt;: Atomic Ownership Transfer
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;SubagentStart&lt;/code&gt; event only provides &lt;code&gt;agent_id&lt;/code&gt; and &lt;code&gt;agent_type&lt;/code&gt;, &lt;strong&gt;not the name the Team Lead gave the Sub-Agent&lt;/strong&gt;. But sessions need to be named (so the Sub-Agent can find its session file by name).&lt;/p&gt;

&lt;p&gt;The solution is a relay between two hooks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;PreToolUse:Task&lt;/code&gt; (Team Lead context) can extract the &lt;code&gt;name&lt;/code&gt; parameter from &lt;code&gt;tool_input&lt;/code&gt;, writing it to a &lt;code&gt;pending/&amp;lt;name&amp;gt;&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SubagentStart&lt;/code&gt; (still Team Lead context, but executing concurrently) atomically claims it via &lt;code&gt;mv pending/&amp;lt;name&amp;gt; claimed/&amp;lt;agent_id&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Timeline:
  T1  PreToolUse:Task fires → write .chorus/pending/frontend-worker
  T2  PreToolUse:Task fires → write .chorus/pending/backend-worker
  T3  SubagentStart(agent_id=a0e) fires → mv pending/frontend-worker → claimed/a0e ✓
  T4  SubagentStart(agent_id=b1f) fires → mv pending/backend-worker → claimed/b1f ✓
  T4' SubagentStart(agent_id=c2g) fires → mv pending/frontend-worker → fails (already claimed by a0e)
                                        → mv pending/backend-worker → fails (already claimed by b1f)
                                        → no more pending files → skip (internal agent, no session needed)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;mv&lt;/code&gt; is atomic on the same filesystem — only one process can successfully move a given file. This is lighter than flock, well-suited for "first come, first served" scenarios.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;sessions/&lt;/code&gt; — Metadata for Cross-Hook State Lookup
&lt;/h4&gt;

&lt;p&gt;Session files now contain only minimal metadata (sessionUuid, agentId, agentName). Workflow instructions are injected directly into the Sub-Agent's context via &lt;code&gt;SubagentStart&lt;/code&gt;'s &lt;code&gt;additionalContext&lt;/code&gt; — Sub-Agents no longer need to read these files. The files still serve a purpose: other hooks (&lt;code&gt;TeammateIdle&lt;/code&gt;, &lt;code&gt;SubagentStop&lt;/code&gt;) use them to look up session information for heartbeats and cleanup.&lt;/p&gt;

&lt;h4&gt;
  
  
  Lifecycle: Creation to Cleanup
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SessionStart  → mkdir -p .chorus/ (if not exists)
PreToolUse    → write .chorus/pending/&amp;lt;name&amp;gt;
SubagentStart → mv pending → claimed, write sessions/&amp;lt;name&amp;gt;.json (metadata only),
                inject workflow via additionalContext → Sub-Agent, update state.json
TeammateIdle  → read state.json (lookup session_uuid), no writes
TaskCompleted → read state.json (lookup session_uuid), no writes
SubagentStop  → delete sessions/&amp;lt;name&amp;gt;.json, delete claimed/&amp;lt;agent_id&amp;gt;, clean state.json entries
SessionEnd    → if sessions/ is empty and state.json is empty → rm -rf .chorus/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The entire directory's lifecycle matches the Claude Code session — created at start, cleaned up at end, leaving no trace.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3 The Core Challenge: Sub-Agent Context Injection
&lt;/h3&gt;

&lt;p&gt;The key question is: how do you automatically provide each Sub-Agent with its session UUID and workflow instructions, without the Team Lead hand-writing boilerplate?&lt;/p&gt;

&lt;p&gt;The answer lies in a critical property of the &lt;code&gt;SubagentStart&lt;/code&gt; hook: &lt;strong&gt;its &lt;code&gt;additionalContext&lt;/code&gt; is injected directly into the Sub-Agent's context&lt;/strong&gt;, not the Team Lead's. This makes it the ideal injection point — the hook that creates the session (and thus knows the sessionUuid) can also inject the workflow, all in one place.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# on-subagent-start.sh — core snippet&lt;/span&gt;
&lt;span class="c"&gt;# After creating/reusing a session and obtaining SESSION_UUID...&lt;/span&gt;

&lt;span class="nv"&gt;WORKFLOW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"## Chorus Session (Auto-injected by plugin)

Your Chorus session UUID is: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SESSION_UUID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
Your session name is: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SESSION_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
Do NOT call chorus_create_session or chorus_close_session.

### Workflow — follow these steps for each task:

**Before starting:**
1. Check in: chorus_session_checkin_task({ sessionUuid: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SESSION_UUID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, taskUuid: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;TASK_UUID&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; })
2. Start work: chorus_update_task({ taskUuid: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;TASK_UUID&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, status: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;in_progress&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, sessionUuid: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SESSION_UUID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; })

**While working:**
3. Report progress: chorus_report_work({ taskUuid: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;TASK_UUID&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, report: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, sessionUuid: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SESSION_UUID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; })

**After completing:**
4. Check out: chorus_session_checkout_task({ sessionUuid: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SESSION_UUID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, taskUuid: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;TASK_UUID&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; })
5. Submit: chorus_submit_for_verify({ taskUuid: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;TASK_UUID&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, summary: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; })

Replace &amp;lt;TASK_UUID&amp;gt; with the actual Chorus task UUID from your prompt."&lt;/span&gt;

&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$API&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; hook-output &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"Chorus session &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SESSION_ACTION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: '&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SESSION_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WORKFLOW&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"SubagentStart"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Sub-Agent sees the workflow as a &lt;code&gt;&amp;lt;system-reminder&amp;gt;&lt;/code&gt; in its context from the very first turn. The session file is kept minimal (just sessionUuid + metadata) for other hooks to use.&lt;/p&gt;

&lt;p&gt;This means the Team Lead's spawn prompt is truly minimal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;frontend-worker&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Your Chorus task UUID: task-A-uuid
    Implement the frontend user form component...
  &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plugin handles everything else — the Team Lead only passes the task UUID.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.4 Session Reuse: Avoiding Duplicate Creation
&lt;/h3&gt;

&lt;p&gt;When the Team Lead spawns a Sub-Agent with the same name multiple times (e.g., after a Task is reopened by Admin), the plugin doesn't create a new Session — it reuses the existing one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Reuse logic in on-subagent-start.sh&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MATCH_STATUS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"active"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;SESSION_UUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MATCH_UUID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;         &lt;span class="c"&gt;# Reuse directly&lt;/span&gt;
    &lt;span class="nv"&gt;SESSION_ACTION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"reused"&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MATCH_STATUS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"closed"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MATCH_STATUS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"inactive"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="c"&gt;# Reopen closed session&lt;/span&gt;
    chorus_reopen_session&lt;span class="o"&gt;(&lt;/span&gt;sessionUuid&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;SESSION_ACTION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"reopened"&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="c"&gt;# Create new session&lt;/span&gt;
    chorus_create_session&lt;span class="o"&gt;(&lt;/span&gt;name&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;SESSION_ACTION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"created"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5.5 Automatic Cleanup: SubagentStop
&lt;/h3&gt;

&lt;p&gt;When a Sub-Agent exits, &lt;a href="https://github.com/Chorus-AIDLC/Chorus/blob/main/public/chorus-plugin/bin/on-subagent-stop.sh" rel="noopener noreferrer"&gt;&lt;code&gt;on-subagent-stop.sh&lt;/code&gt;&lt;/a&gt; (running asynchronously) handles cleanup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Query all active checkins for the Session, checkout each one&lt;/li&gt;
&lt;li&gt;Close the Chorus Session&lt;/li&gt;
&lt;li&gt;Delete local state (state entries, session file, claimed file)&lt;/li&gt;
&lt;li&gt;Query the project for newly unblocked Tasks and notify the Team Lead&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This way, even if a Sub-Agent forgot to checkout or close its session, the plugin provides a safety net.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.6 Automatic Heartbeat: TeammateIdle
&lt;/h3&gt;

&lt;p&gt;Sub-Agents enter an idle state between conversation turns, at which point the &lt;code&gt;TeammateIdle&lt;/code&gt; hook automatically sends a heartbeat:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# on-teammate-idle.sh&lt;/span&gt;
&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$API&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; mcp-tool &lt;span class="s2"&gt;"chorus_session_heartbeat"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'{"sessionUuid":"%s"}'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SESSION_UUID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output is silenced with &lt;code&gt;suppressOutput: true&lt;/code&gt; — heartbeats are too frequent to warrant notifying the Team Lead.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Design Pattern Summary
&lt;/h2&gt;

&lt;p&gt;From the Chorus plugin's practice, we can extract several reusable design patterns:&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 1: SubagentStart for Direct Context Injection
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SubagentStart hook  →  additionalContext  →  Sub-Agent's context
(has session data)      (direct injection)    (sees it immediately)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SubagentStart&lt;/code&gt;'s &lt;code&gt;additionalContext&lt;/code&gt; is the most reliable way to inject context into Sub-Agents. It fires synchronously at spawn time, has access to all session data, and injects directly into the Sub-Agent — no file reading, no prompt manipulation, no Team Lead involvement required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 2: Filesystem for Cross-Hook State (Not Sub-Agent Communication)
&lt;/h3&gt;

&lt;p&gt;The shared filesystem (&lt;code&gt;.chorus/&lt;/code&gt; directory) is valuable for &lt;strong&gt;hook-to-hook&lt;/strong&gt; state passing (e.g., &lt;code&gt;pending/&lt;/code&gt; files relay agent names from &lt;code&gt;PreToolUse&lt;/code&gt; to &lt;code&gt;SubagentStart&lt;/code&gt;), but should not be the primary mechanism for Sub-Agent context injection. Use &lt;code&gt;SubagentStart&lt;/code&gt;'s &lt;code&gt;additionalContext&lt;/code&gt; for that instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 3: PreToolUse Captures + SubagentStart Executes
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;SubagentStart&lt;/code&gt; event doesn't provide the Sub-Agent's name (only &lt;code&gt;agent_id&lt;/code&gt; and &lt;code&gt;agent_type&lt;/code&gt;), but &lt;code&gt;PreToolUse:Task&lt;/code&gt; can extract it from &lt;code&gt;tool_input&lt;/code&gt;. The two hooks pass information via the filesystem (pending → claimed).&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 4: Async Hooks for Non-Blocking Cleanup
&lt;/h3&gt;

&lt;p&gt;Session closing, resource cleanup, notifications, and other operations that don't affect the flow should go in async hooks. Don't let cleanup logic block a Sub-Agent's exit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 5: Hooks Suggest, Don't Enforce
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;PreToolUse:Task&lt;/code&gt; injects a reminder to the Team Lead ("remember to include task UUID in the prompt"), but doesn't block the operation. In team collaboration, &lt;strong&gt;suggestions over enforcement&lt;/strong&gt; — overly strict hooks degrade the user experience.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Quick Start: Building Your Own Plugin
&lt;/h2&gt;

&lt;p&gt;If you want to build a Claude Code plugin for your own toolchain, here are the minimum viable steps:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create Directory Structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; my-plugin/.claude-plugin my-plugin/hooks my-plugin/bin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Write plugin.json
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-plugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My custom plugin for Claude Code"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.1.0"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Write Your First Hook
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;hooks/hooks.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"SessionStart"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"startup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${CLAUDE_PLUGIN_ROOT}/bin/on-start.sh"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;bin/on-start.sh&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
{
  "systemMessage": "My plugin is active!",
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "My Plugin is connected. Custom workflow instructions here."
  }
}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Test Locally
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x my-plugin/bin/on-start.sh
claude &lt;span class="nt"&gt;--plugin-dir&lt;/span&gt; ./my-plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Publish to Marketplace
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;.claude-plugin/marketplace.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-marketplace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Your Name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"plugins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-plugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./my-plugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.1.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;Claude Code's plugin system provides a complete extension mechanism — from Marketplace distribution, to MCP tool integration, to Hooks lifecycle management, to Skills knowledge injection. The introduction of Agent Teams (Swarm mode) makes multi-agent collaboration possible, and plugins make that collaboration manageable and observable.&lt;/p&gt;

&lt;p&gt;The Chorus plugin's practice demonstrates that &lt;code&gt;SubagentStart&lt;/code&gt;'s &lt;code&gt;additionalContext&lt;/code&gt; — which injects directly into the Sub-Agent's context — is the key to seamless multi-agent workflow automation. Combined with the shared filesystem for cross-hook state management and &lt;code&gt;PreToolUse&lt;/code&gt; for capturing spawn-time metadata, a fully automated session lifecycle can be achieved with zero boilerplate in the Team Lead's prompts.&lt;/p&gt;

&lt;p&gt;If you're interested in Chorus, visit &lt;a href="https://github.com/Chorus-AIDLC/chorus" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; to learn more. If you're building your own Claude Code plugin, we hope this article's experience helps you avoid some pitfalls.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>aidlc</category>
      <category>claudecode</category>
      <category>vibecoding</category>
    </item>
  </channel>
</rss>
