Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

[rig-decomposition-bench] Daily rig decomposition benchmark — 2026-10-19 — no result (task picker failed)

オープン
#678 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
3/5
見積もり時間
1〜2日
初心者へのやさしさ
42/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
活発
技術スタック
github-actions, node.js, typescript

調査の方向性

Start with .github/skills/rig/bench.ts and the captured bench_output.json; the issue shows the task picker returned null with empty stderr. Check the skill’s dependency and Node version requirements, then trace how the picker call handles a null result and what repair() does. Done means the cause is identified and the workflow can produce a task and continue to its benchmark results.

索引モデルが issue の本文から書いたものです。

説明

ai-agent automation

Result: benchmark did not produce data

The run completed (exit 0) but the task-picker agent returned null, so the workflow returned early. The task, timings, solutions, attempts and grading are all empty. Nothing below is invented.

Captured stdout (bench_output.json):

{"status":"failed: task picker returned null","task":null,"attempts":[]}

Captured stderr: empty (0 bytes).

Notes

  • The skill dir had no node_modules, so the first --typecheck failed on every program (even a trivial one) with missing node:* and @types/node errors. I ran npm install in .github/skills/rig (8 packages) and the typecheck then passed. The skill needs its deps installed for this workflow to work without a checkout.
  • The runner's Node is v22.23.3, but the skill requires Node >=24 (EBADENGINE warning). This may be related to the failure, but I did not debug it, as instructed.
  • The picker used small, maxTurns: 2 and repair(). The cause of the null is unknown, because stderr was empty.
  • The program was run exactly once, as instructed.
single-call decomposed
duration (ms) n/a n/a
status n/a not run

Decomposition attempts: none (no task was picked).
Grading: none.

Benchmark program (bench.ts)
import { agent, workflow, s, repair, configureAgent, copilotEngine } from "rig";

configureAgent(copilotEngine());

const DEADLINE = Date.now() + 25 * 60_000;
const left = () => DEADLINE - Date.now();

const SKELETON = `import { agent, workflow, s } from "rig";

// Agent role: <role>
const analyze = agent({ model: "small", instructions: "...", output: s.string });

// Workflow role: combines the agents' outputs into the final solution
export default workflow({
  meta: { name: "decomposed-solution", description: "..." },
  body: async ({ call }) => {
    const analysis = await call(analyze, "...");
    return { solution: analysis ?? "" };
  },
});`;

const CONTRACT = `You write a self-contained TypeScript program for the "rig" library. Rules:
- Import ONLY from "rig" (agent, workflow, s). No other imports, no console.log.
- Split the task across AT LEAST TWO agents, each preceded by a "// Agent role: ..." comment. Each agent sets model: "small" for simple sub-steps, "medium" or "large" for harder ones.
- Coordinate them so the final answer combines the agents' outputs rather than one agent solving everything.
- Root must be exported with "export default" and must NOT be invoked. It takes no required input.
- A workflow() spec accepts only meta, an optional input, and body. It has NO output or execute field; the final { solution } object is simply what body returns. body receives { call } (also pipeline, parallel, phase, log); call(agent, input) returns the agent output or null. An agent() root instead declares output: s.object({ solution: s.string }).
- Use s.string as output for every agent whose result is source text or free-form solution. Do not make any agent re-emit a large solution inside an object wrapper.
- Required shape (abridged to one agent; you need at least two):
${SKELETON}
Return ONLY the complete TypeScript source, with no commentary.`;

// Agent role: pick a concrete benchmark task
const picker = agent({
  model: "small",
  maxTurns: 2,
  addons: [repair()],
  instructions: `Pick ONE concrete, complicated task a person or team might face in a single day that would naturally benefit from being split across sub-agents running different models: multi-source research and synthesis, a multi-file coding task with design/implementation/review phases, a structured report combining independent analyses, or a multi-step data transformation pipeline. It must be self-contained (solvable from its description alone, no external files or live web) and concrete enough to grade. Vary the domain; today is ${new Date().toISOString().slice(0, 10)} so pick something unusual. Return a title, domain, description, and 3-6 concrete checkable success criteria.`,
  output: s.object({
    title: s.string,
    domain: s.string,
    description: s.string,
    criteria: s.array(s.string),
  }),
});

// Agent role: solve the whole task in one turn
const solver = agent({
  model: "medium",
  maxTurns: 1,
  instructions: "Solve the whole task yourself in a single response, addressing every success criterion. No delegation, no tools. Return the complete solution.",
  output: s.string,
});

// Agent role: write or fix the decomposed rig program
const writer = agent({
  model: "medium",
  maxTurns: 1,
  instructions: CONTRACT,
  output: s.string,
});

// Agent role: grade both solutions
const grader = agent({
  model: "large",
  maxTurns: 1,
  instructions: "Score each solution 0-10 on how completely and correctly it satisfies the success criteria. Judge only correctness and completeness of content, not length and not which approach produced it. Pick winner single-call, decomposed, or tie, and explain the comparison.",
  output: s.object({
    singleScore: s.number,
    decomposedScore: s.number,
    winner: s.enum("single-call", "decomposed", "tie"),
    rationale: s.string,
  }),
});

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

function runCli(args: string[], input: string, timeoutMs: number): Promise<RunResult> {
  const cp = process.getBuiltinModule("node:child_process");
  return new Promise((resolve) => {
    let stdout = "";
    let stderr = "";
    let timedOut = false;
    const child = cp.spawn(process.execPath, ["rig.ts", ...args], { cwd: process.cwd(), stdio: ["pipe", "pipe", "pipe"] });
    const timer = setTimeout(() => { timedOut = true; child.kill("SIGKILL"); }, timeoutMs);
    child.stdout.on("data", (d: Buffer) => { stdout += d.toString(); });
    child.stderr.on("data", (d: Buffer) => { stderr += d.toString(); });
    child.on("error", (e: Error) => { stderr += String(e); });
    child.on("close", (code: number | null) => { clearTimeout(timer); resolve({ code, stdout, stderr, timedOut }); });
    child.stdin.on("error", () => {});
    child.stdin.end(input);
  });
}

type Attempt = { attempt: number; generation: string; typecheck: string; execute: string; error: string; note: string };

function stripFence(src: string): string {
  const m = src.match(/```(?:ts|typescript|rig)?\s*\n([\s\S]*?)```/);
  return (m ? m[1]! : src).trim();
}

// Workflow role: pick a task, solve it two ways, grade, and return the full report
export default workflow({
  meta: { name: "rig-decomposition-bench", description: "Compare single-call vs decomposed solving", phases: ["Pick", "Single", "Decompose", "Grade"] },
  body: async ({ call, phase, log }) => {
    phase("Pick");
    const task = await call(picker, "Pick the task now.", { timeout: Math.max(1000, Math.min(180_000, left())) });
    if (!task) return { status: "failed: task picker returned null", task: null, attempts: [] as Attempt[] };
    const taskText = `Title: ${task.title}\nDomain: ${task.domain}\nDescription: ${task.description}\nSuccess criteria:\n${task.criteria.map((c: string) => `- ${c}`).join("\n")}`;

    phase("Single");
    const t0 = Date.now();
    const single = await call(solver, taskText, { timeout: Math.max(1000, Math.min(480_000, left())) });
    const singleMs = Date.now() - t0;

    phase("Decompose");
    const d0 = Date.now();
    const attempts: Attempt[] = [];
    let source = "";
    let decomposed = "";
    let status = "fail";
    let prevSource = "";
    let prevError = "";
    for (let n = 1; n <= 2; n++) {
      if (left() < 120_000) { log("budget exhausted"); break; }
      let prompt: string;
      if (n === 1) prompt = `Write the program for this task:\n${taskText}`;
      else if (prevSource.trim()) prompt = `Fix the program below. Fix precisely what the error names and preserve what already worked.\n\nTask:\n${taskText}\n\nRequired shape:\n${SKELETON}\n\nSource:\n${prevSource}\n\nError:\n${prevError}`;
      else prompt = `There is no source to fix: the previous attempt produced nothing. Generate the complete program from scratch.\n\nTask:\n${taskText}\n\nRequired shape:\n${SKELETON}`;
      const raw = await call(writer, prompt, { timeout: Math.max(1000, Math.min(300_000, left())) });
      const src = raw ? stripFence(raw) : "";
      const rec: Attempt = { attempt: n, generation: "", typecheck: "skipped", execute: "skipped", error: "", note: "" };
      attempts.push(rec);
      if (!src.trim()) {
        rec.generation = "empty/null";
        rec.error = "writer returned null or whitespace-only source";
        prevSource = ""; prevError = rec.error;
        rec.note = n < 2 ? "Next attempt generates the program from scratch (no source to fix)." : "";
        continue;
      }
      rec.generation = "non-empty";
      source = src;
      prevSource = src;
      const tc = await runCli(["--typecheck"], src, Math.max(1000, Math.min(120_000, left())));
      if (tc.code !== 0) {
        rec.typecheck = tc.timedOut ? "timeout" : "fail";
        rec.error = `${tc.stdout}\n${tc.stderr}`.trim();
        prevError = rec.error;
        rec.note = n < 2 ? "Next attempt passes source and typecheck error to the fixer." : "";
        continue;
      }
      rec.typecheck = "pass";
      if (left() < 30_000) { rec.execute = "skipped (budget)"; break; }
      const ex = await runCli(["--server"], src, Math.max(1000, Math.min(300_000, left())));
      if (ex.code !== 0 || ex.timedOut) {
        rec.execute = ex.timedOut ? "timeout" : "fail";
        rec.error = `exit=${ex.code}\n${ex.stdout}\n${ex.stderr}`.trim();
        prevError = rec.error;
        rec.note = n < 2 ? "Next attempt passes source and execution error to the fixer." : "";
        continue;
      }
      let parsed: { solution?: unknown } | null = null;
      try { parsed = JSON.parse(ex.stdout); } catch { parsed = null; }
      if (parsed && typeof parsed.solution === "string" && parsed.solution.trim()) {
        rec.execute = "pass";
        decomposed = parsed.solution;
        status = "pass";
        break;
      }
      rec.execute = "fail";
      rec.error = `stdout was not JSON with non-empty solution:\n${ex.stdout}\n${ex.stderr}`.trim();
      prevError = rec.error;
      rec.note = n < 2 ? "Next attempt passes source and the output error to the fixer." : "";
    }
    const decomposedMs = Date.now() - d0;

    phase("Grade");
    const placeholder = "[Decomposition produced no solution: the generated program failed to produce output.]";
    let grade = null;
    if (left() > 20_000) {
      grade = await call(grader, `${taskText}\n\n=== SOLUTION A (single-call) ===\n${single ?? "[no solution]"}\n\n=== SOLUTION B (decomposed) ===\n${decomposed.trim() ? decomposed : placeholder}`, { timeout: Math.max(1000, Math.min(300_000, left())) });
    }
    return { task, singleMs, decomposedMs, singleSolution: single ?? "", decomposedSolution: decomposed, attempts, status, grade, decomposedSource: source };
  },
});

Verdict: no winner — the benchmark produced no data because the task picker returned null (empty stderr), so no solutions were generated or graded.

Generated by Daily Rig Decomposition Benchmark · copilot · auto · 34.9 AIC · ⌖ 0.679 AIC · ⊞ 10.1K · ◷

  • expires on Oct 26, 2026, 1:12 PM UTC
主要言語
TypeScript
スター
14
フォーク
0
平均マージ
9時間 49分
マージ済み PR(30日)
32

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

githubnext/rig のほかの issue

githubnext/rig の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。