Give change.detect deadline-aware phase budgets and return partial results, not tool errors
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 5/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 25/100
- issue の種類
- 機能追加
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- go
- 領域
- api, backend, performance, testing
調査の方向性
Read internal/mcp/tools_analysis.go and internal/analysis/diffmap.go first, then trace the shared helpers in internal/mcp/analysis_diff.go and the deadline rules in internal/mcp/tool_deadline.go. Implement phase budgets while preserving fast parent cancellation and partial results, then run the named detect budget, lowering, enrichment, and cancellation tests. Done means partial work returns before the reserved deadline and the stated load and regression targets pass.
索引モデルが issue の本文から書いたものです。
説明
Why
Under load, change.detect (detect_changes) can still run past its MCP tool deadline (60 s by default). When that happens the agent gets an abandoned call or a raw context deadline exceeded error, not an answer. A report on macOS arm64 saw 59 s abandonments on a 3-file compare. Each phase has a fixed budget that ignores the request deadline. Added together they come to about 36 s before memberships and serialization even start:
- freshness wait: up to 3 s
- git diff plus symbol lowering: one shared 30 s cap
- impact walk: 3 s
- community/process memberships: limited only by the request context
With store contention the total can go past the deadline. Worse, finished work gets thrown away. If the deadline fires after enrichment has completed, the impact result is dropped and a tool error comes back. Clients then retry right away, and each retry adds another full computation on the daemon.
Target for this release: 0 abandonments in 200 detect calls under contention, and detect p95 ≤ 15 s.
Current state
Checked on main (015b3410). None of this work has been merged.
internal/mcp/edit_serialization.go:defaultMutationReindexWait = 3 * time.Secondis the freshness wait thathandleDetectChangesgoes through (awaitMutationFreshnessForRepos).internal/analysis/diffmap.go,MapGitDiffContext: puts both phases under onecontext.WithTimeout(ctx, 30*time.Second)(line ~99) and passes that same ctx tojoinHunksToSymbolsContext(line ~113). A slow lowering therefore uses up the git budget. It comes back as a barectx.Err()from the per-hunk and per-node checks (~223/234/250), not as "git diff failed".internal/mcp/tools_analysis.go,handleDetectChanges: anyMapGitDiffContexterror becomesmcp.NewToolResultError(~384-387), and so does anyanalyzeDiffImpacterror (~436-439). For a non-ready graph the handler setsreader = niland returns file changes withcomplete: falseandrisk: "UNKNOWN". That path already works and must keep working.internal/mcp/analysis_diff.go:diffImpactContextcaps the walk at a fixed 3 s.analyzeDiffImpactreturns an error whenanalysisMembershipsForNodessees a cancel (~95-97), and it ends withreturn result, ctx.Err()(~120), which throws away a finished result.- The same diff and membership helpers are used by
review.diff_context(internal/mcp/tools_enhancements.go,handleDiffContext, ~3054/3070) andpr_context(internal/mcp/tools_pr_review_context.go, ~191/235). - The tool deadline comes from
internal/mcp/tool_deadline.go:DefaultToolCallTimeoutis 60 s, the override env isGORTEX_MCP_TOOL_TIMEOUT, andtransportDeadlineMarginis 1 s.AbandonedToolCalls()is a counter shared by the whole process. - There is no
detectBudgethelper, nolowering_truncatedfield and noenrichmentfield yet. bench/daemon-latencyhas nodetect_changeslanes, no dirty-tree fixture and no writer option yet. They are added by the detect validation and harness work, which is tracked separately.
This work builds on the structured detect envelope (complete / truncated / lower_bound on every response, plus per-phase timings_ms). That envelope is tracked separately and must land first.
What to do
- Split
MapGitDiffContextso git keeps its own capped context and lowering gets a separate budget. A git timeout or failure stays a tool error, because a failedgit diffis not an empty diff. - Add a
detectBudgethelper in a new file,internal/mcp/detect_budget.go:- It derives a soft deadline from
ctx.Deadline()minus a reserve for serialization (start at 2 s). - Each phase gets
min(fixed cap, share of remaining time). Starting shares: git ≤ 40 %, lowering ≤ 25 %, impact ≤ 25 %, memberships get the rest. - Tune the shares from the per-phase
timings_msthat the envelope work exposes, and record the final values in the PR.
- It derives a soft deadline from
- When a phase hits its soft budget, return what it has plus a reason:
- lowering:
changed_symbolsis a prefix,lowering_truncated: trueis set, and the files not yet joined are listed. - impact:
truncated: true, whichAnalyzeImpactContextalready produces. - memberships hit their budget or see a cancel: return a result with
enrichment: "unavailable", not an error. analyzeDiffImpactreturns the result withenrichment: "complete" | "partial" | "unavailable"instead ofctx.Err().
- lowering:
- Any partial result sets
complete: false. A partialchanged_symbolsis never paired withrisk: "NONE". - When the caller (parent context) cancels, as opposed to the soft budget running out, keep returning
ctx.Err()promptly. - Keep the
reader = nilpath for a non-ready graph ahead of all budget logic. - Use the same helper in
review.diff_contextandpr_context. - All new response keys are additive. Existing keys keep their shape.
Out of scope
- Count-only detect output (
detail: counts) and lowering hunks per file instead of per hunk. - Single-flight coalescing of identical concurrent detect calls. That is a separate issue and it depends on this one.
- Handling of failed or pending receipts: refusals, retry limits, reconcile.
- Changes to the global tool deadline or to the dispatch queue.
- Adding the
detect_changeslanes to the latency benchmark (covered by the detect validation and harness work).
Done when
- Reference load: a 4-worktree workspace with about 130k nodes per worktree (±20 %; per-worktree node and edge counts recorded on this issue), a dirty tree of 10 modified files / about 40 hunks / 1 deleted file, a background
analyze healthloop and 2 concurrentedit_filewriters. On this load,detect_changeswith full detail has 0 abandonments in 200 calls. - On the same load, detect p95 ≤ 15 s on Apple M-series 16 GB and on a GitHub ubuntu-24.04 4 vCPU runner, and p95 ≤ 25 s on GitHub windows-2022.
-
TestDetectPhaseBudgetTablehas a slow-reader case for each phase (freshness, git, lowering, impact, memberships). Every case returns beforedeadline − reservewithcomplete: falseand the right reason. -
TestDetectSlowLoweringReturnsPrefixgets a prefix withlowering_truncated: true, never a rawcontext deadline exceeded. -
TestDetectDeadlineAfterEnrichmentReturnsResultgets the result back, not an error. -
TestDetectParentCancelExitsFast: the handler exits within 100 ms of a parent cancel. - Over 50 forced-abandon runs on the reference load (ubuntu-24.04 4 vCPU), the handler goroutine exits ≤ 1 s after the deadline at p99, and never later than 10 s.
- A test proves a git failure is still a tool error and never shows up as "no changes".
- No test or fixture produces a partial
changed_symbolstogether withrisk: "NONE". -
review.diff_contextandpr_contextuse the same helper, with one test for each covering a budget hit. - No regression: on the gortex repo (~31k nodes, single worktree, no contention), full detect returns
complete: truewith no budget reason in 20 of 20 runs. Existing detect golden outputs are unchanged apart from the new additive keys (lowering_truncated,enrichment). -
go test -race ./internal/mcp/... ./internal/analysis/...passes.
How to verify
go test -race -run 'TestDetect(PhaseBudget|SlowLowering|DeadlineAfterEnrichment|ParentCancel)' ./internal/mcp/. The cancellation tests readAbandonedToolCalls(), so they must not uset.Parallel().- Run
bench/daemon-latencywith thedetect_changeslanes, the dirty-tree fixture and 2 writers against the reference workspace on each host. Report p50/p95/p99, the abandonment count and the per-phasetimings_ms, and paste the numbers here. - To force the deadline, lower
GORTEX_MCP_TOOL_TIMEOUT(read bytoolCallTimeoutFromEnv). Compare the goroutine count before and after the forced-abandon runs.
Dependencies
Blocked by (must land first):
- #1066 — Detect phase budgets build on the truthful detect envelope.
Merge after (same code region, keep this landing order): - #1068 — Edit-region landing order puts the remaining change-tool partials before detect phase budgets.
Blocks: - #1070 (needs this)
Related issues
- Addresses: #691 (close those when the targets below pass and the reporters confirm)
- Related: #692
- PRs: #857
Planning
- Milestone: v0.64.15
- Priority: latency (first priority)
- Estimate: ~4 engineer-days
- 主要言語
- Go
- スター
- 1.9k
- フォーク
- 171
- 平均マージ
- 21時間 42分
- マージ済み PR(30日)
- 148
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
zzet/gortex のほかの issue
-
documentation
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
zzet/gortex#865 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
メンテナーはふだん 1 日以内に返信
-
難易度 5/5 1週間以上 初心者へのやさしさ 20/100
メンテナーはふだん 1 日以内に返信
-
難易度 5/5 1週間以上 初心者へのやさしさ 15/100
メンテナーはふだん 1 日以内に返信
-
難易度 5/5 1週間以上 初心者へのやさしさ 25/100
メンテナーはふだん 1 日以内に返信
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
メンテナーはふだん 1 日以内に返信
-
schema-drift
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
stefanhoth/paperless-ngx-cli#61 ·
メンテナーはふだん 1 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 72/100
slavakurilyak/awesome-ai-agents#888 ·
メンテナーはふだん 1 日以内に返信
-
enhancement nav-pilot
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
メンテナーはふだん 1 日以内に返信
-
kind/enhancement
難易度 2/5 1〜3時間 初心者へのやさしさ 64/100
メンテナーはふだん 1 日以内に返信