Background
With the recent attribution-based session filtering (commits d8c4df6, e4f8a3f on all-chats), the Fleet view now only shows sessions that are attributed to each specific agent. This is correct — when multiple agents share the same working directory, we don't want native CLI sessions duplicated under every agent.
However, this means unattributed sessions (created via claude CLI directly, not through herdctl) are only visible in the global recent sessions list and All Chats view. Users who have pre-existing Claude Code conversations they want to manage through herdctl have no way to associate them with an agent.
Feature: Assign to Agent
Allow users to assign unattributed Claude Code sessions to a specific agent from the web UI. This is a metadata-only operation — it creates a herdctl session file in .herdctl/sessions/ that maps the existing session ID to the chosen agent. No JSONL files are modified.
Once assigned:
- The session appears under the agent in the Fleet sidebar
- The session history is fully viewable (already works today for read-only sessions)
- The user can continue the conversation through the web UI — the SDK picks up the existing JSONL by session ID, loads conversation history into context, and continues seamlessly
UI Flow
Entry Point
When viewing an unattributed session in the chat view, show an "Assign to Agent" button (exact placement TBD — likely in the session header area).
Dialog
Clicking the button opens a dialog with:
-
Agent Picker — dropdown or select showing agents that share the same working directory as this session. When multiple agents resolve to the same directory (common pattern: agents in subdirectories with working_directory: ../../), the user picks which one to assign to.
-
Session Table — a table of all unattributed sessions in the same working directory, with three columns:
| Column |
Description |
| ☐ (checkbox) |
Row-level selection. Header checkbox selects/deselects all. |
| Session Name |
customName → autoName → preview → "Untitled session" fallback chain |
| Date |
Relative time (e.g., "2 hours ago") or absolute date |
- The session that triggered the dialog should be scrolled into view with its checkbox pre-checked
- All other sessions start unchecked
-
Action Buttons — two side-by-side submit buttons:
| Button |
Behavior |
| Assign Selected to Agent |
Assigns only the checked sessions to the selected agent |
| Assign All to Agent |
Shortcut — assigns every unattributed session in the directory to the selected agent (equivalent to select-all + assign selected) |
Backend
For each session being assigned:
- Create a platform session YAML file in
.herdctl/sessions/ mapping sessionId → agentName with origin: "web"
- Invalidate the session discovery cache for the affected working directory
- The session will immediately appear under the agent in the Fleet sidebar on next load
Technical Notes
- The attribution system (
session-attribution.ts → buildAttributionIndex()) already reads .herdctl/sessions/ platform files. No changes needed to the attribution layer — just create the files.
SessionDiscoveryService.getAllSessions() already identifies unattributed sessions (those with origin: "native" and no agentName). The dialog's session list can be populated from this.
- Agent list for the picker: filter
FleetManager.getAgents() to those whose resolved workingDirectory matches the session's directory.
- The
getAgentSessions() attribution filter (commit e4f8a3f) means newly assigned sessions will immediately appear under the correct agent without any additional changes.
Out of Scope (for now)
- Un-assigning / reassigning sessions (can be done later)
- Auto-assigning based on heuristics (git branch matching, etc.)
- Assigning sessions from the All Chats page (start with the single-session chat view entry point)
Background
With the recent attribution-based session filtering (commits
d8c4df6,e4f8a3fonall-chats), the Fleet view now only shows sessions that are attributed to each specific agent. This is correct — when multiple agents share the same working directory, we don't want native CLI sessions duplicated under every agent.However, this means unattributed sessions (created via
claudeCLI directly, not through herdctl) are only visible in the global recent sessions list and All Chats view. Users who have pre-existing Claude Code conversations they want to manage through herdctl have no way to associate them with an agent.Feature: Assign to Agent
Allow users to assign unattributed Claude Code sessions to a specific agent from the web UI. This is a metadata-only operation — it creates a herdctl session file in
.herdctl/sessions/that maps the existing session ID to the chosen agent. No JSONL files are modified.Once assigned:
UI Flow
Entry Point
When viewing an unattributed session in the chat view, show an "Assign to Agent" button (exact placement TBD — likely in the session header area).
Dialog
Clicking the button opens a dialog with:
Agent Picker — dropdown or select showing agents that share the same working directory as this session. When multiple agents resolve to the same directory (common pattern: agents in subdirectories with
working_directory: ../../), the user picks which one to assign to.Session Table — a table of all unattributed sessions in the same working directory, with three columns:
customName→autoName→preview→"Untitled session"fallback chainAction Buttons — two side-by-side submit buttons:
Backend
For each session being assigned:
.herdctl/sessions/mappingsessionId → agentNamewithorigin: "web"Technical Notes
session-attribution.ts→buildAttributionIndex()) already reads.herdctl/sessions/platform files. No changes needed to the attribution layer — just create the files.SessionDiscoveryService.getAllSessions()already identifies unattributed sessions (those withorigin: "native"and noagentName). The dialog's session list can be populated from this.FleetManager.getAgents()to those whose resolvedworkingDirectorymatches the session's directory.getAgentSessions()attribution filter (commite4f8a3f) means newly assigned sessions will immediately appear under the correct agent without any additional changes.Out of Scope (for now)