Relax source grounding and pin owner Codex model
This commit is contained in:
parent
9978fd3ef5
commit
2c32b8df1f
16 changed files with 37 additions and 84 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -288,7 +288,7 @@ export function inferSourceGroundingHint(
|
|||
}
|
||||
|
||||
return {
|
||||
required: reasons.size > 0,
|
||||
required: false,
|
||||
reasons: [...reasons],
|
||||
matchedRepoNames: [...matchedRepoNames].sort(),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -149,6 +149,7 @@ export async function startBot(): Promise<void> {
|
|||
mode: config.ownerCodexMode,
|
||||
executable: config.codexExecutable,
|
||||
executableArgs: config.codexExecArgs,
|
||||
model: config.codexModel,
|
||||
reasoningEffort: config.codexModelReasoningEffort,
|
||||
timeoutMs: config.codexExecTimeoutMs,
|
||||
workingDirectory: process.cwd(),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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."}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue