DashboardopencodeDistillation

Distillation

ID: 9654083a-fae0-4257-b645-8c98d87721ad
Session: 0QFZoxrfG577
Generation: 0
Tokens: 2425
R_compression: 32.924
C_norm: 0.003
Archived: No
Created: 2026-09-08 19:48:12
Source IDs:
["lore_tm_v1_Ajm8gLAhuib8ssBmj0caaP57wvXbIS0CvBfJmCx_rFo","lore_tm_v1_4Ab_FIJyF_2wRuSxblHLRRhfaDzf_VBcU5hngtEHcIw"]

Observations

🔴 (19:38) /home/byk/Code/opencode-v2-pilot/packages/ai/AGENTS.md error policy: reason.body is the sole original-response or triggering-event payload field; preserve original text before Schema decoding removes fields and never replace the complete event with only its nested error. 🔴 (19:38) /home/byk/Code/opencode-v2-pilot/packages/ai/AGENTS.md error policy: reason.http must describe an observed HTTP response with required url, status, and response headers; do not invent status codes or derive a separate request ID from headers. 🔴 (19:38) Reclassification and transport recovery must preserve an AI error reason’s body, HTTP context, and underlying cause; because message and cause are non-enumerable, copy them explicitly when enriching a reason through its constructor or AIErrorReason.make. 🔴 (19:38) AI package implementation directives: prefer HttpClient.HttpClient / HttpClientResponse.HttpClientResponse over web fetch / Response; prefer Stream.Stream over ad hoc async generators or manual reader loops; use Schema.fromJsonString(...) instead of direct JSON.parse / JSON.stringify; yield yieldable errors directly in Effect.gen; use Effect.void instead of Effect.succeed(undefined). 🔴 (19:38) AI package constructor convention: use per-type constructors directly—Message.system(...), Message.user(...), Message.assistant(...), Message.tool(...), LanguageModel.make(...), ToolDefinition.make(...), ToolCallPart.make(...), ToolResultPart.make(...), ToolChoice.make(...), ToolChoice.named(...), SystemPart.make(...), and GenerationOptions.make(...); reserve top-level LLM for LLM.request, LLM.generate, LLM.stream, and LLM.generateObject. 🔴 (19:38) Use LLMRequest.update(...) to derive canonical request data; do not introduce a duplicate LLM.updateRequest(...) path. 🔴 (19:38) Provider-defined string enums must remain forward-compatible by exposing known values as Known | (string & {}) and using Schema.String at runtime unless correctness requires rejecting unknown values. 🔴 (19:38) AI tests requiring Effect layers use testEffect(...) from test/lib/effect.ts; provider tests stay fixture-first, and live calls require RECORD=true plus required API-key checks. 🔴 (19:38) AI package architecture: Schema classes under src/schema/ are the canonical runtime data model; src/llm.ts convenience functions are thin constructors for those same instances and must not create a second model. 🔴 (19:38) Session integration belongs in packages/core/src/session: runner/llm.ts owns orchestration, model-request.ts lowers Session state into LLMRequest, and model-transport.ts selects transport behavior; the AI package remains independent of Session auth, permissions, plugins, telemetry headers, and runtime selection. 🔴 (19:38) Route architecture has four orthogonal pieces: 1. Protocol owns request construction/schema and stream event decoding/state transitions; 2. Endpoint owns host, path, and query; 3. Auth owns per-request authentication/signing; 4. Framing converts bytes into frames. 🔴 (19:38) OpenResponsesChannel.transport(...) owns provider-neutral Responses WebSocket behavior: prepare one final request, use HTTP by default, remove WebSocket-disallowed fields, and invoke a per-call WebSocketChannelExecutor when supplied; Route.streamPrepared acknowledges channel completion only after successful full consumption. 🔴 (19:38) Provider facades configure endpoint/auth/resource/API-version before model selection; use model for the default API and named alternatives such as OpenAI responses and chat; configuration belongs on .configure(...), not duplicate model(id, overrides) construction. 🔴 (19:38) Provider auth convention: prefer apiKey sugar and explicit auth override as mutually exclusive via ProviderAuthOption; resolve with AuthOptions.bearer(options, "<PROVIDER>_API_KEY"), honoring explicit auth and otherwise using Auth.config(envVar) so missing keys become typed Authentication errors. 🔴 (19:38) Every named provider gets its own file and top-level export containing endpoint, auth defaults, and route setup; compose shared protocols directly rather than nesting presets under generic compatible facades or storing endpoints in a shared provider profile registry. 🔴 (19:38) Provider package entrypoints are package-like internal paths in the single @opencode/ai package, implement ProviderPackage.Definition, and expose model(modelID, settings) with serializable provider configuration plus common headers, body, and limits; they must not expose Route in settings. 🔴 (19:38) Protocol dependency direction: providers/*.ts may import protocol routes and auth-option utilities; protocol modules may import endpoint/auth/framing/transport pieces; protocols must not import provider facades; lower-level modules must not know provider catalog metadata. 🔴 (19:38) OpenAIResponses composes provider-neutral OpenResponses; the baseline never imports the OpenAI extension. 🔴 (19:38) Shared protocol helpers: ProviderShared.joinText(parts) joins .text values with newlines; parseToolInput(route, name, raw) Schema-decodes tool arguments, treats empty input as {}, and uses the canonical invalid-JSON message; parseJson(route, raw, message) handles generic JSON-via-Schema decoding; eventError(...) constructs typed InvalidProviderOutput; validateWith(decoder) maps Schema decode failures to InvalidRequest; matchToolChoice(...) branches provider lowering over LLMRequest["toolChoice"]. 🔴 (19:38) If a 3-to-5-line snippet is copied between two protocols, lift it into ProviderShared instead of duplicating it. 🔴 (19:38) Chronological system-message policy: LLMRequest.system is the initial privileged prompt; Message.system(...) is a provider-neutral chronological operator update that applies only from its history position and accepts text only. 🔴 (19:38) Open Responses lowers chronological system updates to developer messages; Anthropic Messages lowers them to native system messages only for Claude Opus 4.8 (claude-opus-4-8); other routes/models lower them in place to escaped user-compatible text wrapped in <system-update>...</system-update>. 🔴 (19:38) Never pass a raw chronological role: "system" through a route that may reject it, and never place raw retrieved documents, tool output, or web content into privileged chronological system updates. 🔴 (19:38) LLM.stream(request) and LLM.generate(request) each perform exactly one model call; callers add schemas via Tool.toDefinitions(tools) and explicitly dispatch canonical local tool-call events through ToolRuntime.dispatch(tools, call). 🔴 (19:38) ToolRuntime.dispatch(...) looks up the named tool, decodes input against parameters, invokes typed execute, encodes output against success, and returns canonical tool-result events; it does not stream providers, construct Session events, schedule fibers, append history, count steps, or continue model rounds. 🔴 (19:38) Tool handler dependencies—services, permissions, plugin hooks, and abort handling—are closed over when tools are constructed; build the tool record once inside Effect.gen and reuse it. 🔴 (19:38) Three recoverable dispatch paths produce tool-error events: 1. unknown tool name, 2. input failing the parameters Schema, 3. handler returning ToolFailure. 🔴 (19:38) Hosted/provider-executed tools are surfaced as matching tool-call and tool-result events with providerExecuted: true; callers must skip local dispatch and preserve both events in explicit history when required. 🔴 (19:38) Supported hosted-tool examples: Anthropic web_search, code_execution, and web_fetch; OpenAI Responses web_search_call, file_search_call, code_interpreter_call, mcp_call, image_generation_call, and computer_use_call. 🔴 (19:38) Protocol modules use this exact conceptual section order: 1. public model input, 2. request body schema, 3. streaming event schema, 4. parser state, 5. request construction (fromRequest), 6. stream parsing (step and handlers), 7. protocol and route, 8. protocol route export. 🔴 (19:38) Protocol parser state owns terminal finish reason, usage, and pending tool calls; emit exactly one terminal finish event—or provider-error—per completed response, normally after step-finish; use stream.terminal for completion sentinels and stream.onHalt when final state must flush after stream end. 🔴 (19:38) Protocol style directive: use Effect.fn("Provider.fromRequest") for request construction; effectful event handlers use Effect.fn(...); synchronous handlers remain plain functions returning StepResult; prefer named per-event handlers called from a small top-level step switch. 🔴 (19:38) Recorded tests use one cassette per scenario, containing an ordered array of { request, response } interactions; recordedTests({ prefix, requires }) derives cassette names from test names. 🔴 (19:38) Recorded-test replay is the default; RECORD=true records fresh cassettes and requires declared environment variables; CI always forces replay, so CI must be unset before recording. 🔴 (19:38) Recorded-test filters: RECORDED_PROVIDER=openai filters provider:openai; RECORDED_PREFIX=openai-chat filters cassette groups; RECORDED_TAGS=tool requires all tags, including combinations such as provider:togetherai,tool; RECORDED_TEST="streams text" matches test name, kebab-case test ID, or cassette path. Filters work in replay and record modes. 🔴 (19:38) Recorder body handling: known textual media types are stored as text; all other response bodies are stored as base64 with bodyEncoding: "base64" to preserve formats such as AWS event-stream frames. 🔴 (19:38) Recorded-request matching atomically claims the first unused interaction matching method, URL, allow-listed headers, and canonical JSON body; distinct requests may replay concurrently or in any order, while repeated identical requests consume responses in cassette order. 🔴 (19:38) scriptedResponses in test/lib/http.ts is the deterministic non-live counterpart to cassettes and scripts response bodies in order without reading disk. 🔴 (19:38) Do not blanket re-record a test file when adding one cassette: RECORD=true rewrites every recorded case that runs, and provider streams contain volatile IDs, timestamps, fingerprints, and obfuscation fields; instead delete the single cassette being refreshed or run a focused pattern. 🔴 (19:39) [validation-run] User ran tsgo --noEmit; the command produced no diagnostics, indicating the TypeScript no-emit check completed successfully.