Hacktoberfest 2026:維護者為十月標記出來的 issue,仍然開放、適合新手。 瀏覽 Hacktoberfest issue

[rig-decomposition-bench] Daily rig decomposition benchmark — 2026-10-13 — tie (both failed)

未關閉
#637 0 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

維護者通常 1 天內回覆

還沒有人認領這個 Issue。

評估

難度
4/5
預估耗時
3-5 天
新手友好度
12/100
Issue 類型
缺陷
描述清晰度
需要釐清
活躍度
活躍
技術堆疊
nodejs, typescript
領域
ai, devtools

研究方向

Start in bench.ts, where the decompositionWriter and decompositionFixer agents are defined, and check why both return an empty string; the captured outputs are reported as "no source produced". Then look at the workflow root contract the prompts describe and at how rig.ts is invoked through execFile. Done means the writer returns non-empty TypeScript that typechecks and runs, which the issue does not yet define clearly.

由索引模型根據 Issue 內容生成。

描述

ai-agent automation

Task

Title: Incident Postmortem: Multi-Region Checkout Outage
Domain: Software Engineering / Site Reliability

An e-commerce platform's checkout service experienced a 47-minute partial outage affecting 3 of 5 regions. The task supplies: (1) a timeline of 22 timestamped log/metric events spanning a deploy, a database connection-pool exhaustion, a cascading retry storm, and a rollback; (2) a snippet of the connection-pool configuration code that was changed in the deploy; (3) a customer-impact summary with order-failure counts per region. The task asks for a full postmortem produced by decomposing the work across sub-agents: one analyzing the timeline for a root-cause chain, one reviewing the code snippet for the defect and a fix, one quantifying customer/business impact, and one synthesizing all three into a polished postmortem document.

Success criteria:

  1. The root-cause sub-task produces a clear causal chain (deploy change -> connection pool misconfiguration -> exhaustion -> retry storm -> outage) with timestamps matching the given timeline
  2. The code-review sub-task identifies the exact defect (e.g., a pool size or timeout misconfiguration) and provides a corrected code snippet with an explanatory diff
  3. The impact-analysis sub-task computes total failed orders and estimated revenue loss consistently with the provided per-region numbers, with arithmetic that checks out
  4. The final synthesized postmortem includes all required sections (executive summary, root cause, impact, remediation items with owners/priority, prevention follow-ups) and contains no contradictions between sections
  5. At least 3 concrete, actionable remediation items are listed, each tied to a specific cause identified in the root-cause or code-review analysis
  6. The whole task is completed without requesting any external file, URL, or live data source beyond what is given in the prompt

Timing comparison

Approach Duration (ms) Final status
Single-call 70,226 produced empty output
Decomposed 61,930 fail

Decomposition attempts

# Source generated Typecheck Execute Note
1 No skipped skipped empty result; next attempt asked to generate from scratch
2 No skipped skipped empty result on final attempt
Attempt 1 — captured output ("no source produced")
no source produced
Attempt 2 — captured output ("no source produced")
no source produced

Both attempts to have the decompositionWriter/decompositionFixer agents produce a program failed — the agent returned no usable string content either time, so no typecheck or execute step ever ran.

Grading

  • Single-call score: 0 / 10
  • Decomposed score: 0 / 10
  • Winner: tie

Rationale (verbatim from grader):

Both solutions are explicit placeholders indicating no actual work was produced. Solution A (single-call) is empty, providing nothing against any of the six success criteria. Solution B (decomposed) explicitly states the decomposition did not produce a successful run, also providing nothing against any criteria. Neither solution contains a root-cause chain, code review/diff, impact quantification, synthesized postmortem, remediation items, or any evidence of task completion. Since both are complete non-attempts with no content to evaluate for correctness or completeness, they are equally deficient and tie at the lowest possible score.

Single-call solution

Single-call solution (verbatim)

The single-call solver (medium model, 1 turn, s.string output) returned an empty string. No solution content was produced to display.

Decomposed program source

The decomposition writer and fixer agents did not return usable TypeScript source on either of their two attempts, so there is no generated program source to show. This field is empty in the captured run output (generatedProgramSource: "").

Decomposed solution (verbatim)
(no solution: decomposition did not produce a successful run)

Benchmark program written for this run

