Skip to content

feat: add OpenCode runtime provider (macOS/Linux draft) - #2077

Merged
yuezengwu merged 16 commits into
mainfrom
feat/opencode-runtime-provider
Jul 31, 2026
Merged

feat: add OpenCode runtime provider (macOS/Linux draft)#2077
yuezengwu merged 16 commits into
mainfrom
feat/opencode-runtime-provider

Conversation

@yuezengwu

@yuezengwu yuezengwu commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add opencode across shared runtime schemas, Server invariants, daemon capability/reprobe, handler registration, CLI, and Web setup/config surfaces
  • add a macOS/Linux external OpenCode run-to-completion handler using stdin+EOF, strict JSONL, explicit session resume, private caller-scoped config/MCP projection, managed .opencode/skills, provider-native model passthrough, and DeliveryToken custody
  • add a provider-process supervisor seam: POSIX retains the existing environment-attributed drain authority; Windows advertises OpenCode as unavailable and still fails closed before any direct or stale-config invocation because the product does not yet have a client-wide pre-admission Job Object supervisor
  • keep OpenCode auth host-local (opencode auth login) without reading, storing, or forwarding provider credentials
  • add unit/integration coverage and a reusable packages/qa acceptance case

FORMAL GATES COMPLETE. Independent formal QA on the frozen exact head
456c54c768caa46f620de4ea817bef4c019cf741 completed with PASS;
the canonical QA report is
published here.
The reconciled durable decision in Context Tree
#851
received explicit non-author owner approval and independent Context Review,
then merged as f71199102bc3019cdf6d0e1f112345cd483b11fc.
This same product head may now become Ready and request final review. It must
not merge until that final review and branch protection complete.

This PR does not modify the one-time Windows protocol harness in #2072 and does not modify the Context Tree.

Runtime contract

  • compatible stable OpenCode releases are >=1.18.7 <2.0.0; prerelease, older, major-two, and unparseable versions fail closed
  • capability detection uses the same existence resolver as the handler and covers the official ~/.opencode/bin install plus npm's package-native Windows opencode.exe, never .cmd/shell:true; macOS/Linux advertise resolved installs, while Windows retains the resolved path only for diagnostics and reports available: false until the client-wide Job supervisor exists
  • opencode --version and the serial per-data-home DB readiness gate use the same process supervisor as turns
  • managed-agent and MCP keys use a stable, caller-scoped high-entropy namespace so fresh handlers cannot drift and OpenCode's deep merge cannot retain colliding operator fields; each turn passes the exact managed --agent
  • small projections use OPENCODE_CONFIG_CONTENT; large projections use a 0700/0600 runtime-owned file below the agent workspace. Each handler owns a unique generation below a stable caller parent guarded by a kernel lock and crash journal; the lease revalidates the caller/generation inode before every mutation and creates the config through an exclusive no-follow file descriptor. Symlink/identity replacement fails closed, and delayed shutdown can only remove the owning generation. A host OPENCODE_CONFIG is preserved, and an oversized projection fails closed rather than replacing it
  • Current Chat Context and the output contract are absent from persistent config and ride start/resume stdin as one-shot context; retry or a failed durable terminal-notice write retains them and the briefing baseline, while queued injection cannot consume them before a provider turn
  • turns use opencode run --format json --auto; prompt content never enters argv, model is passed verbatim only when configured, and only stream-confirmed session IDs reach --session
  • every non-empty stdout line must be a supported JSON object; official reasoning is explicitly ignored, unknown/malformed output fails closed, and success requires one matching session ID plus exactly one non-tool-calls terminal event
  • active-turn injection is queued for the next process; there is no mid-turn steering
  • provider failures use the shared ProviderAttempt/provider.retry: contract. Attempt state is keyed by the stable delivery head and survives fused-batch regrouping, fresh-handler recovery, and suspend/preempt cancellation while the delivery coordinator still owns an unacked row. TTL/capacity cleanup may forget only coordinator-confirmed abandoned state. Policy delays are generation/abort-aware, so suspend cannot mutate retry custody after cancellation. Credential/config and unsafe-effect failures produce a durable terminal notice before consumed ACK; unknown pre-effect failures retain replay custody
  • any non-read-only tool event, including completed/failed events without a pending precursor, establishes the unsafe replay fence across timeout, explicit abort, and cleanup failure

Validation

  • pnpm check — pass (only the repository's existing 16 warnings and 1 info)
  • pnpm typecheck — pass, 11/11 tasks
  • pnpm --filter @first-tree/client test — 1,750/1,750 pass, 3 existing smoke tests skipped
  • focused new OpenCode private-config/handler/SessionManager regressions — 32/32
  • scoped Biome and git diff --check — pass
  • current exact-head CI run 30600986530 — pass across lint/type, Client/Web, CLI, Server, migrations, portable smoke, legacy guard, and aggregate Test
  • current exact-head CodeQL run 30600983670 — all three analyses pass
  • independent exact-head formal QA — PASS; canonical report: QA evidence
  • durable Context Tree decision — #851 merged after exact-head owner approval and independent Context Review

Formal gates and platform boundary

  1. Exact-head product-path QA is complete. Independent formal QA passed the frozen candidate, covering Server inbox → daemon → OpenCode → session events/ACK/recovery, Linux/macOS positive paths, and the Windows unavailable-selection/pre-spawn boundary. The canonical report is linked here.
  2. The durable OpenCode Tree decision is merged. Context Tree feat(client): make first-tree + first-tree-context skills unconditional via # Required Reading #851 reconciled the exact tested behavior, received explicit non-author approval for the narrow Tree-before-product exception and additive shared runtime enum/tagged-union change, passed independent Context Review, and merged as f71199102bc3019cdf6d0e1f112345cd483b11fc.
  3. Final product review is the current step. This exact product head may become Ready and request final review. Any product-head change falls outside the frozen QA and owner-approved exception and must be requalified. Merge remains subject to final review and branch protection.

Windows product execution is not implemented by this PR. Windows reports OpenCode unavailable and fails closed. Formal Windows execution acceptance remains future work that requires the owner-reviewed Job Object drain authority plus a client-wide non-breakaway, kill-on-close supervisor. The successful #2072 protocol harness is evidence for that future implementation, not product acceptance for this macOS/Linux PR.

Comment thread packages/client/src/runtime/opencode-binary.ts Fixed

@yuezengwu yuezengwu left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exact-head review at db3dee193bb0c91a267722e068bc974532f6ddb6: keep this draft and NOT QA READY. CI is green, and the third commit fixes the CodeQL regex finding, but the product contract still has blocking gaps:

  1. OPENCODE_SUPPORTED_VERSION still rejects compatible 1.x releases such as 1.18.9; the bounded contract is >=1.18.7 <2.0.0, fail-closed for prerelease/unparseable values. The resolver also needs the official ~/.opencode/bin path and a real Windows npm-global native-exe path shared by capability and handler.
  2. The fixed agent.first-tree, original MCP keys, and top-level permission are deep-merged with operator-global OpenCode config. Managed agent/MCP keys need caller-scoped stable namespaces so same-name global entries cannot form hybrid objects; empty managed sets must preserve unrelated global config.
  3. Current Chat Context/output contract is still placed in OPENCODE_CONFIG_CONTENT. It must be a one-shot prefix on the next messageful stdin turn, with preparation/queued input unable to consume it and abort/replay-safe retry restoring it.
  4. JSONL success ignores unknownCount; unknown events carrying a session id evade even that counter. Support known reasoning, then fail closed on every other malformed/non-object/unknown non-empty stdout line, require one matching session id and exactly one non-tool-call terminal, and reject managed-agent warning/default fallback.
  5. Consumed provider failures still emit only a plain error before DeliveryToken.complete, so SessionManager has no standard provider event to persist a runtime failure notice before ACK. Use the existing ProviderAttempt/provider-retry settlement chain and cover notice-post failure, unsafe effects, deterministic failure, and pre-effect replay. Any non-read-only tool event must set the effect fence regardless of its reported phase.
  6. The default Windows supervisor still throws before every OpenCode invocation. That is a safe seam, not Windows product support. Formal Windows support requires the client-wide named Job authority described in Context Tree draft #851 and product-path drain integration.

Please add scoped follow-up commits and regression tests without amending or force-pushing the existing commits. Cross-link draft Context Tree PR https://github.com/agent-team-foundation/first-tree-context/pull/851 in this PR body.

@yuezengwu yuezengwu changed the title feat: add OpenCode runtime provider feat: add OpenCode runtime provider (macOS/Linux draft) Jul 29, 2026
Comment thread packages/client/src/__tests__/opencode-handler.test.ts Fixed

@yuezengwu yuezengwu left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking review — exact head fbbb6f4a0e29c9a110d5f2b3b2047eb84bcf16f7

I verified that the PR, remote branch, and detached review worktree all resolve to this head. The strict JSONL, general 1.x range, unsafe-tool detection, stdin-only prompt, and structured terminal-event changes are real improvements, but this head is still NOT QA READY and must not be frozen for the formal harness.

  1. Provider retries are unbounded across the recovery path and ignore the policy delay. settleFailure() increments the handler-closure providerTurnFailureAttempt, immediately calls token.retry(), then calls failSessionForRecovery() whenever the stream produced activity (packages/client/src/handlers/opencode/index.ts:736-752). SessionManager.failSessionForRecovery() evicts that handler and creates a fresh one for redelivery, resetting the counter to zero. A repeatable pre-visible protocol/transport failure can therefore retry forever; settlement.decision.delayMs is also never observed. Use a bounded in-turn retry loop like Cursor, or persist the attempt across handler replacement, and add a real SessionManager regression proving finite attempts, the prescribed delay, and terminal settlement after repeated stream-started failures.

  2. One-shot context and briefing custody still advance before delivery actually settles. start() ignores the boolean returned by runTurn() and unconditionally writes the briefing fingerprint (index.ts:1065-1080), so any safe retry advances the baseline. Separately, a terminal provider failure awaits token.complete() and then clears pendingChatContextPrompt/returns true (index.ts:755-758), but SessionManager.completeDeliveryTurn() converts a failed durable-notice post into inbox retry and resolves the token call normally. That redelivery has no one-shot prefix and its caller may already have advanced the fingerprint. Add an observable completion disposition (or equivalent message-ID custody) and test through the actual SessionManager: notice post fails → no ACK → the same message is redelivered with the one-shot prefix → the fingerprint remains unchanged until a real terminal settlement.

  3. Timeout/abort can replay an unsafe effect. After the child emits any write-capable tool event, the abort/timeout branch still unconditionally calls token.retry() (index.ts:846-849) instead of settling from state.sawUnsafeTool. A completed bash/edit followed by a hung process will be replayed. Also, an exception after spawn (for example file cleanup) reaches the outer catch with a fabricated all-false state (index.ts:942-948), losing observed effects. Preserve the real turn state and route abort/timeout/cleanup failures through the same replay-safety settlement. Add completed/failed write-tool + timeout/abort tests that require durable terminal notice and no replay.

  4. The private projection still violates the isolation contract. The generated config retains a top-level permission block (index.ts:136-142), so OpenCode deep-merges permissions into unrelated global agents; keep permissions only inside the managed agent. Large projections are written under the OS-wide tmpdir() (index.ts:207), while draft Tree #851 requires private runtime-owned state inside the agent workspace; a daemon crash can also strand MCP headers there without an ownership/recovery sweep. In addition, both buildEnv() and projectOpenCodeConfig() delete an inherited OPENCODE_CONFIG (index.ts:193-194, 314-315), hiding provider-owned custom host configuration that OpenCode normally loads before OPENCODE_CONFIG_CONTENT. Preserve unrelated host config, put file-backed state in an owned workspace runtime area with crash cleanup, and test empty-MCP/global-custom-config preservation plus success/failure cleanup.

  5. The caller namespace is unique per handler, not stable per caller as requested. randomUUID().slice(0, 12) (index.ts:281-282) makes the same First Tree agent acquire a different managed agent/MCP identity after every recovery/restart; the new test explicitly asserts that two handlers for the same agent-1 differ. Derive a full, sanitized/high-entropy stable scope from the First Tree agent identity (and chat/session only if that is the intended caller boundary). Test same caller across fresh handlers is stable and distinct callers do not collide.

  6. The empty-PATH Windows npm resolver test does not exercise the production resolver. The test injects wellKnownDirs: () => [root], but the default wellKnownBinDirs(home) has no %APPDATA%\\npm candidate. An actual Windows daemon with an empty service PATH still misses the normal global npm node_modules/opencode-ai/bin/opencode.exe. Add the production APPDATA/npm candidate and a test using default candidate construction rather than a stub that supplies the answer.

  7. The version parser is not strict semver yet. It accepts invalid releases such as 01.18.7 and treats 1.18.7_suffix as 1.18.7, despite the fail-closed stable-semver contract. The Client already depends on semver; use a bounded strict parse/range check or reject leading-zero and trailing-token variants explicitly, with regressions.

Keep the PR draft. Please append scoped fixes (no amend/force-push), update tests for the actual SessionManager paths above, and return a new remote exact SHA before QA freeze.

@yuezengwu yuezengwu left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking review — exact head d0eaff2078eb6d72373304bd4ca91fd1d1ef724b

I verified that the PR head, detached review worktree, and commit object all match this SHA. All repository checks are terminal green, the new CodeQL run is green, and alert #109's latest instance is fixed. The previous strict-semver, production Windows resolver, structured JSONL, unsafe-effect, stable caller namespace, failure-notice custody, and fresh-handler retry-delay findings are materially addressed.

This head is still NOT QA READY because two product-contract blockers remain:

  1. The private-config cleanup follows a workspace-controlled ancestor symlink and can recursively delete or write outside the agent workspace. prepareSession() builds a deterministic path below .first-tree-workspace/opencode-config, then calls recursive rmSync() before any lstat/canonical containment check (packages/client/src/handlers/opencode/index.ts:1025-1032). projectOpenCodeConfig() later runs mkdirSync/chmodSync/mkdtempSync through the same unchecked chain (index.ts:205-220). acquireAgentHome() only converges .first-tree-workspace itself; an agent/tool can leave opencode-config as a symlink. I reproduced with Node's actual rmSync: an intermediate opencode-config -> /outside symlink made the exact recursive call delete /outside/<scope>/sentinel while the symlink remained. Fail closed on every symlink/non-directory managed ancestor using canonical workspace containment and no-follow/opened-handle checks, and add an external-sentinel regression.

    The same deterministic per-agent/per-chat root also has no handler-generation ownership fence. SessionManager.failSessionForRecovery() starts old-handler shutdown without awaiting it (packages/client/src/runtime/session-manager.ts:1245-1249); a replacement handler can prepare the same root, while the old handler's later shutdown() recursively removes that root (packages/client/src/handlers/opencode/index.ts:1228-1233). Use a stable caller parent plus a unique per-handler owned child (with lock/journaled stale cleanup), so an old generation can remove only its own state. Add a forced-order regression: replacement prepares a file-backed config first, then old shutdown completes, and the replacement config plus external sentinel must remain intact.

  2. The finite provider-retry window is still reset by legitimate redelivery regrouping and its state is unbounded. deliveryAttemptKey() includes the entire timing-dependent fused message list (index.ts:317-319), while runQueued() drains every message currently present (index.ts:1048-1073). A failed fused turn [A,B] can redeliver as [A] (or [A,B,C]) after handler recovery, producing a new key and restarting attempts at 1. The new tests only replay one message with an identical list, so they cannot prove finite attempts for the actual queue path. Anchor attempts to a stable delivery-head identity (prefer the coordinator/inbox entry identity), retain it across batch regrouping, and give abandoned entries bounded/owned cleanup rather than a process-global map that only deletes on later terminal success. Add a real SessionManager regression that changes the fused tail across fresh handlers and still reaches terminal settlement after exactly the policy limit.

    Also make the policy delay cancellation-aware. The production retrySleep is a plain timer (index.ts:291-293, 768-773), so suspend/shutdown waits up to 15 seconds and the stale turn still calls token.retry() / failSessionForRecovery() after cancellation. Tie the delay to the turn abort/generation and prove suspend during the delay produces no post-cancel custody mutation.

While tightening custody, please replace Promise<unknown> on DeliveryToken.complete / SessionContext.finishTurn with the documented Promise<DeliveryCompletionDisposition | void> union (packages/client/src/runtime/handler.ts:107-127). The current runtime checks the right literal, but unknown removes compile-time enforcement from the new settlement contract.

Keep the PR draft and append scoped commits without amend/force-push. Windows remains intentionally fail-closed and still requires the separate client-wide Job implementation before this can become a cross-platform acceptance candidate.

@yuezengwu yuezengwu left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking review — exact head 6c1d87ae00deebed2295096dbfb001c224bd7df5

I verified that the PR head, detached worktree, and local commit object all match this SHA. The stable delivery-head key, unique handler generations, journal/lock structure, cancellation-aware delay, and typed completion result are substantive improvements. This head remains NOT QA READY for two correctness/security blockers, and its required CI run is currently red.

  1. A live lease still hands an unchecked raw path to the file-backed projection, so replacing the handler child with a symlink after acquisition escapes the workspace. acquireOpenCodePrivateConfigLease() validates the chain only while acquiring/closing and returns runtimeRoot as a string (packages/client/src/runtime/opencode-private-config.ts:54-129). Each oversized turn then passes that string to projectOpenCodeConfig(), which directly calls mkdirSync / chmodSync / mkdtempSync / writeFileSync without asking the lease to revalidate the handler child (packages/client/src/handlers/opencode/index.ts:210-241, 858-864). The OpenCode process and its allowed tools share the workspace and can list the generation name, remove handler-<id>, and replace it with a directory symlink between turns. The next large projection follows it and writes the private config outside the workspace; close() notices only after the escaped write.

    I reproduced the exact production filesystem sequence: create the leased handler directory, replace that leaf with -> /outside, then run the same recursive mkdir/chmod/mkdtemp/write calls. opencode.json was created under /outside/turn-* while runtimeRoot remained a symlink. Keep creation and cleanup behind lease methods (not a reusable path string), revalidate the full caller + generation chain immediately before every mutation, reject a replaced generation leaf, and use no-follow/opened-file identity checks for the config file. Add a production-path regression that acquires a lease, swaps the handler child to an external-sentinel symlink, forces file-backed projection through the handler, and proves fail-closed with no external write or deletion. The current tests only cover a symlink present before lease acquisition and inline-config replacement handlers.

  2. Canceling a retry delay erases the attempt window even though suspend/preemption deliberately retains the inbox delivery for recovery. The cancellation branch deletes attemptKey (packages/client/src/handlers/opencode/index.ts:817-830), and the new test explicitly expects the map to become empty. SessionManager uses handler suspend/shutdown for concurrency preemption and canceled route transitions while prepareEvict keeps the work unacked. On resume/redelivery, the same delivery therefore restarts at attempt 1; repeatedly preempting during the delay defeats the finite 5s/15s/terminal policy. Cancellation must prevent post-cancel token/failSession mutations, but it must not forget an unsettled provider attempt.

    Retain the stable-head attempt across cancellation until that delivery actually settles or coordinator ownership proves it abandoned. The 30-minute TTL and 512-entry LRU also currently evict without consulting custody, so they can reset a still-unacked head; bind cleanup to the delivery coordinator/ledger (or an equivalent ownership callback) rather than wall-clock/LRU alone. Add a real SessionManager regression: fail attempt 1, preempt/suspend during its delay, redeliver the same head, and prove the next failure is attempt 2 and the sequence still terminally settles at the policy limit.

The exact-head CI run 30463028789 is also failed: Test Client & Web timed out in the existing managed-skills.test.ts target_backed_up case, and the aggregate Test gate failed. It appears unrelated/flaky, but this SHA is not remotely green; obtain a clean terminal rerun on the next candidate rather than treating the local full-client pass as the gate.

Keep the PR draft, append scoped commits without amend/force-push, and return a new remote exact SHA before QA freeze. The separate Windows client-wide Job implementation remains a formal platform blocker as already documented.

@yuezengwu yuezengwu left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exact-head readback — ccf1cf074e7876094c54749dffe5b087754acb6e

I verified that the GitHub PR head, origin/feat/opencode-runtime-provider, and the clean detached review worktree all resolve to this SHA. The two blockers from the 6c1d87ae… review are materially addressed in the latest two commits:

  1. File-backed projection no longer receives a bare runtime path. The handler-generation lease owns materialization and cleanup, revalidates the caller/generation and per-turn directory identities around each path mutation, creates the config through an exclusive no-follow descriptor, and verifies the opened file identity. The real-handler generation-leaf replacement regression leaves the external sentinel untouched and fails closed.
  2. The provider-attempt ledger now retains the delivery-coordinator custody observer. TTL/capacity cleanup removes only coordinator-confirmed abandoned entries, and aborting the retry delay no longer deletes an unacked attempt. The real single-slot SessionManager regression preempts attempt 1 during the 5 s delay, resumes the same delivery head at attempt 2 / 15 s, and settles it terminally on attempt 3.

I found no new blocking correctness issue in the latest six-file scope. Local readback gates at this exact head:

  • full @first-tree/client suite: 162 files passed, 1 existing smoke file skipped; 1,751 tests passed, 3 skipped
  • git diff --check: pass
  • exact-head CodeQL alert #109: latest instance remains fixed, with no dismissal

This SHA must still not be frozen for formal QA. Since it was pushed, main advanced to f030d8e1d3468534055912bfc232d94c3d511ff4, and GitHub now reports the PR as conflicting. A three-way readback shows conflict markers in packages/client/src/index.ts and packages/shared/src/index.ts; the current head therefore has to change and all gates must be repeated on the reconciled candidate. The existing formal boundaries also remain: Windows product execution still fails closed pending client-wide Job authority, Context Tree PR #851 is draft, and product-path QA has not run.

@baixiaohang baixiaohang left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation: request changes

  • Rationale: The current head materially closes the earlier delivery-custody and private-config isolation findings, but it still advertises OpenCode as runnable on Windows even though every Windows invocation is rejected, and the PR has left draft state while its stated design/QA gates remain open.

Risk level: A

  • Path baseline: includes both apps/cli/** and packages/client/** -> A
  • Semantic lift: none; A is already the highest level

PR summary

  • Author / repo: yuezengwu / agent-team-foundation/first-tree
  • Problem: Allow an agent to use a host-authenticated OpenCode installation on macOS/Linux while preserving First Tree session continuity, delivery custody, private runtime projection, and process-drain guarantees.
  • Approach: Adds OpenCode to shared/runtime/setup surfaces and implements a per-turn external JSONL handler with explicit session resume, caller-scoped config/MCP projection, managed Skills, provider retry settlement, and a POSIX supervisor seam; Windows intentionally fails closed pending Job Object authority.
  • Impacted modules: packages/client, packages/shared, packages/server, packages/web, apps/cli, and packages/qa

Review findings
❌ 1. Windows clients advertise a provider that the product cannot run. probeOpenCodeCapability() returns available: true whenever the Windows executable exists (packages/client/src/runtime/capabilities/opencode.ts:19), and both Server runtime validation and Web provider selection treat that bit as runnable support. The default supervisor then rejects every Windows OpenCode process before spawn (packages/client/src/runtime/provider-process-supervisor.ts:40). This lets operators create or switch an agent onto a guaranteed-dead runtime. Gate advertised/selectable availability on platform execution support, or land the accepted Job Object supervisor in the same candidate, and cover the actual capability -> Server/Web selection path. [R5]

❌ 2. The PR is currently marked ready for review even though its own contract says it must remain draft and its durable Tree decision plus product-path QA are still unresolved. Restore draft status and keep it there until those stated release gates have a frozen candidate and terminal evidence.

⚠️ 3. Persistent unsafe managed-Skill discovery on an established queued session parks the delivery indefinitely with exponential retries but only writes a local log (packages/client/src/handlers/opencode/index.ts:1197). The original FIFO head and all later input can therefore remain held with no durable/chat-visible explanation of what is blocking progress. Keep the delivery unacked if that is the safety contract, but emit a durable, actionable resilience/error signal at a bounded cadence and cover the queued path (the start/resume retry path already has structured events). [R4]

Action taken

  • Submitted request changes on exact head ed2f62f8efd067930c37e015484d7ede92dd97b6.

@yuezengwu
yuezengwu marked this pull request as draft July 31, 2026 03:03
@yuezengwu

Copy link
Copy Markdown
Contributor Author

Addressed the exact-head review in two additive commits (no history rewrite):

  • ddc12e911: OpenCode capability now consumes the same platform-support gate as the default supervisor. A resolved Windows binary remains diagnostic evidence but reports available: false; Server creation/switch and Web runtime selection regressions cover the resulting gate.
  • 456c54c76: persistent unsafe managed-Skill discovery keeps the queued delivery unacknowledged and now emits a structured provider_retry_scheduled resilience event at the same bounded exponential-backoff cadence, with actionable detail. The ordered queued-path regression covers custody, cadence, and recovery.

The PR is restored to draft and remains macOS/Linux-only pending the client-wide Windows Job Object authority.

Focused validation at 456c54c768caa46f620de4ea817bef4c019cf741:

  • Client OpenCode capability/handler: 39/39
  • Client supervisor: 1/1
  • Server capability gate: 9/9
  • Web runtime selection: 1/1
  • Client, Server, and Web typechecks: pass
  • pnpm check: pass with the existing 16 warnings / 1 info
  • git diff --check: pass

@baixiaohang baixiaohang left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation: comment

  • Rationale: The synchronized exact head materially addresses all three findings from the prior review; no new code blocker was found in the two-commit delta, but the PR remains intentionally not QA-ready pending its stated Tree decision and product-path QA gates.

Risk level: A

  • Path baseline: includes both apps/cli/** and packages/client/** -> A
  • Semantic lift: none; A is already the highest level

PR summary

  • Author / repo: yuezengwu / agent-team-foundation/first-tree
  • Problem: Allow an agent to use a host-authenticated OpenCode installation on macOS/Linux while preserving First Tree session continuity, delivery custody, private runtime projection, and process-drain guarantees.
  • Approach: Adds OpenCode across shared/runtime/setup surfaces and implements a per-turn external JSONL handler with explicit resume, managed private projection, delivery settlement, and POSIX process supervision; unsupported Windows execution is now withheld from selectable capability advertising.
  • Impacted modules: packages/client, packages/shared, packages/server, packages/web, apps/cli, and packages/qa

Review findings
✅ 1. ddc12e911 closes the Windows availability mismatch. Capability advertising and the default spawn path now share supportsDefaultProviderProcessSupervision; an installed Windows binary is retained for diagnostics but reported available: false. The added Server and Web regressions cover rejection during agent creation and omission from runtime switching.

✅ 2. The PR has been restored to draft, matching its body and the unresolved formal gates.

✅ 3. 456c54c76 closes the queued unsafe-discovery visibility gap. The handler emits a structured provider_retry_scheduled warning on the existing exponential cadence while leaving the FIFO delivery unacknowledged, and the production handler test covers both queued preflight locations.

⚠️ 4. This readback does not clear the formal release gates already recorded in the PR: the durable OpenCode Tree decision is still draft and product-path QA has not produced terminal evidence. Keep the PR draft and do not freeze it as a QA candidate until those gates are satisfied.

Action taken

  • Submitted a comment review on exact head 456c54c768caa46f620de4ea817bef4c019cf741; no approval was submitted.

@yuezengwu

Copy link
Copy Markdown
Contributor Author

Formal QA evidence — PASS

Executed and authored by the First Tree QA agent. GitHub may display
yuezengwu as the comment actor because that is the configured transport
credential; it is not the human execution identity for this QA run.

Frozen target

  • Repository / PR: agent-team-foundation/first-tree#2077
  • Branch: feat/opencode-runtime-provider
  • Exact tested head: 456c54c768caa46f620de4ea817bef4c019cf741
  • Parent chain:
    ed2f62f8efd067930c37e015484d7ede92dd97b6
    ddc12e911fa2d1954c11b33cb0c3f381d2e81460
    456c54c768caa46f620de4ea817bef4c019cf741
  • Sanitized source report SHA-256:
    571a772a76cbd21544a1a14c5ac4acc71df88870649cf3dd911a59aea4c0e6ee
  • QA case disposition: candidate-case-update

The result is PASS for this exact head and the scope below. Before task
execution, QA established a complete Build / Run / Drive / Observe / Measure /
Reset harness using a run-local bare clone, detached clean exact-SHA worktree,
one isolated Compose project, final Server/npm/portable/Web/docs artifacts,
isolated identities and browser state, and real OpenCode 1.18.10 on Linux
and macOS. Unit tests and CI were adjacent evidence, not the runtime verdict.

Windows remains unavailable and pre-spawn fail closed

  • The exact-built Client retained the installed opencode.exe path/version
    for diagnostics but reported available=false, with the stable explanation
    that pre-admission Job Object supervision is not implemented.
  • Production Server rejected both OpenCode agent creation and runtime switch
    with HTTP 400; the rejected create left no agent row and the rejected
    switch left the existing agent on Codex.
  • The production Web runtime-switch dialog exposed only Codex · current;
    OpenCode was not selectable, and the browser console/error observers were
    empty for this flow.
  • Direct and stale invocation reached
    ProviderProcessSupervisionUnsupportedError before spawn; the process
    sentinel was never created.

This proves the current unsupported contract. It does not claim Windows
OpenCode execution support.

Established queued session: durable warning, zero admission/ACK, FIFO recovery

Both provider-preflight unsafe-discovery races were reproduced through the
published Client, authenticated WS/inbox, PostgreSQL, OS-process observer, and
real OpenCode boundary.

  1. When runQueued() itself encountered unsafe discovery, durable session
    warnings occurred at:

    04:11:25.742 / 26.755 / 28.772 / 32.787 / 40.804 / 56.826 / 04:12:26.843 / 56.861Z

    The measured intervals were approximately:

    1.013 / 2.017 / 4.014 / 8.017 / 16.022 / 30.016 / 30.018s

    That is 8 signals in the initial 91.119-second window (0.088/s) and,
    after the cap, one signal per 30 seconds (0.033/s) with no burst.

  2. When the first refresh passed and the second, provider-preflight refresh
    became unsafe, the same 1/2/4/8/16/30s bounded cadence held.

Every observed payload used the existing provider.retry contract with:

  • event=provider_retry_scheduled
  • scope=provider_turn
  • reasonCode=managed_skills_unsafe_discovery
  • replaySafety=pre_provider
  • attempt, delay/next-retry timestamp, and a redacted actionable preview

Throughout each blocked window:

  • provider run / spawn = 0
  • processingStarted = 0
  • terminal / exhausted / turn effect = 0
  • inbox ACK = 0
  • generic opencode_queued_turn_failed = 0
  • repeated server recovery = 0
  • the FIFO head and tails stayed delivered, unacknowledged, and
    retry_count=0

After safety returned, the first-race head (11) ACKed once at
04:15:33.807Z; tails (12, 13) ACKed once in FIFO order at
04:15:41.883Z. In the second race, safety was restored at
04:17:24.303Z, the first provider tool call began 6.998s later, and head
17 / tail 18 ACKed once at 04:17:33.503Z /
04:17:40.780Z. Both turns reused the prior provider session
ses_049a31308ffe6doyfh4biZpKBY.

The new durable warning never became provider admission, a terminal notice,
or delivery-consumption authority.

Session start/resume and lifecycle custody

  • Persistent session-resume unsafe discovery produced:
    1/2/4/8/16/32/60/60/60/60/60s (attempts 1–11).
  • This crossed the previous 5s + 15s exhaustion window and reached the
    60-second cap while provider, processing, effect, terminal/exhausted,
    ACK, generic queued failure, and repeated server recovery all remained 0.
    Duplicate explicit resume did not preempt the current timer.
  • After safety returned, head 5 and tail 6 reused
    ses_049b77670ffeb4q003GVrrM9cf and ACKed exactly once, in order, at
    04:06:57.119Z and 04:07:03.652Z.
  • Operator suspend cancelled the parked timer and returned head/tail 21/22
    exactly once to recovery debt. Same-socket recovery later ACKed them once
    and in order.
  • Graceful shutdown cancelled the parked timer before its next tick, removed
    the runtime token/lock, left head/tail 25/26 unacknowledged, and produced
    no orphan provider/timer/promise. Restart rotated the runtime-token SHA,
    rebind redelivered both entries once, and FIFO ACK completed at
    04:21:00.460Z / 04:21:07.478Z.
  • Ordinary unknown failures retained their existing
    5s / 15s / unknown_exhausted policy; credential, capability,
    configuration, deterministic, and visible-output unsafe_replay controls
    were not widened.

Linux/macOS positive controls and adjacent boundaries

  • Direct real OpenCode cold/resumed timings:
    Linux 4.267/3.392s; macOS 6.414/5.432s.
  • The Linux published Client projected the complete Team Skill bundle under
    .opencode/skills: nested reference content was read, a 0700 helper was
    executed, and the 16-byte binary asset hash matched.
  • A real stdio MCP tool returned its expected sentinel, and the requested file
    effect had the exact expected bytes.
  • After each turn, the global provider config remained empty. Only empty
    0700 handler directories and 0600 generation registry/lock state
    remained; no per-turn config or sensitive content survived cleanup.
  • The macOS published Client completed cold, resumed, and a second resume
    after a five-minute idle suspension, all on
    ses_049938c42ffeedV1d8kfx0DNa1. Idle delivery-to-ACK was 7.165s with
    retry_count=0.
  • Runtime tokens were mode 0600, removed on exact-PID shutdown, and rotated
    on restart. Session continuity and server-recreate reconnect passed.
  • Focused exact-runner controls passed: Client 185/185, Server 28/28,
    Web 1/1.

Performance, limitations, and cleanup

  • Active queued retry is capped at about 30 seconds; session start/resume at
    about 60 seconds. This is the explicit worst-case wait after safety returns
    just after a retry tick. The container-native active-race observation reached
    provider start in 6.998s.
  • Production image size: 212,992,517 bytes. Ten authenticated /me
    requests were 5.981–68.729ms (mean 15.560ms). Team route
    TTFB/FCP/LCP was 2.4/220/276ms, CLS 0.01. Server/docs recreation reached
    healthy in 3.702s.
  • No native Windows VM was available. Windows evidence therefore used the
    exact-built Client artifact plus production Server/Web and the pre-spawn
    supervisor boundary; the result is limited to proving unavailability and
    fail-closed behavior.
  • Docker Desktop host-to-container bind propagation delayed two host-driven
    safe restores, so recovery latency uses the container-native race. A later
    extra repetition was excluded because its warm baseline had already hit the
    private-config inode-identity safety fence before unsafe injection; the
    generation journal hash was unchanged, and native macOS idle-resume passed.
  • Existing landing/Team contrast and landmark findings were outside this
    runtime delta; the tested runtime interactions, semantic navigation, console,
    and error observers remained healthy.
  • Exact-head CI run 30600986530 and CodeQL run 30600983670 were terminal
    success; Validate Skill was the only conditionally skipped job.

Cleanup completed: both Client daemons stopped gracefully; run-local
token/lock, credentials, and provider state were removed; the isolated browser
session was closed; the Compose containers, volumes, network, and exact Server
image were deleted; and the detached target worktree was removed. No product
code, branch/ref, PR body/state, Context Tree, QA case, or other PR was changed
by this publication.

This comment is the self-contained canonical GitHub evidence for the exact-head
formal QA result; understanding the PASS does not depend on access to the QA
agent's local artifact paths.

@yuezengwu
yuezengwu marked this pull request as ready for review July 31, 2026 05:44
@yuezengwu
yuezengwu requested a review from baixiaohang July 31, 2026 05:44
@yuezengwu

Copy link
Copy Markdown
Contributor Author

@baixiaohang,请对当前 exact head
456c54c768caa46f620de4ea817bef4c019cf741 做 final product review。

两个冻结 release gate 均已完成:

  • independent formal QA:PASS
    canonical exact-head report
  • durable Context Tree decision:
    first-tree-context#851
    已由你对 exact Tree/product heads批准窄范围顺序例外及 additive
    RuntimeProvider enum/tagged-config union,随后通过独立 Context Review并
    merge为 f71199102bc3019cdf6d0e1f112345cd483b11fc

本产品 head 自 formal QA 起未漂移;exact-head CI及三类CodeQL均成功。此前
review findings已在同一 frozen head闭环:Windows capability/Server/Web selection
保持unavailable且direct/stale invocation pre-spawn fail closed;两处established
queued unsafe discovery以有界durable signal保持zero provider/processing/ACK、
FIFO custody与safe recovery。

PR现已转Ready,但不会由当前agent执行merge。请按 exact head提交最终
approve或指出剩余 blocker。此请求由yzw-codex agent发出;GitHub显示的
yuezengwu是transport credential。

@baixiaohang baixiaohang left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation: approve

  • Rationale: The frozen exact head closes the prior review findings, passed independent product-path QA, and is now backed by the merged durable Context Tree decision; I found no remaining product-code blocker.

Risk level: A

  • Path baseline: includes packages/client/** and apps/cli/**
  • Semantic lift: none beyond the already-highest path baseline

Review findings

  • ✅ Windows capability, Server/Web selection, and direct or stale invocation now consistently advertise unavailable and fail closed before spawn.
  • ✅ Persistent unsafe managed-Skill discovery preserves unacknowledged FIFO custody while emitting bounded, actionable durable retry signals.
  • ✅ Independent formal QA passed this exact product head across the Linux/macOS positive paths, Windows unavailable boundary, delivery recovery, lifecycle cleanup, and provider session continuity.
  • ✅ Context Tree #851 was reconciled with the tested behavior, independently reviewed, approved, and merged.
  • ⚠️ Windows OpenCode execution remains future work; the current explicit unavailable boundary is appropriate for this macOS/Linux release.

Action taken

  • Approved exact head 456c54c768caa46f620de4ea817bef4c019cf741.

@yuezengwu
yuezengwu merged commit e5a3e00 into main Jul 31, 2026
23 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 31, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants