From 2c32b8df1fa24776d6f50daa6d3b1cd5b16dee20 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 2 May 2026 19:04:09 +0100 Subject: [PATCH] Relax source grounding and pin owner Codex model --- .env.example | 1 + README.md | 4 +-- apps/bot/src/discord-bot-support.ts | 2 +- apps/bot/src/discord-bot.ts | 1 + apps/worker/src/index.ts | 1 + notes/fresh-workspace-handoff.md | 1 + packages/config/src/index.ts | 3 ++ packages/providers/src/local-llm-provider.ts | 24 --------------- packages/providers/src/local-llm-render.ts | 30 +++++++------------ packages/providers/src/local-llm-shared.ts | 1 - .../providers/src/owner-codex-provider.ts | 25 ++-------------- packages/providers/src/owner-codex-render.ts | 8 +++-- packages/providers/src/owner-codex-runtime.ts | 3 ++ packages/providers/src/owner-codex-shared.ts | 12 +------- state/evidence.jsonl | 1 + state/map.yaml | 4 +-- 16 files changed, 37 insertions(+), 84 deletions(-) diff --git a/.env.example b/.env.example index 760eddd..0697c4b 100644 --- a/.env.example +++ b/.env.example @@ -41,6 +41,7 @@ ENABLED_PROVIDERS=owner_codex OWNER_CODEX_MODE=local_exec_owner_only CODEX_EXECUTABLE=codex CODEX_EXEC_ARGS= +CODEX_MODEL=gpt-5.4 CODEX_MODEL_REASONING_EFFORT=low CODEX_EXEC_TIMEOUT_MS=120000 STYLE_PACK_PATH=styles/void-default.md diff --git a/README.md b/README.md index e79b513..f4aca2c 100644 --- a/README.md +++ b/README.md @@ -213,7 +213,7 @@ npm run dev:worker If `DISCORD_APPLICATION_ID` and `DISCORD_GUILD_ID` are set, the bot registers slash commands into that guild on startup. -For `owner_codex`, the worker also needs a working local Codex CLI. If plain `codex` is not invokable from Node on your machine, point `CODEX_EXECUTABLE` and `CODEX_EXEC_ARGS` at the actual install. +For `owner_codex`, the worker also needs a working local Codex CLI. If plain `codex` is not invokable from Node on your machine, point `CODEX_EXECUTABLE` and `CODEX_EXEC_ARGS` at the actual install. `CODEX_MODEL` pins the owner Discord lane to a specific model; the default is `gpt-5.4` so older local Codex installs do not faceplant on `gpt-5.5`. ## Example Questions @@ -262,7 +262,7 @@ The `local_llm` provider uses Ollama chat completion plus a bounded host-managed Unlike the owner Codex lane, this is not MCP. It is a tighter host-controlled loop and deliberately excludes side-effecting tools. -Project, repo, and lore questions are now source-grounded by policy: if the prompt matches indexed repo/project cues, Void must touch the source-side lookup tools before answering confidently. +Project, repo, and lore questions should lean on the source-side lookup tools when the answer depends on indexed repo or lore facts. The providers now treat source-grounding hints as advisory instead of letting a dumb lexical hall monitor veto the whole reply before the model can think. ## Discord, Repo, And Lore Indexing diff --git a/apps/bot/src/discord-bot-support.ts b/apps/bot/src/discord-bot-support.ts index 6f0b1da..0047b98 100644 --- a/apps/bot/src/discord-bot-support.ts +++ b/apps/bot/src/discord-bot-support.ts @@ -288,7 +288,7 @@ export function inferSourceGroundingHint( } return { - required: reasons.size > 0, + required: false, reasons: [...reasons], matchedRepoNames: [...matchedRepoNames].sort(), }; diff --git a/apps/bot/src/discord-bot.ts b/apps/bot/src/discord-bot.ts index 86d631e..8530a2d 100644 --- a/apps/bot/src/discord-bot.ts +++ b/apps/bot/src/discord-bot.ts @@ -149,6 +149,7 @@ export async function startBot(): Promise { mode: config.ownerCodexMode, executable: config.codexExecutable, executableArgs: config.codexExecArgs, + model: config.codexModel, reasoningEffort: config.codexModelReasoningEffort, timeoutMs: config.codexExecTimeoutMs, workingDirectory: process.cwd(), diff --git a/apps/worker/src/index.ts b/apps/worker/src/index.ts index fcd5769..f778ea3 100644 --- a/apps/worker/src/index.ts +++ b/apps/worker/src/index.ts @@ -136,6 +136,7 @@ async function buildProviderRegistry(systemMessagesCatalog: SystemMessageCatalog mode: config.ownerCodexMode, executable: config.codexExecutable, executableArgs: config.codexExecArgs, + model: config.codexModel, reasoningEffort: config.codexModelReasoningEffort, timeoutMs: config.codexExecTimeoutMs, workingDirectory: process.cwd(), diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index de79209..fbd071b 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -32,6 +32,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou - The stack has a dedicated logon startup task path now, so a reboot does not have to mean manual necromancy for bot and worker. - The operations watchdog now supports ignored local extension checks, so adjacent-service fire signals can ride the same owner DM alert path without hardcoding private project gossip into the public repo. - Source repo discovery now reconciles automatically during `npm run stack:start` and through the watchdog path, so new repos under `SOURCE_REPO_ROOT` stop waiting for manual hook folklore before they get indexed. +- Source-grounding hints are advisory now; the owner and local lanes can decide when repo or lore tool use is actually warranted instead of obeying a lexical front-door veto. - The new state/notes surfaces are now the continuity spine for future nontrivial work. ## Likely Next Bounded Move diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index d65b5fd..d96b295 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -86,6 +86,7 @@ const envSchema = z.object({ OWNER_CODEX_MODE: z.string().min(1).default("local_exec_owner_only"), CODEX_EXECUTABLE: z.string().min(1).default("codex"), CODEX_EXEC_ARGS: z.string().default(""), + CODEX_MODEL: z.string().min(1).default("gpt-5.4"), CODEX_MODEL_REASONING_EFFORT: z.enum(["low", "medium", "high", "xhigh"]).default("low"), CODEX_EXEC_TIMEOUT_MS: z.coerce.number().int().positive().default(120000), STYLE_PACK_PATH: z.string().min(1).default("styles/void-default.md"), @@ -116,6 +117,7 @@ export interface AppConfig { ownerCodexMode: OwnerCodexMode; codexExecutable: string; codexExecArgs: string[]; + codexModel: string; codexModelReasoningEffort: "low" | "medium" | "high" | "xhigh"; codexExecTimeoutMs: number; stylePackPath: string; @@ -285,6 +287,7 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): AppConfig { ownerCodexMode: parseOwnerCodexMode(parsed.OWNER_CODEX_MODE), codexExecutable: parsed.CODEX_EXECUTABLE, codexExecArgs: parseList(parsed.CODEX_EXEC_ARGS), + codexModel: parsed.CODEX_MODEL, codexModelReasoningEffort: parsed.CODEX_MODEL_REASONING_EFFORT, codexExecTimeoutMs: parsed.CODEX_EXEC_TIMEOUT_MS, stylePackPath: resolve(parsed.STYLE_PACK_PATH), diff --git a/packages/providers/src/local-llm-provider.ts b/packages/providers/src/local-llm-provider.ts index d572368..8bf9b20 100644 --- a/packages/providers/src/local-llm-provider.ts +++ b/packages/providers/src/local-llm-provider.ts @@ -10,9 +10,7 @@ import { import { buildArtifacts, buildPrompt, - buildSourceGroundingReminder, buildSystemPrompt, - didUseSourceGrounding, } from "./local-llm-render"; import { executeToolCall, @@ -20,7 +18,6 @@ import { normalizeAssistantMessage, } from "./local-llm-tools"; import { - MAX_SOURCE_GROUNDING_RETRIES, MAX_TOOL_TURNS, type LocalLlmProviderOptions, type OllamaChatMessage, @@ -96,8 +93,6 @@ export class LocalLlmProvider implements ProviderAdapter { const tools = this.options.toolbox ? buildToolDefinitions() : undefined; const toolTrace: ToolTraceRecord[] = []; const thinkingTrace: string[] = []; - const sourceGroundingRequired = Boolean(request.contextBundle.sourceGrounding?.required); - let sourceGroundingReminderCount = 0; try { let finalPayload: OllamaChatResponse | undefined; @@ -147,25 +142,6 @@ export class LocalLlmProvider implements ProviderAdapter { if (toolCalls.length === 0) { const candidateOutput = normalizeModelText(assistantMessage.content); - if ( - sourceGroundingRequired && - !didUseSourceGrounding(toolTrace) && - this.options.toolbox - ) { - if (sourceGroundingReminderCount < MAX_SOURCE_GROUNDING_RETRIES) { - sourceGroundingReminderCount += 1; - messages.push({ - role: "user", - content: buildSourceGroundingReminder(request.contextBundle), - }); - continue; - } - - finalOutputText = - "This request needs actual source grounding before I can answer it cleanly. Ask again after I pull from the indexed repos, or use the deeper Codex lane."; - break; - } - finalOutputText = candidateOutput; break; } diff --git a/packages/providers/src/local-llm-render.ts b/packages/providers/src/local-llm-render.ts index 432d47f..89ee48e 100644 --- a/packages/providers/src/local-llm-render.ts +++ b/packages/providers/src/local-llm-render.ts @@ -79,18 +79,6 @@ export function buildPrompt(context: ContextBundle): string { ].join("\n"); } -export function buildSourceGroundingReminder(context: ContextBundle): string { - return `${renderSourceGroundingInstructions(context, true)} Answer again only after using the source-side tools.`; -} - -export function didUseSourceGrounding(toolTrace: ToolTraceRecord[]): boolean { - return toolTrace.some((trace) => - trace.tool === "list_indexed_repos" || - trace.tool === "search_sources" || - trace.tool === "get_source_context", - ); -} - export function buildArtifacts(input: LocalLlmArtifactsInput): ProviderArtifact[] { return buildBaseArtifacts(input); } @@ -140,16 +128,12 @@ function renderSourceGroundingInstructions( context: ContextBundle, reminder: boolean, ): string { - if (!context.sourceGrounding?.required) { - return "Source-side grounding is optional here; use it when it clearly helps."; - } - const matchedRepos = - context.sourceGrounding.matchedRepoNames.length > 0 + context.sourceGrounding?.matchedRepoNames && context.sourceGrounding.matchedRepoNames.length > 0 ? ` Matched repos/projects: ${context.sourceGrounding.matchedRepoNames.join(", ")}.` : ""; const reasons = - context.sourceGrounding.reasons.length > 0 + context.sourceGrounding?.reasons && context.sourceGrounding.reasons.length > 0 ? ` Reasons: ${context.sourceGrounding.reasons.join(", ")}.` : ""; const retry = @@ -157,5 +141,13 @@ function renderSourceGroundingInstructions( ? " The previous answer attempt was discarded because it did not touch the source-side tools." : ""; - return `This prompt requires source-grounded evidence before you answer. Use list_indexed_repos, search_sources, or get_source_context first.${matchedRepos}${reasons}${retry}`; + if ( + !context.sourceGrounding || + (context.sourceGrounding.matchedRepoNames.length === 0 && + context.sourceGrounding.reasons.length === 0) + ) { + return "Source-side grounding is optional here; use it when it clearly helps."; + } + + return `This prompt may benefit from source-side grounding. Use list_indexed_repos, search_sources, or get_source_context if repo, lore, or source evidence would sharpen the answer.${matchedRepos}${reasons}${retry}`; } diff --git a/packages/providers/src/local-llm-shared.ts b/packages/providers/src/local-llm-shared.ts index 69efab1..747d907 100644 --- a/packages/providers/src/local-llm-shared.ts +++ b/packages/providers/src/local-llm-shared.ts @@ -5,7 +5,6 @@ import { export type OllamaThinkMode = boolean | "low" | "medium" | "high"; export const MAX_TOOL_TURNS = 4; -export const MAX_SOURCE_GROUNDING_RETRIES = 1; export const MAX_TOOL_RESULTS = MAX_RETRIEVAL_RESULT_LIMIT; export const MAX_CONTEXT_WINDOW = 20; diff --git a/packages/providers/src/owner-codex-provider.ts b/packages/providers/src/owner-codex-provider.ts index 9bd8e5e..c7c7bb2 100644 --- a/packages/providers/src/owner-codex-provider.ts +++ b/packages/providers/src/owner-codex-provider.ts @@ -11,10 +11,8 @@ import type { import { buildDefaultHandoffNotice, buildRequestPayload, - didUseSourceGrounding, type HistoryLookupTool, MAX_HISTORY_TOOL_CALLS, - MAX_SOURCE_GROUNDING_RETRIES, type OwnerCodexProviderOptions, type ToolCallRecord, } from "./owner-codex-shared"; @@ -176,25 +174,18 @@ export class OwnerCodexProvider implements ProviderAdapter { const artifacts: ProviderArtifact[] = [...baseArtifacts]; const turnResults = []; let result; - let sourceGroundingReminderCount = 0; - const sourceGroundingRequired = Boolean( - request.contextBundle.sourceGrounding?.required, - ); - for ( - let turn = 0; - turn <= MAX_HISTORY_TOOL_CALLS + MAX_SOURCE_GROUNDING_RETRIES; - turn += 1 - ) { + for (let turn = 0; turn <= MAX_HISTORY_TOOL_CALLS; turn += 1) { const codexPrompt = buildDiscordReplyPrompt( request.contextBundle, toolCalls, - sourceGroundingReminderCount > 0, + false, ); result = await runCodexExec({ executable: this.options.executable, executableArgs: this.options.executableArgs, + model: this.options.model, reasoningEffort: this.options.reasoningEffort, timeoutMs: this.options.timeoutMs, workingDirectory: this.options.workingDirectory, @@ -246,16 +237,6 @@ export class OwnerCodexProvider implements ProviderAdapter { } if (!result.toolRequest) { - if (sourceGroundingRequired && !didUseSourceGrounding(result.traceEvents)) { - if (sourceGroundingReminderCount < MAX_SOURCE_GROUNDING_RETRIES) { - sourceGroundingReminderCount += 1; - continue; - } - - result.handoffReason = - "This question needed source-grounded evidence, but the reply lane never used the source tools."; - } - break; } diff --git a/packages/providers/src/owner-codex-render.ts b/packages/providers/src/owner-codex-render.ts index e29d4d3..f56912a 100644 --- a/packages/providers/src/owner-codex-render.ts +++ b/packages/providers/src/owner-codex-render.ts @@ -333,7 +333,11 @@ function renderSourceGroundingInstructions( context: ContextBundle, reminder: boolean, ): string { - if (!context.sourceGrounding?.required) { + if ( + !context.sourceGrounding || + (context.sourceGrounding.matchedRepoNames.length === 0 && + context.sourceGrounding.reasons.length === 0) + ) { return "- Source-side grounding is optional here; use it when it clearly helps."; } @@ -349,7 +353,7 @@ function renderSourceGroundingInstructions( ? " The previous answer attempt was discarded because it did not touch the source-side tools." : ""; - return `- This prompt requires source-grounded evidence before you answer. Use list_indexed_repos, search_sources, or get_source_context first.${matchedRepos}${reasons}${retry}`; + return `- This prompt may benefit from source-side grounding. Use list_indexed_repos, search_sources, or get_source_context if repo, lore, or source evidence would sharpen the answer.${matchedRepos}${reasons}${retry}`; } function renderAgentMessageTrace(events: CodexTraceEvent[]): string { diff --git a/packages/providers/src/owner-codex-runtime.ts b/packages/providers/src/owner-codex-runtime.ts index 9436ca5..f94bfe6 100644 --- a/packages/providers/src/owner-codex-runtime.ts +++ b/packages/providers/src/owner-codex-runtime.ts @@ -42,6 +42,7 @@ const MAX_HISTORY_LIMIT = MAX_RETRIEVAL_RESULT_LIMIT; export async function runCodexExec(input: { executable: string; executableArgs: string[]; + model: string; reasoningEffort: "low" | "medium" | "high" | "xhigh"; timeoutMs: number; workingDirectory: string; @@ -54,6 +55,8 @@ export async function runCodexExec(input: { const args = [ ...input.executableArgs, "exec", + "-m", + input.model, "-c", 'approval_policy="never"', "-c", diff --git a/packages/providers/src/owner-codex-shared.ts b/packages/providers/src/owner-codex-shared.ts index d989a46..fcd1ab7 100644 --- a/packages/providers/src/owner-codex-shared.ts +++ b/packages/providers/src/owner-codex-shared.ts @@ -12,7 +12,6 @@ export const HANDOFF_SENTINEL = "VOIDBOT_HANDOFF_REQUIRED:"; export const OWNER_NOTIFY_SENTINEL = "VOIDBOT_OWNER_NOTIFY:"; export const TOOL_REQUEST_SENTINEL = "VOIDBOT_TOOL_REQUEST:"; export const MAX_HISTORY_TOOL_CALLS = 4; -export const MAX_SOURCE_GROUNDING_RETRIES = 1; export const MAX_NOTIFICATION_MESSAGE_LENGTH = 400; export interface CodexUsageSnapshot { @@ -35,6 +34,7 @@ export interface OwnerCodexProviderOptions { mode: OwnerCodexMode; executable: string; executableArgs: string[]; + model: string; reasoningEffort: "low" | "medium" | "high" | "xhigh"; timeoutMs: number; workingDirectory: string; @@ -133,16 +133,6 @@ export function buildDefaultHandoffNotice(jobId: string): string { return `This request needs the fuller local Codex flow. Check \`.voidbot/artifacts/${jobId}/handoff.md\` and \`.voidbot/artifacts/${jobId}/debug-trace.md\` in the local workspace.`; } -export function didUseSourceGrounding(events: CodexTraceEvent[]): boolean { - return events.some( - (event) => - event.kind === "mcp_tool_completed" && - (event.tool === "list_indexed_repos" || - event.tool === "search_sources" || - event.tool === "get_source_context"), - ); -} - export function extractLatestUsage( events: CodexTraceEvent[], ): CodexUsageSnapshot | undefined { diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 26762cb..2f54995 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -19,3 +19,4 @@ {"ts":"2026-05-02T14:05:54Z","type":"implementation","status":"ok","note":"Scheduled-task helpers must anchor repo-relative scripts, dotenv loading, and path resolution to the VoidBot root; relying on cwd keeps sending System32 in to eat them."} {"ts":"2026-05-02T17:34:02Z","type":"implementation","status":"ok","note":"Layered private system message catalogs over the tracked base, deferred slow slash replies before provider execution, and chunked Discord output so missing keys and overlong local_llm replies stop failing live interactions."} {"ts":"2026-05-02T17:39:46Z","type":"implementation","status":"ok","note":"Source-grounding alias matching now ignores overly short repo components so social prompts mentioning Void stop tripping fake repo-evidence requirements, and direct messages intentionally count as prompts without requiring a mention."} +{"ts":"2026-05-02T18:28:04Z","type":"implementation","status":"ok","note":"Pinned the owner Codex lane to a configurable CODEX_MODEL defaulting to gpt-5.4 because the local CLI still faceplants on gpt-5.5, and relaxed source-grounding from a lexical hard gate into an advisory hint so the model can decide when repo/lore tool use is actually needed."} diff --git a/state/map.yaml b/state/map.yaml index bb39724..6709cc0 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -30,7 +30,7 @@ invariants: - Scratch is temporary and disposable. - Evidence is durable and distilled; routine activity proof belongs in git history, commits, smoke artifacts, or targeted logs. - Jobs, audit events, interaction memory, and rate-limit state live in Postgres; vectors live in Qdrant; archives, artifacts, logs, and backups remain under `.voidbot/`. - - Source, repo, and lore answers must be grounded through source-side retrieval instead of vibes. + - Source, repo, and lore answers should use source-side retrieval when the reply depends on indexed facts; the hinting is advisory, not a lexical veto. - When GameCult history, repo, or lore retrieval can answer the question, use the VoidBot MCP surfaces before raw repo spelunking. data_flow: @@ -75,7 +75,7 @@ landed_baseline: - Local and offsite backups, watchdogs, and the operations dashboard are live. - The stack now has a dedicated logon startup task path so bot and worker can come back after a reboot without waiting for manual resurrection. - Interaction memory and behavioral profiling are live. - - Source-grounding enforcement and tool-driven retrieval are live. + - Tool-driven retrieval is live, and source-grounding hints now bias the reply lanes instead of hard-gating them with a lexical pre-classifier. - The operations watchdog can now load ignored local extension checks, so adjacent-service fire signals can piggyback on the owner DM path without turning the public repo into cross-project gossip. - The owner Codex lane is now split into orchestration, runtime/parsing, shared helpers, and rendering modules instead of one swollen provider file. - The local Ollama lane is now split into orchestration, prompt/render helpers, tool-loop helpers, and shared types/constants instead of one swollen provider file.