bench.ts (the program written and executed in this run)
import { agent, configureAgent, copilotEngine, repair, s, workflow } from "rig";
import { execFile } from "node:child_process";

// Configure the Copilot engine in server (stdio) mode for this benchmark run.
configureAgent(copilotEngine({ server: true }));

const DEADLINE_MS = Date.now() + 25 * 60_000;
function remaining(): number {
  return DEADLINE_MS - Date.now();
}
function clamp(ms: number, max: number): number {
  return Math.max(0, Math.min(ms, max));
}

const RIG_CLI = `${process.cwd()}/rig.ts`;

type RunResult = { stdout: string; stderr: string; code: number | null; timedOut: boolean };

function runRigCli(args: string[], stdin: string, timeoutMs: number): Promise<RunResult> {
  return new Promise((resolve) => {
    if (timeoutMs <= 0) {
      resolve({ stdout: "", stderr: "skipped: no time budget remaining", code: null, timedOut: true });
      return;
    }
    const child = execFile(
      "node",
      [RIG_CLI, ...args],
      { cwd: process.cwd(), timeout: timeoutMs, maxBuffer: 1024 * 1024 * 50 },
      (error, stdout, stderr) => {
        const timedOut = !!error && (error as any).signal === "SIGTERM";
        resolve({ stdout, stderr, code: error ? (error as any).code ?? null : 0, timedOut });
      },
    );
    child.stdin?.write(stdin);
    child.stdin?.end();
  });
}

// Task picker schema
const TaskSchema = s.object({
  title: s.string,
  domain: s.string,
  description: s.string,
  successCriteria: s.nonEmptyArray(s.string),
});

// Agent role: pick a concrete, self-contained daily task suited to multi-agent decomposition.
const taskPicker = agent({
  name: "taskPicker",
  model: "small",
  maxTurns: 2,
  addons: repair(),
  instructions:
    "Pick one concrete, complicated task that a person or team might face in a single day, " +
    "which would naturally benefit from being split across sub-agents running different models " +
    "(e.g. multi-source research and synthesis, a multi-file coding task with distinct " +
    "design/implementation/review phases, a structured report combining several independent " +
    "analyses, or a multi-step data transformation pipeline). The task must be fully " +
    "self-contained and solvable purely from its own description, with no external file or " +
    "live web access required. Prefer variety across domains. Return a title, a domain, a " +
    "description, and 3-6 concrete, checkable success criteria.",
  output: TaskSchema,
});

// Agent role: solve the whole task alone in a single turn, addressing every success criterion.
const soloSolver = agent({
  name: "soloSolver",
  model: "medium",
  maxTurns: 1,
  instructions:
    "You are given a task description and its success criteria. Produce the complete solution " +
    "to the task by yourself in this single response, addressing every success criterion. " +
    "Do not delegate, do not call any tools. Return only the solution content (including " +
    "Markdown code fences if appropriate) -- do not wrap it in JSON or add commentary about " +
    "what you are doing.",
  output: s.string,
});

// Agent role: write a second, self-contained rig program that decomposes the task across
// at least two agents of varying model sizes, combining their outputs into a final solution.
const decompositionWriter = agent({
  name: "decompositionWriter",
  model: "medium",
  maxTurns: 1,
  instructions:
    "Write a complete, self-contained TypeScript program using the 'rig' agent framework " +
    "that solves the given task by splitting it across at least two agents, each with a " +
    "`// Agent role: ...` comment above it, choosing `small` for simple sub-steps and " +
    "`medium` or `large` for harder ones. The agents must be coordinated so the final answer " +
    "combines their outputs rather than one agent solving everything alone. The program must " +
    "import only from \"rig\" (never any other package), must take no required input on its " +
    "root, and must export its root via `export default` without invoking it.\n\n" +
    "You do not have access to the rig skill documentation, so follow this exact root " +
    "contract: a `workflow()` spec accepts only `meta`, an optional `input`, and `body`; it " +
    "has no `output` or `execute` field, and the final `{ solution }` object is simply what " +
    "`body` returns. An `agent()` root instead declares " +
    "`output: s.object({ solution: s.string })`. Use `s.string` for every agent whose result " +
    "is source text or another free-form solution; reserve object schemas for structured " +
    "metadata, and never make a solver re-emit a large solution inside a wrapper object.\n\n" +
    "Here is the required skeleton shape (abridged to one agent -- your program must still " +
    "define at least two agents):\n\n" +
    "```ts\n" +
    "import { agent, workflow, s } from \"rig\";\n\n" +
    "// Agent role: <role>\n" +
    "const analyze = agent({ model: \"small\", instructions: \"...\", output: s.string });\n\n" +
    "// Workflow role: combines the agents' outputs into the final solution\n" +
    "export default workflow({\n" +
    "  meta: { name: \"decomposed-solution\", description: \"...\" },\n" +
    "  body: async ({ call }) => {\n" +
    "    const analysis = await call(analyze, \"...\");\n" +
    "    return { solution: analysis ?? \"\" };\n" +
    "  },\n" +
    "});\n" +
    "```\n\n" +
    "Return ONLY the raw TypeScript source code of the program, with no Markdown code fences " +
    "and no commentary before or after it.",
  output: s.string,
});

