# Task Sidebar Simplification Plan ## User Decisions - First pass is manual project-local tasks only. Future GitHub/Linear linking should not be built now. - The right sidebar should put Tasks first, while keeping existing process, agent tree, and scratchpad navigation secondary. - Generic palette-spawned top-level agents must not inherit a selected task. Only explicit task-scoped actions, such as `Start agent for task`, create task-bound agents. - Agents should receive task context only when they are task-bound, and task-bound agents should be told to register task worktrees through MCP. ## UX Review: Missing Pieces Today - There is no task model. The current right rail in `internal/app/sidebar.go` is `Processes`, `Agent Tree`, and `Scratchpads` only. - There is no task list, task detail view, task-focused navigation entry, or visible way to discover existing tasks. - Task creation is absent. Existing creation flows are process/agent palette actions in `internal/app/palette.go` and `internal/app/app.go`. - There is no explicit task-scoped launch action, so a user cannot intentionally start an agent for a task. - MCP has no task context and no worktree registration tool, so agents cannot tell patterm which worktrees belong to a task. - Current process rows are not grouped by task and do not surface task membership. - Sidebar empty states are generic process/scratchpad placeholders and do not advertise task creation. - The sidebar has no click or row activation model. First pass should stay keyboard/palette driven rather than inventing mouse interactions. - Cleanup/status/archive and external issue links are real task-list UX needs, but they are out of scope for this first simplification pass unless requested later. ## Recommended Scope Implement a minimal first-class task feature: - Persist manual tasks per project. - Render Tasks as the first sidebar section. - Let users create, open, and rename tasks through the palette. - Render a focused task detail view in the main viewport. - Add explicit `Start agent for task` actions for focused tasks and task-bound children. - Thread `TaskID` through children spawned from task context. - Expose task context and `task_register_worktree` only to task-bound MCP callers. - Fix PTY working-directory propagation, because task worktree paths are otherwise misleading. Do not add GitHub/Linear linking, task import, agent-side task creation, task list MCP tools, click handling, or broad task/project management UI in this pass. ## Implementation Steps ### 1. Add Project Task Store Add `internal/task/task.go` and `internal/task/task_test.go`. Persist to `$XDG_DATA_HOME/patterm/projects//tasks.json`, matching the existing project-local persistence pattern in `internal/persist/persist.go`. Minimal structs: ```go type Task struct { ID string `json:"id"` Title string `json:"title"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` Worktrees []Worktree `json:"worktrees,omitempty"` } type Worktree struct { Path string `json:"path"` Branch string `json:"branch,omitempty"` CreatedByProcessID string `json:"created_by_process_id,omitempty"` RegisteredAt string `json:"registered_at"` } ``` Store API: ```go Open(projectKey string) (*Store, error) (*Store).List() []Task (*Store).Get(id string) (Task, bool) (*Store).Create(title string) (Task, error) (*Store).Rename(id, title string) (Task, error) (*Store).RegisterWorktree(taskID string, wt Worktree) (Task, error) ``` Keep it deliberately small: - Generate opaque IDs like `task_`. - Validate non-empty titles. - Use stable list ordering. - De-duplicate worktrees by cleaned absolute path. - Atomic write via `tasks.json.tmp` then rename. - No status, assignee, priority, external refs, labels, or issue links yet. ### 2. Fix PTY Working Directory Change `internal/pty/pty.go`: - Update `Start(argv, env, cols, rows)` to `Start(argv, env, workDir, cols, rows)`. - Set `cmd.Dir = workDir` when non-empty. Update call sites: - `internal/app/child.go: startPTY` passes `c.WorkDir`. - `internal/harness/session.go` and `internal/harness/restart_persist_test.go` pass an empty workdir or project dir as appropriate. - `cmd/spike/main.go` passes an empty workdir. Add a focused PTY/app test that spawns `sh -lc pwd` with a temp workdir and verifies the child runs there. ### 3. Thread Task Context Through Runtime State Modify `internal/app/child.go`: - Add `TaskID string` to `Child`. - Add `taskID` to `newChildEntry`. Modify `internal/app/session.go`: - Add `TaskID string` to `SpawnSpec`. - Pass it into `newChildEntry`. - Keep persistence limited to top-level command entries. Do not persist agent/task process trees in `processes.json`. Modify `internal/app/launch.go`: - Avoid adding more positional string parameters. Introduce a small launch context struct, for example: ```go type LaunchContext struct { ParentID string TaskID string WorkDir string } ``` - Update `LaunchAgent`, `LaunchCommandPreset`, `LaunchCommandArgv`, and `LaunchTerminal` to accept this context or an equivalent minimal option. - Generic palette spawns pass empty `TaskID` even when a task is focused. - Explicit task actions pass `TaskID`. - MCP spawns inherit `TaskID` from the caller only when the caller is task-bound. ### 4. Wire Task Store Into App State Modify `internal/app/app.go`: - Open `task.Open(opts.ProjectKey)` during `Run`, near scratchpads/trust/persist stores. - Add `tasks *task.Store` to `uiState`. - Add `focusedTaskID string`, mutually exclusive with `focusedID` and `focusedPad`. - Add `tasksCacheMu` / `tasksCache`, mirroring `padsList()`. - Add `tasksList()`, `invalidateTasksCache()`, and `tasksChanged()`. - Pass the task store to `newToolHost`. Modify `internal/app/host.go`: - Add `tasks *task.Store` to `toolHost`. - Extend `newToolHost` to accept it. - Add a small `taskSink` interface like `tasksChanged()` so MCP worktree registration can refresh the sidebar/detail view. ### 5. Make Tasks First In The Sidebar Modify `internal/app/sidebar.go:drawSidebar`: - Render `Tasks` before `Processes`. - Show empty state as something actionable, for example `(Ctrl-K create task)`. - Show task rows with focus marker, title, and a compact suffix such as `2 worktrees` or `2w`. - Keep current `Processes`, `Agent Tree`, and `Scratchpads` sections after Tasks. - Do not add mouse or click behavior in this pass. Modify `internal/app/tree.go`: - Extend `navEntry` with `taskID string` and `isTask()`. - Change `empty()` to include task entries. - Change `sidebarNav` and `nextNavEntry` to accept tasks and order entries as `tasks -> processes -> active agent tree -> scratchpads`. - Keep `nextChildID` compatibility tests working for process-only behavior. Modify `internal/app/app.go`: - Add `focusTask(taskID string)`. - Add `renderTaskView(taskID string)` and `repaintFocusedTask()`. - Update `focusProcess` and `focusScratchpad` to clear `focusedTaskID`. - Update `repaintFocused`, `repaintFocusedWithChrome`, `restoreView` inside `closePalette`, and Ctrl-W/S pending nav handling to route task entries correctly. - Update `drawStatusLine` to show `task: ` when a task is focused, and optionally `task: <title>` when a focused child is task-bound. - Update `renderEmptyState` copy to mention tasks, for example `Press Ctrl-K to create a task or spawn an agent/process`. Focused task detail view should be read-only and compact: - Title and task ID. - Registered worktrees. - Task-bound running children if cheap to derive from `Session.Children()`. - Hints: `Ctrl-K task actions`, `Ctrl-W/S navigate`. ### 6. Add Palette Task Actions Modify `internal/app/palette.go`: - Add task list and focused task fields to `paletteState`. - Add `taskID string` to `paletteAction`. - Add task action kinds: - `task-create-form` - `task-create-submit` - `task-switch` - `task-rename-form` - `task-rename-submit` - `task-start-agent` - Add a task macro if useful, but do not make it required for the first patch. - Reuse `renameForm` for create/rename by allowing subject `task` and different submit kinds. Palette item behavior: - Global/Open group includes `Create task...` and `Open task: <title>`. - When a task is focused, show `Rename task` and `Start agent for task: <preset>` rows. - When a task-bound child is focused, show `Open task: <title>` and `Start another agent for task: <preset>` rows. - Normal `Spawn agent: <preset>` remains unscoped and passes empty `TaskID`. Modify `internal/app/app.go:openPaletteLocked`: - Pass `tasksList()`, `focusedTaskID`, and current focused child task information into `newPalette`. Modify `internal/app/app.go:closePalette`: - `task-create-submit`: create task, invalidate cache, focus the new task. - `task-switch`: focus task. - `task-rename-submit`: rename task, invalidate cache, redraw/focus task. - `task-start-agent`: launch selected agent preset with the task ID and a task initial prompt. Task initial prompt should be injected only for task-bound agents. Keep it single-line because agent input submission is line-sensitive: ```text [system: you are working on patterm task "<title>" (<task_id>). If you create or use git worktrees for this task, call task_register_worktree with the path and branch.] ``` Include known registered worktrees in the prompt only if the list is short; otherwise rely on `whoami`. ### 7. Add Conditional MCP Task Context And Worktree Registration Modify `internal/mcp/tools.go`: - Add MCP structs `TaskInfo`, `TaskWorktree`, and `TaskRegisterWorktreeArgs`. - Add optional `Task *TaskInfo `json:"task,omitempty"`` to `WhoAmI` and `ProjectStatus`. - Do not add task fields to `ProcessInfo` in this pass, so unbound agents cannot discover task assignments through `list_processes`. - Extend `ToolHost` with: ```go CallerTask(processID string) (TaskInfo, bool) RegisterTaskWorktree(callerID string, args TaskRegisterWorktreeArgs) (TaskInfo, error) ``` `TaskRegisterWorktreeArgs` should contain only `path` and optional `branch`. Do not accept a task ID from the caller; the host infers the task from `callerID`. Modify `internal/mcp/protocol.go`: - Replace the constant-only `serverInstructions` path with a helper that can append task-bound instructions only when `host.CallerTask(callerID)` is present. - Change `toolCatalog(role)` to `toolCatalog(role, taskBound bool)`. - Advertise `task_register_worktree` only when `taskBound` is true. - Keep `spawn_agent` hidden from sub-agents as today. Modify `internal/mcp/tools.go:callTool`: - Add case `task_register_worktree`. - Reject unbound callers with `role_forbidden`. - Call `h.RegisterTaskWorktree(callerID, args)` and return the updated task. Modify `internal/app/host.go`: - Add helper `callerTaskID(callerID string) string` based on `Child.TaskID`. - Implement `CallerTask` by resolving the caller child and task store entry. - `WhoAmI` includes `Task` only when the caller child has a valid task ID. - `GetProjectStatus` includes the same optional task only for task-bound callers. - `SpawnAgent` and `SpawnProcess` inherit `TaskID` from the caller. - Replace `wrapSubAgentPrompt` with a small prompt builder that can include sub-agent instructions, task instructions, or both. Task instructions should be added whenever the spawned agent is task-bound, even when caller `agent_instructions` is empty. - `RegisterTaskWorktree` resolves relative paths against the caller child `WorkDir`, falling back to the project dir, cleans/absolutizes the path, stores it, and triggers `tasksChanged()`. Important security/UX rule: - Unbound MCP callers should not receive task context, task tool availability, task worktree data, or task IDs. ### 8. Tests Add or update focused unit tests before broad harness work. Task store: - `internal/task/task_test.go`: create/list persists across reopen. - Validate empty title errors. - Rename updates title and `updated_at`. - Register worktree de-duplicates cleaned absolute paths. Runtime/spawn: - `internal/app/session_test.go` or focused launch tests: `TaskID` lands on spawned child. - MCP spawn from task-bound caller propagates `TaskID` to child. - Generic palette spawn while `focusedTaskID` is set keeps `TaskID == ""`. - Explicit `task-start-agent` sets `TaskID` and injects task prompt. Sidebar/navigation/palette: - `internal/app/tree_test.go`: task nav entries precede processes, agent tree, and scratchpads. - `internal/app/palette_context_test.go`: focused task shows task actions; generic spawn remains unscoped. - Add render-level tests only where existing helpers make that cheap; avoid brittle full-frame snapshots. MCP: - `internal/mcp/protocol_test.go`: unbound tools/list omits `task_register_worktree`; task-bound tools/list includes it. - `internal/mcp/tools.go` tests or app host tests: unbound registration is rejected. - `internal/app/host_test.go`: task-bound `whoami` includes task; unbound `whoami` omits task. - `internal/app/host_test.go`: registration stores an absolute path and returns updated task info. PTY working dir: - Add the smallest test proving a PTY child honors configured `WorkDir`. Harness: - Add one lightweight scenario under `internal/harness/scenarios/`: - Open palette. - Create a task. - Assert sidebar shows `Tasks` and the task title. - Start a fake agent through `Start agent for task`. - Use MCP `whoami` from that fake agent if feasible, or assert the prompt/context path indirectly through captured output. ### 9. Changelog Update `CHANGELOG.md` under `[Unreleased]`. Suggested bullets: ```md ### Added - Added project-local manual tasks in the right sidebar, with palette actions to create tasks, open tasks, rename tasks, and start task-scoped agents. - Added task-bound MCP context and `task_register_worktree` so agents launched for a task can register git worktrees they create. ### Fixed - Child PTYs now honor their configured working directory when launched. ``` ## Verification Commands Run focused checks first: ```sh go test ./internal/task/... go test ./internal/pty/... go test ./internal/app/... go test ./internal/mcp/... ``` Then run broader checks: ```sh go test ./internal/harness/... go test ./... go build -o ./bin/patterm ./cmd/patterm ``` Manual TUI smoke test: ```sh ./bin/patterm --project /home/harry/Dev/patterm ``` Manual flow to verify: - Press `Ctrl-K`, create a task, and confirm it appears first in the sidebar. - Focus the task from the sidebar navigation with `Ctrl-W/S`. - Press `Ctrl-K`, start an agent for that task, and confirm the task remains visible and the agent is task-bound. - Start a normal agent through `Spawn agent` while the task is selected and confirm it is not task-bound. - From a task-bound fake/test agent, call `whoami` and confirm task context appears. - From an unbound agent, call `whoami` and confirm no task context appears. - From a task-bound agent, call `task_register_worktree` and confirm the task detail view/sidebar suffix updates. If harness tests fail with Unix socket or PTY permission errors, rerun them in an environment that permits sockets and PTYs, as documented in `AGENTS.md`.