Dashboard › opencode › Distillation
6c24dc4d-a276-49e0-92e4-288436a30b7f["lore_tm_v1_guDxnUyqBEl4hQP1M10ekdqFSpSKxSYXfOPoxyp-DB0","lore_tm_v1_DROGEbeI4r5TxCa6FYyOcld7sXB_-bg93vuXoQB3fFw","lore_tm_v1_yXSb5ooeVx9t2A8cO3_PDh8gjFIke3dCKRusMBor6sU","lore_tm_v1_sb_u4Vwljgygn05a5Vn3TN8q3mrSynxhyWLsqDj3Ogw","lore_tm_v1_lgBZe8QREgb7Q_1FkQ3ZL1DS3etF9TprQCG9GP6vrZ0","lore_tm_v1_imfBxDH3ukudn3dcR03UbdQU61QHpyDDlfHHsaUDVno","lore_tm_v1_6R2l2WOD_NG6gAw7N5b50uuYJI2eXvw6bKe3w21pT98","lore_tm_v1_n7xhNyTAtMBw9sUc_hUNVx1d2HOl12DynuGIbKbAbzo","lore_tm_v1_svtotNJC0KJmN-WL_SkpshVCpRoR6Rkgk2usqeNBqF4","lore_tm_v1_cLnBLd8_Rx5WDxNawlUqBoEeoRjWjROMaWxGGdjp4RY","lore_tm_v1_oENs1H_4u7916YXX2ACo_GOzcdGkeQW8MlLuuqL09i8","lore_tm_v1_2wd3p9iD7GoQheBDTP5j9RxovMA2q1IBjmgM5EarbIQ","lore_tm_v1_9fNWpPplqS0WqxfOUbzS7bzdKw-2BMsSnC_36NeBcTw","lore_tm_v1_pPfH0ulx5UjKWwf58m90VgTf6Fb_r8TKWcOJ9M7luUI"]
🔴 (19:13) Repository workspace is /home/byk/Code/opencode-v2-pilot; root listing contained 64 entries, including AGENTS.md, packages/, services/, sdks/, specs/, package.json, bun.lock, tsconfig.json, and turbo.json.
🔴 (19:14) User-provided repository policy: after changing the public Protocol or Server HttpApi, run bun run generate from packages/client; never edit generated client files directly.
🔴 (19:14) User-provided dependency policy: keep runtime dependencies directed from Schema to Core and Protocol, then from Core and Protocol to Server. Client runtime code may depend on Schema and Protocol but never Core or Server; sdk composes Client, Core, and Server.
🔴 (19:14) User stated current implementation changes belong in packages/core, packages/cli, packages/server, packages/protocol, packages/schema, and related generated client surfaces when required.
🔴 (19:14) User stated the repository does not use Changesets; do not add .changeset files and follow the existing release workflow.
🔴 (19:14) User stated the default branch is v2; new branches/worktrees should default to v2, or origin/v2 if local v2 is unavailable; pull requests should target v2 unless explicitly instructed otherwise. Local main may not exist, so use v2 or origin/v2 for diffs.
🔴 (19:14) User-provided live TUI workflow: run bun run dev:live from a development worktree against the elected opencode2 server; optionally pass a project path, e.g. bun run dev:live /path/to/project. The script discovers the server via opencode2 service status, obtains its private local credential via opencode2 service get password, and uses the dev TUI storage channel. Prefer dev:live over bun run dev; an implicit managed-service connection may replace a version-mismatched live server, while explicit --server warns and continues.
🔴 (19:14) User-provided TUI story workflow: create fixture-driven stories under packages/tui/src/feature-plugins/system/storybook, register them in index.tsx, render the real production component, keep side effects local, expose meaningful state dimensions through keybindings listed in StoryFooter, and provide reset when combinations can confuse fixture state. Run with OPENCODE_STORY=<story-id> bun run dev:live and test narrow/wide terminal sizes when layout matters.
🔴 (19:14) User-provided theme policy: choose tokens by semantic role, never by current color; do not use raw theme.hue values or repurpose unrelated semantic tokens. text.feedback and background.feedback are only for outcome/status feedback; use formfield states for form-control text, ordinals, and selection markers, and action states for actions. If a semantic role is missing, extend theme schema, defaults, resolution, and types. Public theme-token changes must verify built-in light/dark defaults and the custom-theme fallback path.
🔴 (19:14) User stated branch names must contain at most three hyphen-separated words, with no slashes or type prefixes such as feat/ or fix/; examples: session-recovery, fix-scroll-state, regenerate-sdk.
🔴 (19:14) User stated commits and PR titles use type(scope): summary; valid types are feat, fix, docs, chore, refactor, and test, with optional area/package scopes such as core, opencode, tui, app, desktop, sdk, or plugin.
🔴 (19:14) User-provided coding style: keep code in one function unless composable/reusable; validate unknown values once at the owning boundary; do not defensively revalidate schema-, constructor-, or internal-type-guaranteed values; do not preemptively extract single-use helpers; explain concrete failure mode, likelihood, and complexity cost and obtain buy-in before adding speculative race/security complexity.
🔴 (19:14) User-provided coding style: avoid try/catch and any; prefer Bun APIs such as Bun.file(); rely on type inference; prefer flatMap, filter, and map over loops and use filter type guards; bind Effect services to named variables before method calls rather than nested service yields.
🔴 (19:14) User stated src/config additions must follow the existing self-export pattern, e.g. export * as ConfigAgent from "./agent".
🔴 (19:14) User stated to reduce variable count by inlining one-use values, avoid unnecessary destructuring in favor of dot notation, prefer const over let, use ternaries or early returns instead of reassignment, and avoid else statements.
🔴 (19:14) User stated complex functions should read as a happy path with genuinely meaningful supporting helpers kept nearby/below the main export; do not over-abstract simple expressions, do not return Effect from synchronous helpers, prefer Schema.UnknownFromJsonString and Schema.decodeUnknownOption over manual JSON.parse in Effect.try, and comment only non-obvious constraints or surprising behavior.
🔴 (19:14) User stated Drizzle schema fields must use snake_case so column names do not need string remapping, e.g. project_id: text().notNull() rather than projectID: text("project_id").notNull().
🔴 (19:14) User stated tests should avoid mocks and globalThis.* unless unavoidable, test actual implementation rather than duplicating logic, and never run from repository root because of do-not-run-tests-from-root; run from package directories such as packages/core.
🔴 (19:14) User stated always run bun typecheck from package directories (for example, packages/core), never tsc directly.
🔴 (19:14) User stated never alias imports; prohibited examples include import { foo as bar } from "..." and resolve as pathResolve.
🔴 (19:14) User stated never use type-position import("...") references, e.g. Schema.declare<import("@opencode/plugin/effect/plugin").Plugin["effect"]>. Only a genuine name collision with no other option permits an aliased type import such as import type { Plugin as PluginDefinition } from "...", and even then it is strongly discouraged.
🔴 (19:14) User stated never use star imports, including import * as Foo from "..." and import type * as Foo from "...".
🔴 (19:14) User stated namespace-style values must use the module’s named exported namespace, e.g. import { Project } from "@opencode/core/project" followed by Project.ID.
🔴 (19:14) User stated heavy modules needed only on selected paths should use dynamic imports in the narrowest branch/scope; destructure bindings near the top of that scope and avoid inline chains such as await import("./module").then((mod) => mod.value()) or (await import("./module")).value().
🔴 (19:14) User-provided V2 Session Core policy: durable events contain only irreducible new facts, while projections/read models may enrich prior or derived state.
🔴 (19:14) User-provided V2 prompt-admission architecture: Session.prompt(...) publishes session.inbox.enqueued; its projection inserts one durable session_inbox row before advisory SessionExecution.wake(sessionID), unless resume: false requests admit-only behavior. session.inbox.delivered consumes the inbox row and inserts the visible message transactionally; session_inbox contains only unconsumed work.
🔴 (19:14) User-provided V2 idempotency policy: reusing a Session ID adopts the existing Session. Reusing user/synthetic inbox item IDs is idempotent when Session and type match; first admission wins, and retried payload, metadata, and delivery mode are ignored whether pending or delivered. Cross-Session or cross-type reuse fails; control items retain operation-specific conflict behavior.
🔴 (19:14) User-provided V2 execution architecture: SessionExecution remains process-global and Session-ID based; its local implementation owns the process-local coordinator and resolves placement through SessionStore plus LocationServiceMap.get(session.location) only when draining. No layer takes a Session ID. Interrupting a known idle or locally unowned Session is a no-op, while the public API rejects unknown Sessions.
🔴 (19:14) User-provided V2 scoping policy: SessionRunner, model resolution, tool registry, permissions, and filesystem remain Location-scoped. Missing Location.workspaceID means implicit-local placement; explicit workspace identity is reserved for future placement semantics.
🔴 (19:14) User-provided V2 model-call policy: preserve exactly one explicit llm.stream(request) per Physical Attempt and reload projected history before durable continuation. A logical Step may use generic pre-output retries, one full-context retry after continuation rejection, incomplete-stream continuation, or one overflow-compaction rebuild. Generic retries keep the logical step number and do not consume another agent-step allowance; orchestration must not be delegated to an in-memory tool loop.
🔴 (19:14) User-provided V2 drain/recovery policy: Session drains remain process-local until clustering. SessionRunCoordinator joins explicit same-Session resumes, coalesces prompt wakeups, and permits different Sessions concurrently. A write-ahead execution claim marks process-local busy state for recovery: completion, failure, or user interruption releases it; shutdown interruption/process death preserves it. Startup recovery resumes claimed top-level Sessions with durable per-execution attempt accounting. The claim is not clustered ownership, fencing, or exactly-once execution.
🔴 (19:14) User-provided V2 delivery semantics: prompts steer by default. At safe step boundaries, steered compaction has priority through the first steered move control; remaining steers retain enqueue order. At idle boundaries, steers have priority; otherwise exactly one queued item is delivered before continuation is reevaluated. Items may be cancelled/changed between queue and steer. Promoting new user input resets the chosen agent’s step allowance; a steer batch resets it once.
🔴 (19:14) User stated one step means one logical LLM call and its durable record covers only the model-visible span. Do not use “provider turn” or bare “turn” for one call; “turn” is reserved for a future assistant-turn unit spanning all steps from prompt promotion until idle.
🔴 (19:14) User stated event replay ownership must remain separate from clustered Session execution ownership.
🔴 (19:14) User-provided instruction architecture: keep Instructions algebra and built-ins in src/instructions; instruction producers stay with observed domains; Session owns History selection plus InstructionState and InstructionEntry persistence. InstructionDiscovery observes ambient global/upward-project instructions. The runner explicitly composes built-ins, discovery, guidance, and entries in loadInstructions; there is no instruction registry.
🔴 (19:14) User-provided instruction persistence semantics: session.instructions.updated stores changed source keys/content hashes and may freeze chronological update text; blob values live once in instruction_blob; projected instruction_state is the normal current/initial-value boundary source. Request assembly renders the epoch baseline from stored values, later frozen updates enter history as durable System messages, completed compaction moves the instruction epoch, Session movement retains it, committed revert clears it, forks adopt the parent’s newest instruction values even when copied history ends earlier, and unavailable sources retain their last value while blocking only the initial complete delta.
🟡 (19:14) Assistant committed to freeze review inputs—repository HEAD, tracked diff hash/file set, external file hashes, and final SEA hash—then trace authorization and PTY lifecycle paths, audit active config/service/cutover files, and run only package-scoped read-only validation.
🔴 (19:14) Repository HEAD was 2ac698d65aa4690a694307e9cbdf44537ea9a4fb.
🔴 (19:14) Tracked working-tree diff hash was e3d14c2a661025b56eb941304df6c5d217d0dd95c8d01497e78011d3e990a34a; modified files were packages/core/src/plugin/host.ts, packages/core/test/plugin.test.ts, packages/core/test/plugin/fixture.ts, packages/core/test/plugin/host.ts, packages/plugin/src/effect/permission.ts, packages/plugin/src/promise/adapter.ts, and packages/plugin/src/promise/permission.ts.
🔴 (19:14) Untracked build artifacts were packages/cli/dist-v2-pilot-byk/cli-node-linux-x64/bin/opencode2-node, packages/cli/dist-v2-pilot-byk/cli-node-linux-x64/package.json, packages/cli/dist-v2-pilot-final/cli-node-linux-x64/bin/opencode2-node, packages/cli/dist-v2-pilot-final/cli-node-linux-x64/package.json, packages/cli/dist-v2-pilot-next/cli-node-linux-x64/bin/opencode2-node, packages/cli/dist-v2-pilot-next/cli-node-linux-x64/package.json, packages/cli/dist-v2-pilot/cli-node-linux-x64/bin/opencode2-node, and packages/cli/dist-v2-pilot/cli-node-linux-x64/package.json.
🔴 (19:14) /home/byk/.local/share/opencode-v2-pilot contained cache/, config/, CUTOVER.md, data/, opencode-v2.service, state/, and supervisor/.
🔴 (19:15) /home/byk/.local/share/opencode-v2-pilot/config/opencode contained bun.lock, node_modules/, opencode.json, package.json, plugins/, service-v2-pilot.json, test/, tsconfig.json, and types/; plugins/ contained followup.ts and pty.ts, while test/ contained followup.test.ts and pty.test.ts.
🔴 (19:15) Frozen external hashes: config/opencode/plugins/followup.ts=8d51037989aa2cbfa10de82b56ef9512218a07544a5308a74890d59252f43bad; config/opencode/plugins/pty.ts=b90315811f1ff67cd93d48676dda2fa02d9be0694bf154a049c8746a8ed11aa5; config/opencode/test/followup.test.ts=507a152473809a95363a3758ab39fb6e8f5c82f32c23a9f11d7655607951f7ec; config/opencode/test/pty.test.ts=c2bed5de452d64198e20cb4255e276e7b37be4b9fd60ee0f4bd98ccac14288b3.
🔴 (19:15) Frozen config/service hashes: config/opencode/opencode.json=571a0730c86bde522edf63516b7fa69cb9cbf2ff8730e15e2a8ce064c33ad905; config/opencode/service-v2-pilot.json=0dbe0518ab13584c3a4ae465fb552d79572460766910ff9f7ea4271280d66bb0; opencode-v2.service=38fc8a7f4b25acf611557c32380365f3f7d0422f8a31d0f5eb7ffba48e916045; CUTOVER.md=31ce6b09b9448660dfed2ebd9f4a44e6e033cc0753ef978253db02884fc68b2b.
🔴 (19:15) Frozen SEA binary hash: /home/byk/Code/opencode-v2-pilot/packages/cli/dist-v2-pilot-byk/cli-node-linux-x64/bin/opencode2-node=c67985bbc31f99e040ed58abf2aa37fc7c9a197a46c269eafaec468d99658937.
🔴 (19:16) Working-tree change in packages/core/src/plugin/host.ts: the plugin host’s permission context now exposes canonical assert via assert: permission.assert, alongside hook, list, get, and other methods.
🔴 (19:16) Working-tree change in packages/plugin/src/effect/permission.ts: added PermissionAssertInput with sessionID: Session.ID, action: string, resources: ReadonlyArray<string>, optional save, metadata, source, and agent; PermissionDomain now exposes assert(input): Effect.Effect<void, unknown>.
🔴 (19:16) Working-tree change in packages/plugin/src/promise/permission.ts: added the same PermissionAssertInput surface and PermissionDomain.assert(input): Promise<void>.
🔴 (19:16) Working-tree change in packages/plugin/src/promise/adapter.ts: added TypedFailure, and Promise-plugin ctx.permission.assert now runs host.permission.assert(input) while converting typed Effect failures into a defect carrying TypedFailure across the Promise boundary.
🔴 (19:16) Working-tree change in packages/plugin/src/promise/adapter.ts: executePromiseTool changed from Effect.promise to Effect.tryPromise; a caught TypedFailure becomes Tool.Error, using error.feedback when it is a string, otherwise an Error.message, otherwise String(error.error). Other Promise rejections remain defects via Effect.die(error).
🔴 (19:16) Working-tree tests in packages/core/test/plugin.test.ts added “exposes canonical permission assertions to Promise plugins,” activating plugin promise-permission, calling ctx.permission.assert for Session ses_plugin_permission, action shell, resource "'true'", and tool source IDs msg_plugin_permission / call_plugin_permission, then verifying plugin status remains active.
🔴 (19:16) Working-tree tests in packages/core/test/plugin.test.ts added “preserves Promise permission assertion outcomes through tool execution.” Tool permission returns allowed on allow; Permission.CorrectedError with feedback "Use a read-only command instead" becomes Tool.Error with that message; a generic defect "permission assertion defect" remains a die with no typed failure; Permission.DeclinedError also remains a die with no typed failure.
🔴 (19:16) Working-tree fixture change in packages/core/test/plugin/fixture.ts: introduced pluginTestLayer(assert: Permission.Interface["assert"] = () => Effect.void) so tests can inject permission assertion behavior; PluginTestLayer now equals pluginTestLayer().
🔴 (19:16) Working-tree fixture change in packages/core/test/plugin/host.ts: default permission host stubs now include assert: () => Effect.die("unused permission.assert").
🔴 (19:17) Canonical permission implementation is /home/byk/Code/opencode-v2-pilot/packages/core/src/permission.ts; related files include /home/byk/Code/opencode-v2-pilot/packages/core/src/permission/sql.ts and /home/byk/Code/opencode-v2-pilot/packages/core/src/permission/saved.ts.
🔴 (19:17) packages/core/src/permission.ts defines Permission.AssertInput with optional id and agent, plus request fields sessionID, action, resources, save, metadata, and source; Permission.Interface.assert returns Effect.Effect<void, BlockedError | CorrectedError | SessionErrors.NotFoundError>.
🔴 (19:17) Canonical Permission.assert semantics: deny produces BlockedError; allow returns successfully; ask creates a pending request and awaits its deferred response. Permission.DeclinedError is deliberately tunneled as a defect so blanket leaf mapError handling cannot convert a user decline to model-facing tool output, while Permission.CorrectedError stays typed so the leaf can produce ToolFailure and continue the model.
🔴 (19:17) Canonical permission reply semantics: "reject" fails the selected request with CorrectedError when a message exists, otherwise DeclinedError, then declines all other pending requests for the same Session. "always" persists saved resources when available and reevaluates pending requests, automatically allowing those newly resolved to allow.