// Agent role: fix a previously generated decomposition program given its exact captured error.
const decompositionFixer = agent({
  name: "decompositionFixer",
  model: "medium",
  maxTurns: 1,
  instructions:
    "You previously wrote (or are now writing for the first time) a self-contained TypeScript " +
    "program using the 'rig' agent framework to solve a given task by splitting it across at " +
    "least two agents, each with a `// Agent role: ...` comment above it, choosing `small` for " +
    "simple sub-steps and `medium` or `large` for harder ones, coordinated so the final answer " +
    "combines their outputs rather than one agent solving everything. The program must import " +
    "only from \"rig\", must take no required input on its root, and must export its root via " +
    "`export default` without invoking it.\n\n" +
    "Required root contract: a `workflow()` spec accepts only `meta`, an optional `input`, and " +
    "`body`; it has no `output` or `execute` field, and the final `{ solution }` object is " +
    "simply what `body` returns. An `agent()` root instead declares " +
    "`output: s.object({ solution: s.string })`. Use `s.string` for every agent whose result " +
    "is source text or another free-form solution; reserve object schemas for structured " +
    "metadata, and never make a solver re-emit a large solution inside a wrapper object.\n\n" +
    "Required skeleton shape (abridged to one agent -- the full program must still define at " +
    "least two agents):\n\n" +
    "```ts\n" +
    "import { agent, workflow, s } from \"rig\";\n\n" +
    "// Agent role: <role>\n" +
    "const analyze = agent({ model: \"small\", instructions: \"...\", output: s.string });\n\n" +
    "// Workflow role: combines the agents' outputs into the final solution\n" +
    "export default workflow({\n" +
    "  meta: { name: \"decomposed-solution\", description: \"...\" },\n" +
    "  body: async ({ call }) => {\n" +
    "    const analysis = await call(analyze, \"...\");\n" +
    "    return { solution: analysis ?? \"\" };\n" +
    "  },\n" +
    "});\n" +
    "```\n\n" +
    "If prior source is provided below, fix precisely what the captured error names while " +
    "preserving everything that already worked -- do not invent unrelated API edits. If no " +
    "prior source is provided, generate the complete program from scratch using the task and " +
    "skeleton above.\n\n" +
    "Return ONLY the raw TypeScript source code of the program, with no Markdown code fences " +
    "and no commentary before or after it.",
  output: s.string,
});

// Agent role: grade both solutions for correctness/completeness only, and pick a winner.
const grader = agent({
  name: "grader",
  model: "large",
  maxTurns: 1,
  instructions:
    "You are given a task with success criteria, and two candidate solutions: 'single-call' " +
    "and 'decomposed'. Score each solution from 0 to 10 on how completely and correctly it " +
    "satisfies the success criteria. Judge ONLY correctness and completeness of content -- " +
    "never on length, verbosity, or which approach (single-call vs decomposed) produced it. " +
    "If a solution is an explicit placeholder stating that no solution was produced, score it " +
    "low and explain why. Pick a winner: 'single-call', 'decomposed', or 'tie'. Explain your " +
    "comparison rationale referencing the success criteria.",
  output: s.object({
    singleCallScore: s.number,
    decomposedScore: s.number,
    winner: s.enum("single-call", "decomposed", "tie"),
    rationale: s.string,
  }),
});

function stripFences(src: string): string {
  const trimmed = src.trim();
  const fenceMatch = trimmed.match(/^```(?:ts|typescript)?\n([\s\S]*?)\n```$/);
  return fenceMatch ? fenceMatch[1] : trimmed;
}

type AttemptRecord = {
  attempt: number;
  sourceGenerated: boolean;
  sourceLength: number;
  typecheck: { status: "passed" | "failed" | "skipped"; output: string };
  execute: { status: "passed" | "failed" | "skipped"; output: string };
  note: string;
};

// Workflow role: orchestrate the rig decomposition benchmark end to end within a wall-clock budget.
export default workflow({
  meta: {
    name: "rig-decomposition-bench",
    description: "Compare a single-call solve against a multi-agent decomposed solve for one generated task.",
  },
  body: async ({ call }) => {
    const attempts: AttemptRecord[] = [];
    let decomposedSource = "";
    let decomposedSolution = "";
    let decomposedStatus: "pass" | "fail" = "fail";

    // Step 1: pick a task.
    const task = remaining() > 30_000
      ? await call(taskPicker, "Pick the task now.")
      : null;

    if (!task) {
      return {
        task: null,
        singleCallDurationMs: 0,
        decomposedDurationMs: 0,
        singleCallSolution: "",
        decomposedSolution: "",
        decomposedStatus: "fail" as const,
        attempts,
        grading: null,
        generatedProgramSource: "",
        failureReason: "Task picker failed or ran out of time budget before starting.",
      };
    }

    const taskPrompt =
      `Task: ${task.title}\nDomain: ${task.domain}\nDescription: ${task.description}\n` +
      `Success criteria:\n${task.successCriteria.map((c, i) => `${i + 1}. ${c}`).join("\n")}`;

    // Step 2: solve it in one call.
    const singleCallStart = Date.now();
    const singleCallSolution = remaining() > 30_000
      ? (await call(soloSolver, taskPrompt)) ?? ""
      : "";
    const singleCallDurationMs = Date.now() - singleCallStart;

    // Step 3: solve it by decomposition.
    const decompStart = Date.now();
    let priorSource: string | null = null;
    let priorError: string | null = null;

    for (let attemptNum = 1; attemptNum <= 2; attemptNum++) {
      if (remaining() < 60_000) {
        attempts.push({
          attempt: attemptNum,
          sourceGenerated: false,
          sourceLength: 0,
          typecheck: { status: "skipped", output: "insufficient time budget remaining" },
          execute: { status: "skipped", output: "insufficient time budget remaining" },
          note: "skipped: not enough wall-clock budget left",
        });
        break;
      }

      let rawSource: string | null;
      if (attemptNum === 1) {
        rawSource = await call(decompositionWriter, taskPrompt);
      } else {
        const fixerPrompt: string = priorSource
          ? `${taskPrompt}\n\nPrior generated source:\n${priorSource}\n\nCaptured error to fix:\n${priorError}`
          : `${taskPrompt}\n\nThere is no prior source (the first attempt produced nothing). Generate the complete program from scratch.`;
        rawSource = await call(decompositionFixer, fixerPrompt);
      }

      const sourceGenerated: boolean = !!rawSource && rawSource.trim().length > 0;
      const source: string = sourceGenerated ? stripFences(rawSource as string) : "";

      if (!sourceGenerated) {
        attempts.push({
          attempt: attemptNum,
          sourceGenerated: false,
          sourceLength: 0,
          typecheck: { status: "skipped", output: "no source produced" },
          execute: { status: "skipped", output: "no source produced" },
          note: attemptNum === 1 ? "empty result; next attempt asked to generate from scratch" : "empty result on final attempt",
        });
        priorSource = null;
        priorError = "No source was generated by the writer/fixer.";
        continue;
      }

      // Typecheck
      const typecheckTimeout = clamp(remaining() - 30_000, 2 * 60_000);
      const tc = await runRigCli(["--typecheck"], source, typecheckTimeout);
      const typecheckOutput = `${tc.stdout}\n${tc.stderr}`.trim();
      const typecheckPassed = tc.code === 0 && /typecheck passed/i.test(typecheckOutput);

      if (!typecheckPassed) {
        attempts.push({
          attempt: attemptNum,
          sourceGenerated: true,
          sourceLength: source.length,
          typecheck: { status: tc.timedOut ? "skipped" : "failed", output: typecheckOutput || "(no output)" },
          execute: { status: "skipped", output: "skipped: typecheck did not pass" },
          note: attemptNum === 1 ? "typecheck failed; next attempt asked to fix the exact error" : "typecheck failed on final attempt",
        });
        priorSource = source;
        priorError = typecheckOutput;
        continue;
      }

      // Execute
      const executeTimeout = clamp(remaining() - 10_000, 5 * 60_000);
      const ex = await runRigCli(["--server"], source, executeTimeout);
      const executeOutput = `${ex.stdout}\n${ex.stderr}`.trim();
      let parsedSolution: string | null = null;
      try {
        const parsed = JSON.parse(ex.stdout.trim());
        if (typeof parsed === "string") {
          parsedSolution = parsed;
        } else if (parsed && typeof parsed === "object" && typeof (parsed as any).solution === "string") {
          parsedSolution = (parsed as any).solution;
        } else if (parsed && typeof parsed === "object" && typeof (parsed as any).text === "string") {
          parsedSolution = (parsed as any).text;
        }
      } catch {
        if (ex.code === 0 && ex.stdout.trim().length > 0) {
          parsedSolution = ex.stdout.trim();
        }
      }

      const executePassed = ex.code === 0 && !ex.timedOut && parsedSolution !== null;

      attempts.push({
        attempt: attemptNum,
        sourceGenerated: true,
        sourceLength: source.length,
        typecheck: { status: "passed", output: typecheckOutput || "(no output)" },
        execute: { status: executePassed ? "passed" : "failed", output: executeOutput || "(no output)" },
        note: executePassed
          ? "succeeded"
          : attemptNum === 1
          ? "execute failed; next attempt asked to fix the exact error"
          : "execute failed on final attempt",
      });

      if (executePassed) {
        decomposedSource = source;
        decomposedSolution = parsedSolution ?? "";
        decomposedStatus = "pass";
        break;
      } else {
        priorSource = source;
        priorError = executeOutput;
      }
    }

    if (decomposedStatus !== "pass" && attempts.length > 0) {
      const last = attempts[attempts.length - 1];
      if (last.sourceGenerated && last.typecheck.status === "passed") {
        decomposedSource = decomposedSource || "";
      }
    }

    const decomposedDurationMs = Date.now() - decompStart;

    const finalDecomposedSolution = decomposedStatus === "pass"
      ? decomposedSolution
      : "(no solution: decomposition did not produce a successful run)";

    // Step 4: grade both.
    const grading = remaining() > 30_000
      ? await call(
          grader,
          `${taskPrompt}\n\nSolution A (single-call):\n${singleCallSolution || "(empty)"}\n\n` +
            `Solution B (decomposed):\n${finalDecomposedSolution}`,
        )
      : null;

    return {
      task,
      singleCallDurationMs,
      decomposedDurationMs,
      singleCallSolution,
      decomposedSolution: finalDecomposedSolution,
      decomposedStatus,
      attempts,
      grading,
      generatedProgramSource: decomposedSource,
    };
  },
});

Verdict

Winner: tie (both failed). Neither the single-call solver nor the decomposition pipeline produced any usable content in this run — the single-call medium/1-turn/s.string agent returned an empty string, and both decomposition-writer/fixer attempts likewise returned no source to typecheck or execute, so this run surfaces a reliability gap in getting a plain-string-output agent call to return non-empty content under the current engine/session rather than any meaningful comparison between the two orchestration strategies.

Generated by Daily Rig Decomposition Benchmark · copilot · auto · 120.8 AIC · ⌖ 17.8 AIC · ⊞ 9.9K · ◷

  • expires on Oct 20, 2026, 1:22 PM UTC
主要語言
TypeScript
星號
14
分支
0
平均合併
3 小時 38 分鐘
30 天內合併 PR
29

環境準備

這個專案沒有提供開發容器、Dockerfile 或貢獻指南,環境需要你自己搭建:先看它的 README,通用步驟見我們的新手貢獻指南。

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

githubnext/rig 的其他 Issue

查看 githubnext/rig 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。