From 005282946d8692f17c85331f0c044e49e48bed5f Mon Sep 17 00:00:00 2001 From: Lim Yu Xi Date: Fri, 10 Apr 2026 10:46:53 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20grid=20override=20file=20=E2=80=94=20.c?= =?UTF-8?q?odedb/model=5Fgrid.toml=20for=20per-role=20overrides=20(#269)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds TOML-based model override system to runtime/grid.zig: - parseOverrides(): parses minimal TOML format with [role] sections, model = "tier" and rationale = "reason" keys. Strips quotes, skips comments, validates tier names. - loadOverrides(): lazy-loads from .codedb/model_grid.toml with caching - overrideTierForRole(): looks up override tier for a role - resolveModel() updated: override file → grid → mode → fallback Override files can be hand-written or generated by evolve_agent. Supports up to 32 role overrides. 7 new tests covering: basic TOML parsing, invalid tier skipping, empty content, rationale ordering, quote stripping, unquoted values, and existing resolveModel behavior preservation. All 14 grid tests pass (4 existing + 3 types + 7 new). Made-with: Cursor --- src/runtime/grid.zig | 187 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 184 insertions(+), 3 deletions(-) diff --git a/src/runtime/grid.zig b/src/runtime/grid.zig index 2d16ca0..bcf34d5 100644 --- a/src/runtime/grid.zig +++ b/src/runtime/grid.zig @@ -84,16 +84,124 @@ pub fn tierForRole(role: []const u8) ?ModelTier { return null; } +// ── Grid override file (.codedb/model_grid.toml) ───────────────────────────── + +const MAX_OVERRIDES = 32; +const OVERRIDE_PATH = ".codedb/model_grid.toml"; + +pub const GridOverride = struct { + role: []const u8, + tier: ModelTier, + rationale: []const u8, +}; + +var g_overrides: [MAX_OVERRIDES]GridOverride = undefined; +var g_override_count: usize = 0; +var g_overrides_loaded: bool = false; + +/// Parse a minimal TOML-like override file. Supports: +/// [role_name] +/// model = "tier_name" +/// rationale = "reason" +pub fn parseOverrides(content: []const u8) struct { overrides: [MAX_OVERRIDES]GridOverride, count: usize } { + var result: [MAX_OVERRIDES]GridOverride = undefined; + var count: usize = 0; + var current_role: ?[]const u8 = null; + var current_rationale: []const u8 = ""; + + var lines = std.mem.splitScalar(u8, content, '\n'); + while (lines.next()) |raw_line| { + const line = std.mem.trim(u8, raw_line, " \t\r"); + if (line.len == 0 or line[0] == '#') continue; + + // Section header: [role_name] + if (line[0] == '[' and line[line.len - 1] == ']') { + current_role = line[1 .. line.len - 1]; + current_rationale = ""; + continue; + } + + if (current_role == null) continue; + + // Key = "value" + if (std.mem.indexOf(u8, line, "=")) |eq| { + const key = std.mem.trim(u8, line[0..eq], " \t"); + const raw_val = std.mem.trim(u8, line[eq + 1 ..], " \t"); + // Strip surrounding quotes + const val = if (raw_val.len >= 2 and raw_val[0] == '"' and raw_val[raw_val.len - 1] == '"') + raw_val[1 .. raw_val.len - 1] + else + raw_val; + + if (std.mem.eql(u8, key, "model")) { + if (ModelTier.fromString(val)) |tier| { + if (count < MAX_OVERRIDES) { + result[count] = .{ + .role = current_role.?, + .tier = tier, + .rationale = current_rationale, + }; + count += 1; + } + } + } else if (std.mem.eql(u8, key, "rationale")) { + current_rationale = val; + // Update the last entry's rationale if it matches this role + if (count > 0 and std.mem.eql(u8, result[count - 1].role, current_role.?)) { + result[count - 1].rationale = val; + } + } + } + } + + return .{ .overrides = result, .count = count }; +} + +/// Load overrides from the file system. Caches after first load. +pub fn loadOverrides() void { + if (g_overrides_loaded) return; + g_overrides_loaded = true; + + const file = std.fs.cwd().openFile(OVERRIDE_PATH, .{}) catch return; + defer file.close(); + + var buf: [8192]u8 = undefined; + const len = file.readAll(&buf) catch return; + + const parsed = parseOverrides(buf[0..len]); + g_overrides = parsed.overrides; + g_override_count = parsed.count; +} + +/// Reset cached overrides (for testing). +pub fn resetOverrides() void { + g_overrides_loaded = false; + g_override_count = 0; +} + +/// Look up override tier for a role. +pub fn overrideTierForRole(role: []const u8) ?ModelTier { + loadOverrides(); + for (g_overrides[0..g_override_count]) |ov| { + if (std.mem.eql(u8, ov.role, role)) return ov.tier; + } + return null; +} + /// Resolve the model ID for a given role + mode combination. -/// Priority: role grid → mode default → fallback (Sonnet). +/// Priority: override file → role grid → mode default → fallback (Sonnet). pub fn resolveModel(role: ?[]const u8, mode: AgentMode) []const u8 { - // 1. Check grid for role-specific tier if (role) |r| { + // 1. Check override file (highest priority) + if (overrideTierForRole(r)) |tier| { + return tier.toModelId(); + } + // 2. Check grid for role-specific tier if (tierForRole(r)) |tier| { return tier.toModelId(); } } - // 2. Fall back to mode default + // 3. Fall back to mode default return mode.defaultModel(); } @@ -123,3 +231,76 @@ test "grid: ModelTier round-trips" { try std.testing.expectEqual(ModelTier.opus, ModelTier.fromString("opus").?); try std.testing.expectEqual(@as(?ModelTier, null), ModelTier.fromString("gpt4")); } + +// ── Grid override tests (#269) ────────────────────────────────────────────── + +test "grid: parseOverrides basic TOML" { + const toml = + \\# Generated by evolve_agent + \\[orchestrator] + \\model = "sonnet" + \\rationale = "Haiku failed to decompose complex tasks" + \\ + \\[mutator] + \\model = "opus" + \\rationale = "Opus produces higher fitness organisms" + ; + const result = parseOverrides(toml); + try std.testing.expectEqual(@as(usize, 2), result.count); + try std.testing.expectEqualStrings("orchestrator", result.overrides[0].role); + try std.testing.expectEqual(ModelTier.sonnet, result.overrides[0].tier); + try std.testing.expectEqualStrings("mutator", result.overrides[1].role); + try std.testing.expectEqual(ModelTier.opus, result.overrides[1].tier); +} + +test "grid: parseOverrides skips invalid tier" { + const toml = + \\[reviewer] + \\model = "nonexistent" + ; + const result = parseOverrides(toml); + try std.testing.expectEqual(@as(usize, 0), result.count); +} + +test "grid: parseOverrides handles empty content" { + const result = parseOverrides(""); + try std.testing.expectEqual(@as(usize, 0), result.count); +} + +test "grid: parseOverrides rationale before model" { + const toml = + \\[fixer] + \\rationale = "Better at fixing" + \\model = "haiku" + ; + const result = parseOverrides(toml); + try std.testing.expectEqual(@as(usize, 1), result.count); + try std.testing.expectEqual(ModelTier.haiku, result.overrides[0].tier); +} + +test "grid: parseOverrides strips quotes" { + const toml = + \\[finder] + \\model = "sonnet" + ; + const result = parseOverrides(toml); + try std.testing.expectEqual(@as(usize, 1), result.count); + try std.testing.expectEqual(ModelTier.sonnet, result.overrides[0].tier); +} + +test "grid: parseOverrides no quotes works too" { + const toml = + \\[finder] + \\model = opus + ; + const result = parseOverrides(toml); + try std.testing.expectEqual(@as(usize, 1), result.count); + try std.testing.expectEqual(ModelTier.opus, result.overrides[0].tier); +} + +test "grid: resolveModel existing tests still pass with override reset" { + resetOverrides(); + try std.testing.expectEqualStrings("gpt-5.4", resolveModel("orchestrator", .smart)); + try std.testing.expectEqualStrings("claude-sonnet-4-6", resolveModel("finder", .rush)); + resetOverrides(); +}