Repository navigation
Description
Start after the upstream sync (#5) lands. It builds on the ported Issues browser (#4), model roles, the delegate_task worktree option and pull-request linking.
Goal
Drive agent work directly from GitHub issues, with the issue and the agent threads staying in sync.
Required behavior
-
Start an agent from an issue. An action on an issue (the Issues page and the issue side panel) opens a thread whose task is the issue.
- It chooses the model through the user's model roles.
- It can use a worktree.
- The issue is linked to the thread the same way pull requests are linked today.
- This replaces or extends today's "Start in worktree".
-
An orchestrator works through a milestone. Point an orchestrator thread at a milestone.
- It starts workers on each open issue that isn't blocked.
- It respects the worker cap. The global rules set about 4, and heavy checks run through
agent-heavy. - It tracks the workers and reports per issue.
- It honors native "blocked by" dependencies and sub-issues.
-
Issues show agent status. An issue shows which thread or worker is on it and its live state: working, waiting on the user, or done. A worker's closeout posts back to the issue.
-
Agents keep issues current. As part of orchestration, agents can:
- create and split issues;
- change milestones;
- update issue bodies.
Approval rules decide which of these need the user's go: closing issues, editing user-written text, and anything else the user chooses.
Agent structure (user requirements, 2026-10-05)
- One main orchestrator. Each repository has exactly one main orchestrator thread, and it is in charge of every milestone and all orchestration for that repository. The app doesn't start or allow a second top-level orchestrator for the same repository.
- Every subagent is tied to one issue.
- Each subagent works on exactly one issue, and its thread name says which, e.g.
#243 Diagnose two-liquid feed-scale conditioning. - The issue-to-thread link is durable, as in part 3.
- Each subagent works on exactly one issue, and its thread name says which, e.g.
- Worktree icon. A subagent working in its own worktree shows the worktree icon from Worktree icon on child threads in their own worktree #7.
- The thread tree mirrors the issue tree.
- A subagent can own a parent issue. The workers for that issue's sub-issues are then that subagent's own subagents, not the main orchestrator's.
- A leaf issue's worker reports to the subagent that owns its parent issue. That subagent reports to the main orchestrator.
- This uses the existing delegation lineage. Results flow up only through final-message delivery: children never message their parents mid-task (Sync fork onto upstream's released orchestrator v2 #5 enforces this).
Open questions to settle before building
- Where the issue-to-thread link lives: thread metadata like pull-request linking, plus a marker on the issue.
- How the app recognizes "the" main orchestrator for a repository (a thread flag set when it is created or handed off?) and what handoff looks like.
- Whether milestone orchestration is a new orchestrator mode or a skill or instruction set on top of existing tools.
- Which parts need the user's approval in-app, and which are covered by standing rules.
Activity
tannerpolley commentedon Oct 5, 2026
Part of this work (user, 2026-10-05): "Browse all issues" opens the full list in the side panel, not the main panel. That is tracked in #6, which should land first or alongside, so starting agents from issues happens in the side panel next to the thread.
tannerpolley commentedon Oct 5, 2026
Design pass started 2026-10-05: a read-only GPT-6 Astra xhigh proposal that answers the three open questions and proposes 3–5 shippable slices, each marked interface-only or server. The user decides before any building.
tannerpolley commentedon Oct 5, 2026
Design proposal (GPT-6 Astra xhigh, read-only, 2026-10-05)
Status: proposal awaiting the user's decisions. Mobile is out of scope for this fork for now.
I recommend one ordinary orchestrator thread per repository, backed by a small server-owned ownership record, with issue orchestration instructions layered on the existing delegation tools. The server should enforce identity, ancestry, capacity, and approvals; the agent should decide how to do the work.
The durable issue link belongs in thread metadata. GitHub gets a managed status comment and closeout comments. Routine model handoff should keep the same app thread, preserving its children and pending results.
This proposal covers all eight requirements in #8. It builds on #6’s repository-scoped side panel and #7’s worktree indicator. It proposes no new general workflow engine, separate milestone conversations, or independent messaging protocol.
I read the issues, comments, and checkout on
sync/upstream-v2. The checkout was clean when inspected, atb6ae77a737. I made no edits, commits, checkouts, stashes, GitHub writes, or test runs.What the code already provides
These are verified facts; the recommendations that follow are proposals.
delegate_taskcreates an app-owned child under the calling thread, requires an active parent run, and uses stable command IDs for retries.parentThreadId, relationship, and root. Completion delivery also depends on the parent’s task node and run.parentThreadIdwould not safely transfer a live task.rolefield is a separate task classification.role.linkedPullRequestfield and newer multiple-link contracts.link_pull_requestdispatches a durable command; a server reactor watches linked PRs.The three open questions
1. Put the authoritative link on the thread; put a visible backlink and status on GitHub.
Add one nullable
linkedIssueto the app thread and its shell projection. It contains the existing issue reference—host, repository, number—plus the URL and GitHub’s stable repository/issue IDs. Numbers remain the human-facing identity; stable IDs prevent a repository rename from creating a second owner accidentally.Every newly created repository subagent must have exactly one issue before its run starts. Historical threads remain readable without fabricated links. One issue may retain several historical threads, but only one current owner at a time. A later implementation or review attempt can take ownership after the previous attempt finishes.
On GitHub:
The existing thread route already includes both environment and thread IDs: agentAwareness.ts:33. Turning that into a public GitHub backlink needs an explicitly configured, usable origin.
The comment is a mirror, not the ownership lock. Deleting it must not release the issue or cause another worker to start.
2. Use a mix: ordinary agent instructions plus a small amount of enforced server state.
There should be no new provider interaction mode and no second orchestration runtime.
Inject a short repository-orchestration instruction block into the registered root and its issue workers, following the existing model-role instruction mechanism. It tells the agent to:
delegate_task.The server, rather than those instructions, must enforce:
The agent chooses priorities, decomposition, implementation, and whether an issue’s acceptance criteria have been met. GitHub remains authoritative for issue text, milestones, native hierarchy, blockers, and open/closed state.
3. Identify the main orchestrator through a repository record, not its title, model, or a loose thread flag.
Create one record keyed by GitHub host and stable repository ID, containing the owning thread, selected project/workspace, ownership revision, paused/running state, worker cap, selected milestones, and approved policy.
A unique database key prevents two roots for the same repository in the owning environment—even when several local projects or worktrees point to it. Thread badges derive from this record. Renaming, forking, changing models, or pinning a thread never grants ownership.
“Start agent” and “Work through milestone” look up this record. The first action creates the root atomically if necessary; later actions route to it. They never silently create another top-level orchestrator.
For routine handoff, change the provider/model on the same app thread. Existing provider-switch planning operates on the current thread and its provider history, and
t3_thread_configurealready exposes selection changes: ProviderSwitchService.ts:69, thread/tools.ts:190. The root ID, children, approvals, and issue ownership stay put.For a genuinely new app thread:
Do not transfer live children by editing their lineage. Initially, fresh-thread handoff should wait for the subtree to drain. A user who needs an immediate model change can use same-thread handoff.
One boundary needs to be explicit: a repository has one authoritative T3 environment. Connected clients route work there; another environment does not automatically take over when it is offline. SQLite cannot guarantee exclusivity across unrelated, independently configured T3 installations. Global ownership across those installations would require a shared coordinator and is outside this minimal design. A GitHub comment is not a sufficient substitute.
How issue work would run
The issue action becomes Start agent, with a model-role choice, workspace choice, and the effective approval policy visible before starting. Preserve an optional “Prepare draft” action for the current unsent-composer behavior.
The start service resolves the selected issue repository and a matching project, reads the issue, then queues a start request to the registered root. The root delegates through the existing active-run path. The server validates the issue and parent at delegation time.
Repository resolution must use the issue’s selected repository. The current thread Issues helper intentionally prefers the fork’s origin repository over the upstream PR identity: ThreadDetailsIssueRows.tsx:42. Preserve that distinction.
For nested issues, the active tree is:
flowchart TD R["Repository orchestrator — all milestones"] R --> P["#120 Parent issue owner"] P --> A["#121 Sub-issue worker"] P --> B["#122 Sub-issue worker"] R --> C["#130 Independent issue worker"]Milestones select work; they do not add artificial thread levels.
A caller may delegate an issue only if it is:
If a selected sub-issue has no parent owner, start the required ancestor issue owners first. Do not silently attach it to the root. If this would enter an unapproved milestone or another repository, present that expansion for approval.
A reviewer is also an issue worker. Sequential review can become the issue’s next attempt under the same proper parent. Parallel independent review needs its own genuine sub-issue; do not create untracked review children or attach a same-issue child beneath itself.
Read native GitHub blockers and hierarchy. GitHub supplies APIs for issue dependencies and sub-issues and parents; the current T3 IssueService needs those additions.
An issue is eligible only when it is open, authorized, unclaimed, correctly parented, and all known blockers are closed. Missing permissions, incomplete pagination, or failed dependency reads mean eligibility unknown, not “unblocked.” Check again before launch. A parent issue does not implicitly block its children; hierarchy and dependencies serve different purposes.
Capacity should mean active work across the whole repository tree.
Default to four worker slots shared across all milestones and descendant levels. The repository root is the coordinator and is outside those four; a parent-issue agent occupies a slot while actively working.
Apply admission when a worker run starts, including resumed runs and completion follow-ups—not just when a child is first created. A child may be queued without starting its provider. Worktree preparation should also be bounded.
A live worker waiting on user input retains its slot. An issue owner whose turn has ended and is waiting on children does not. Managed delegation uses
mode="async"so an ancestor does not hold a blocking tool call while waiting for children that cannot get a slot.This needs one repository-level admission rule shared by initial starts and resumes. Counting four children separately under every parent would violate the requested cap.
Keep
agent-heavyin the worker instructions for heavy checks. Worker admission and command memory limits address different resources.Status and writeback
Derive live status from existing runs, pending requests, delegated progress, and background work:
Agent done and GitHub issue closed are separate states. A completed implementation can still need review, a push, a PR, or merge approval. Do not close an issue simply because a provider turn ended.
Publish closeouts from the existing delegated-result milestone, after
delegatedTaskProgressreportsresult_available. This avoids an early parent closeout while its children are still running.Use the existing event/outbox boundary for GitHub writes. It already commits command receipts, events, projections, and effects together: EventSink.ts:515. Give every outgoing closeout a stable key derived from its issue, thread, and result event.
GitHub calls can have ambiguous outcomes after network failures. Persist returned IDs, and reconcile the marker before retrying an uncertain create. Promise recoverable, deduplicated delivery—not an impossible cross-system transaction.
The app gets live status through existing server events. Coalesce GitHub status-comment updates; do not write for every token or tool call. Display “GitHub update pending” when publishing fails.
Minimal data, contracts, and migrations
Keep the new state limited to three responsibilities:
linkedIssue: stable identity, current repository/number, URL. Included in thread and shell projections. Historical links remain on their threads.The issue work pointer answers “who owns this now?” Thread bindings answer “what was this thread about?” Those are distinct responsibilities, not competing sources for the same fact.
Contract changes:
linkedIssueto thread, shell, launch/delegation, MCP read, and client representations.delegate_task; require it for new repository-managed children. Persist it in the same command that creates the child.Suggested migration allocation:
Thread JSON gains optional fields decoded as absent/null for old data. Do not infer issue links from titles or issue URLs in old messages. Replay must reconstruct the new projections from durable events.
The current manifest ends at 058 and explicitly requires new migrations for future V2 changes: Migrations.ts:141. These numbers are proposed reservations; recheck them when implementation begins.
Keep issue numbering in the actual title.
Store
#243 Diagnose two-liquid feed-scale conditioningas the title. Do not add a decorative prefix only in the sidebar.Reuse
titleSource:generated: suffix comes from the issue title.user: suffix was explicitly renamed by the user.A linked issue owns the numeric prefix in either case. A single server helper should normalize it on creation, linking, and renaming. Linked threads bypass first-message and periodic AI retitling; stale title-generation results must also be rejected. “Use issue title” restores the generated suffix. No third title-source value is necessary.
Service, MCP, and UI boundaries
Extend IssueService for GitHub reads and writes, credential verification, pagination, and rate limits. Add one domain service, IssueWorkService, for starting/linking work, repository ownership, eligibility checks, approval policy, and writeback coordination. Keep pure invariants in the orchestration command path and external I/O in effects.
Expose thin MCP tools such as:
link_issue,unlink_issue, andget_issue_work.start_issue_workandget_repository_orchestration.propose_issue_change, with a typed operation for create, update, milestone, hierarchy, or close/reopen.Extend
delegate_task; do not add a competingdelegate_issue_task. Where useful, expose the same methods through WebSocket RPC and scheduled tasks.Existing issue RPCs have read scope, while PR writes and schedule mutations use operate scope: RpcAuthorization.ts:95. New starts and mutations need operate scope. Approval-policy changes and mutation approval must additionally verify that the actor is the user; an agent cannot acquire that authority by setting a payload field.
The UI owns presentation and input:
Follow the service boundary already documented in the repo: handlers decode, call a service, and map errors.
Approval rules
Starting work is explicit authorization for the selected scope and displayed publishing policy. It should not require another approval for each eligible worker.
Do not identify “agent-written” text by GitHub author login: the agent and user may share an account. Track the baseline of bodies written through this service; if it no longer matches, treat the content as externally edited.
Use
t3_request_user_inputfor questions about scope or choices. Its definition explicitly says it never approves a permission request: thread/tools.ts:136.GitHub mutations need a durable application approval tied to the exact proposal. The tool returns a pending-change reference; the user approves in-app; the server executes and records the result. Parent agents may help resolve ordinary questions, but cannot approve a protected mutation on the user’s behalf.
Re-read the affected issue before execution. If its relevant content differs from the approved baseline, return a conflict for a new preview. This detects stale proposals; it is not a claim that GitHub provides atomic compare-and-swap for every mutation.
Failure and recovery
For external changes, use a narrow server reconciliation pass registered with the existing Scheduler, following the PR reactor’s stale-read guards and failure reporting. Poll only active repository plans and relevant dependencies. The Scheduler already prevents a source from overlapping itself: Scheduler.ts:24.
Recurring user schedules should target repository ownership dynamically, so fresh-thread handoff does not leave schedules dispatching to the retired root. Do not automatically create a persistent user schedule merely to implement milestone progress.
Surface and provider coverage
The full Issues page remains all-repositories; #6’s side-panel list must remain scoped to the repository from which it was opened. Starting an agent should preserve the adjacent issue view.
Web and desktop share the main UI implementation. Mobile needs issue links, status, start controls, and approval responses through the same contracts. Shared selectors belong in
packages/client-runtime. Existing mobile approval cards are tied to live provider approvals, so durable issue-change approval needs explicit client support rather than assuming the current card works unchanged: PendingApprovalCard.tsx:27.Use one Start action implementation for issue details, lists, command-palette access, and any keybinding. Supply reverse actions: pause/resume, unlink where allowed, cancel/retry, and visible handoff history. An active issue worker cannot silently unlink or switch issues.
For Codex, Claude, Cursor, Grok, OpenCode, and Antigravity, the proposed route is the same app-owned MCP delegation path. Provider-native untracked spawning must not bypass issue assignment, ancestry, or the cap. In managed issue sessions, disable native delegation where supported and advertise guaranteed issue orchestration only for adapters where that restriction can be enforced.
I did not verify native-spawn suppression for all six adapters. That is an implementation prerequisite, not an established capability. Instructions alone cannot guarantee “every subagent has one issue.”
Remote, relay, and tunnel clients use the owning server’s credentials and services. No client executes
gh, assumes a local checkout, or embeds a localhost origin.Five independently shippable slices
#6 and #7 are sibling prerequisites to reuse, not work to duplicate inside #8.
#numbertitle; working/waiting/done status; recoverable status and closeout comments. No automatic milestone sweep. This delivers 1, 3, and 6 early.The first implementation proof should be deliberately narrow: select one issue, start one properly named worker in a worktree, observe waiting and completion, see its closeout on GitHub, and retry the accepted start/writeback without producing duplicates.
Focused later checks should cover concurrent ownership claims, nested capacity, title-refresh races, pending approvals surviving restart, stale body edits, and re-parenting without misdelivered results. No repo-wide test run is necessary to establish those behaviors.
Risks and scope cuts
The largest correctness risks are live lineage changes, counting capacity only at initial spawn, treating a finished turn as completed issue work, and claiming global ownership with an environment-local lock. The design above gives each an explicit boundary.
The main operational risks are GitHub rate limits, ambiguous mutation responses, external edits, and native provider tools bypassing T3’s managed delegation. Keep synchronization narrow and capability claims honest.
If scope must shrink, defer fresh-thread transfer first: same-thread model handoff covers the frequent use case. Next defer automatic splitting/body editing, retaining reviewed proposals; then defer recurring schedules and richer GitHub status-comment updates. Preserve the durable issue link, numbered titles, status, final closeouts, parent guard, singleton owner, and shared admission rule. Those are the behaviors that make issue-driven work dependable.
tannerpolley commentedon Oct 5, 2026
User decisions (2026-10-05):
tannerpolley commentedon Oct 6, 2026
Slice 1 (single-issue agent work) is merged into
sync/upstream-v2and ships in fork build v2.9.3 (staged; goes live on the next server restart).#N <title>and protected from automatic retitling.Review fixes (bb1e80c, 466acd8):
Not yet tried in a real client. Slices 2–5 and mobile are still open.
tannerpolley commentedon Oct 6, 2026
Steps 2–5 are built into fork build v2.10.0 (
sync/upstream-v2@ 4034fa3). It installs when the app next quits; database backup is taken first. Migrations 061 RepositoryScheduledWork and 062 IssueChanges.propose_issue_change. Create, split and milestone moves run automatically unless Settings → Customizations → "Ask before agents create, split or move issues" is on. Body edits and close/reopen always ask, with an exact preview. Durable, conflict-checked and recoverable (no duplicate issues).Each step had a read-only GPT-6.1 Sol review: 8, 4 and 6 defects respectively, all fixed with regression tests. Combined branch: 2,219 tests pass across the touched suites. The one failure is
MessagesTimeline.test.tsx, which already fails to load before this work (node:seaimport). Typechecks are clean.Not yet tried in a real client. Mobile is out of scope.
tannerpolley commentedon Oct 13, 2026
Checked in the fork sandbox on 2026-10-13 (in-app browser, test repo tannerpolley/t3code-sandbox).
Works:
Bug found and fixed (
a04508d703onsync/upstream-v2, not released yet):supportsManagedIssueWorkcompared the driver with "claude", but Claude's driver kind is "claudeAgent". So Start agent failed whenever the model role chose Claude, and restoring a Claude lead (the ePC-SAFT recovery) would have been rejected. The test fixture used the same wrong name. A new test is tied to the real drivers, and the Start error now includes the rejected target's reason.Smaller things:
Close after the fix ships in v2.11.1.
tannerpolley commentedon Oct 13, 2026
Shipped in v2.11.1 (tag
release/v2.11.1, now onmain):cancel_issue_requestandrestore_repository_root.Tested in the sandbox in the in-app browser on 2026-10-13. Follow-ups have their own issues (#13 roadmap, #29 Project chips, #30).