Dashboard › opencode › Distillation
ff7e50e9-d56f-40e7-8bbe-cd59050cf14d["lore_tm_v1_2-mQY-DUNXjOPk5ZbjDq4c97W7vS1_oM4jKTg_tO2CU","lore_tm_v1_7BIVvEnH2jrrXR7viHK0BmSQYvTDJWQaZRpnFOamiVM","lore_tm_v1_DLDHRctZu5--6PjSKoOWFVmmLYBlwQwSQ8quxCs_7mk","lore_tm_v1_nU5xuYNUu6jaMN-zKGrX2nKTceU6oFv9X3glzwe30v8","lore_tm_v1_fsCSt8cq2YWB2xvUSeO0hXVDP2JmIFuQNL9li-FQCx4","lore_tm_v1_37Vs2oooWoFPOmzKXdWRHi34r5PxJhT6oQ5DG_tWVl8","lore_tm_v1_rydInBBwRn-oQGnV2mpZl5Xn5ety6b3t8kOKlZCg_YQ","lore_tm_v1_uKhnmwy0vJw8WjtiWneIKHxoORfjljOzyWfc0NqKEOs"]
packages/core/src/config/plugin/external.ts defines PluginModule as a Schema.Struct whose default is a union of: an Effect plugin { id: Schema.String, effect: function }, or a Promise plugin { id: Schema.String, setup: function }.packages/core/src/config/plugin/external.ts processes config document entries and directory entries. Document plugin references support package strings or { package, options }; file:// references use fileURLToPath(), ./ and ../ references resolve relative to the config document directory, and package names remain unchanged.packages/core/src/config/plugin/external.ts globs {plugin,plugins}/*.{ts,js} with absolute: true, include: "file", dot: true, and symlink: true; glob failures become [], results are sorted, and every file is configured as a plugin package.pathToFileURL(...).href, or an npm package resolved through (yield* npm.add(ref.package)).entrypoint; the module is dynamically imported and decoded with Schema.decodeUnknownEffect(PluginModule).PluginPromise.fromPromise(value); plugins are added through ctx.plugin.add({ id: plugin.id, effect: (host) => plugin.effect({ ...host, options: ref.options ?? {} }) }). Per-plugin failures are suppressed with Effect.ignoreCause, and loading runs in a scoped fork with startImmediately: true.packages/gateway/src/cli/start.ts, the --local flag always disables remote mode, mirroring hosted mode.packages/gateway/src/cli/start.ts uses withLifecycleLock("gateway-start", ...), withLifecycleLock("gateway-shutdown", ...), and withoutLifecycleLock(...); it publishes discovery via writePortFile and writeGatewayProcessFile.packages/gateway/src/cli/start.ts:1244-1255 stops any successfully bound server before retrying, preventing leaked file descriptors when some hosts bind before another fails with EADDRINUSE; non-port-conflict errors are rethrown rather than retried.packages/gateway/src/cli/start.ts:1257-1273 re-reads the process record and authenticates that exact persisted owner. Public health never establishes identity.candidatePort and io.authenticate(record) succeeds, gateway startup reuses the authenticated host, sets config.hosts = [authenticatedHost], and returns { port: candidatePort, owned: false, managementToken: record.token, shutdown: async () => {} }.Port ${candidatePort} is already in use by another process (not an authenticated lore gateway). Use --port / LORE_LISTEN_PORT to pick a different port.portsToTry, logs either the next numeric port or "random port", and does not log fallback for port 0.0 always succeeds or throws a non-EADDRINUSE error; the terminal throw new Error("Failed to bind to any port.") in packages/gateway/src/cli/start.ts:1296 is unreachable.commandStart(opts) in packages/gateway/src/cli/start.ts delegates background mode to startDaemon(opts); foreground startup calls startGateway({ ...opts, processBoundary: true }).getGitRemote(path: string): string | null in packages/core/src/git.d.ts:46 and packages/core/src/git.ts:101, and discoverWorkspaceRoot(startDir: string): string in packages/core/src/workspace.ts:116.>= 22.5; it is a TypeScript monorepo using pnpm workspaces; storage is SQLite in WAL mode with FTS5 at ~/.local/share/lore/lore.db.@loreai/core at packages/core/ for DB, distillation, knowledge/LTM, recall, gradient context, and prompts; @loreai/gateway at packages/gateway/ for the transparent LLM proxy, CLI, context management, and dashboard; @loreai/opencode at packages/opencode/ for OpenCode lifecycle integration; and @loreai/pi at packages/pi/ for the Pi extension adapter.@loreai/core is consumed by gateway, opencode, and pi; gateway bundles core into one CJS file through esbuild, while the OpenCode package ships raw TypeScript.packages/gateway/src/cli/bin.ts β packages/gateway/src/cli/main.ts (_cli()). Commands are: run (default) in cli/run.ts, start in cli/start.ts, data in cli/data.ts with list, show, clear, delete, merge, and recover, recall in cli/recall-cmd.ts, and upgrade in cli/upgrade.ts.cli/agents.ts scans $PATH for Claude Code, Codex, Pi, and OpenCode, then sets routing environment variables such as ANTHROPIC_BASE_URL.packages/core/src/temporal.ts: store() converts message parts to text and writes temporal_messages with FTS5 indexing; messages use a distilled=0/1 flag and chunks are separated by \x1f (ASCII Unit Separator).packages/core/src/distillation.ts: run() executes on idle or urgency, with flow resetOrphans() β undistilled messages β detectSegments() β distillSegment() β generation-0 storage. Segments split on time gaps/token limits with a maximum of 16K tokens per segment.20, metaDistill() consolidates into generation 1 or higher. Generation-0 segments remain archived and searchable through recall but are excluded from the in-context prefix.transform() in packages/core/src/gradient.ts and has 4 layers in order: Layer 0 full passthrough; Layer 1 distilled prefix plus compressed raw history; Layer 2 aggressive tool-output stripping; Layer 3 emergency compression. It calibrates from real API token counts and applies cost-aware caps based on model pricing.packages/core/src/ltm.ts use categories decision, pattern, preference, architecture, and gotcha; fields are id (UUIDv7), project_id, category, title, content (maximum 1200 characters), confidence (0.0-1.0), and cross_project.packages/core/src/curator.ts, producing create/update/delete operations; 2. regex-based, non-LLM Pattern Extraction in packages/core/src/pattern-extract.ts after each distillation; 3. File Import in packages/core/src/agents-file.ts from .lore.md markers; 4. curator.ts consolidate() when entry count exceeds maxEntries (25).packages/core/src/ltm.ts functions are create(), update(), and remove() for CRUD with a dedup guard; forSession() for relevance-ranked, budget-capped prompt entries; and search() / searchScored() for FTS5 BM25 search with a LIKE fallback..lore.md entries use <!-- lore:UUID --> markers and are sorted alphabetically by title within each category. exportLoreFile() builds from the DB and skips writes when the content hash is unchanged; importLoreFile() updates known UUIDs whose content changed and creates unknown UUIDs; shouldImportLoreFile() first checks mtime, then compares content hashes.exportToFile() writes the Lore pointer section in AGENTS.md between managed markers; splitFile() preserves all content outside those markers, so manually added content outside the markers is safe.temporal.store() β temporal_messages; distillation.run() with observer LLM β generation-0 distillations; metaDistill() with reflector LLM β generation-1+ while generation 0 is archived; curator.run() β knowledge create/update/delete; exportLoreFile() β .lore.md; exportToFile() β AGENTS.md; next-session importLoreFile(); then forSession() + formatKnowledge() for system-prompt injection and searchRecall for recall tool responses.pnpm install, pnpm test, pnpm run typecheck, pnpm run lint, pnpm run lint:fix, pnpm run format, pnpm run format:check, and pnpm run build. Lint uses type-aware oxlint and formatting uses oxfmt.packages/core/test/setup.ts; they never use the production DB.packages/gateway/script/build.ts produces the gateway CJS bundle, script/bundle.ts creates the standalone binary, and packages/core/script/build.ts creates Node.js-compatible CJS output.CHANGELOG.md; Craft handles both automatically.gh workflow run release.yml -f version=auto for conventional-commit semver selection, or an explicit version such as gh workflow run release.yml -f version=0.23.0.scripts/bump-version.sh updates every package.json version; 2. changelog is generated from conventional commits; 3. a release/X.Y.Z branch is created and pushed; 4. CI tests and builds npm tarballs, standalone binaries, and delta patches; 5. Craft opens a "publish" issue and the Publish workflow runs when labeled accepted; 6. publishing uses npm OIDC trusted publishing and creates a GitHub Release with binaries and patches..craft.yml defines release targets for 4 npm workspaces, the legacy opencode-lore alias, and a GitHub Release containing binaries.quality/REVIEW.md requires regression-test discipline, adversarial-order state setup, fan-out registry coverage, recurring bug-class batteries, and the two-reviewer rule; every non-trivial PR requires an adversarial correctness review before merge.packages/opencode/src/index.ts maintains per-project state because the OpenCode plugin function runs once per session/project while multiple projects and sub-agents may share one process; project path and git remote are tracked so concurrent requests do not clobber one another."chat.headers" adds remote gateway access headers, stable x-lore-session-id from input.sessionID, and x-lore-agent from input.agent. The OpenCode DB session ID survives restarts, unlike process-regenerated x-session-affinity.resolveParentSession(ctx.client, input.sessionID) and emits x-parent-session-id when found. Resolution is cached and never blocks or throws on failure."chat.headers" sets x-lore-project from thisProjectPath and x-lore-git-remote from thisGitRemote for the current plugin project; this per-request injection prevents concurrent sub-agents or sibling projects from clobbering path/remote data.getHeaders() remains a fallback for SDK requests that bypass "chat.headers", including embedding and image generation. It only emits a git remote paired with the project path for which that remote was resolved, never a remote left over from another project plugin call.input.provider.id via Record<string, unknown> because SDK typings omit .id; when present it emits x-lore-provider, allowing protocol/upstream routing by provider rather than model-prefix guessing.vllm, ollama, and llamacpp, the plugin maps provider IDs to LORE_UPSTREAM_${providerID.toUpperCase().replace(/-/g, "_")} and emits that value as x-lore-upstream-url only if the header is not already set. This matches the Pi pluginβs registerProviders() convention.LORE_UPSTREAM_EXTRA_HEADERS is parsed line-by-line using the first : separator; blank/malformed lines are skipped, names and values are trimmed, and headers are forwarded only when shouldForwardUpstreamExtraHeader(name) allows them, avoiding clobbering gateway-managed headers. Intended consumers include corporate proxies, LiteLLM, and Cloudflare AI Gateway; the gateway applies the same environment variable as a safety net.log and never raw stderr because OpenCode owns a full-screen TUI and any stray byte corrupts it. Startup information remains visible through lore logs.discoverWorkspaceRoot(ctx.worktree || ctx.directory) and logs active: ${projectPath}. If Lore is active, it installs the fetch interceptor once, logs routing through ${gatewayBase} and dashboard: ${gatewayBase}/ui, then sets processInitDone = true.log.error(\init failed: ${detail}`)` before being rethrown, preserving root cause in the file and Sentry sink even when stderr is silenced and OpenCodeβs plugin loader catches the error.packages/opencode/src/index.ts must not add any other export: OpenCodeβs legacy loader invokes every function export as a plugin and throws on every non-function export, dropping the plugin entirely. Helpers belong in ./internal.ts; the module must export only LorePlugin and the same-reference default export. This is guarded by the "plugin entry module export shape" test in test/index.test.ts.