From 606545734452084910ed8d8d0b3da4f6ce93e719 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2026 14:47:52 +0000 Subject: [PATCH] =?UTF-8?q?Add=2010=20novel=20rig=20samples=20=E2=80=94=20?= =?UTF-8?q?2026-10-13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Theme seed: 649507a8347906fbdfc3cd73c78f23c0 New samples (551-560) covering nested-catalog rollups with partial observations, tool-call budget exhaustion, linked-record chain budget walks, multi-source conflict reconciliation with tie-break rules, foreign-key joins across linked tables, asymmetric source-priority reconciliation, parallel timeline conflict flagging, nested budget allocation with subtree cutoff, multi-locale manifest resolution, and a two-phase recency-reconciliation workflow with audit-trail output. Refreshed skills/rig/samples.md catalog. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- skills/rig/samples.md | 23 +++++-- .../551-nested-catalog-partial-rollup.md | 65 ++++++++++++++++++ .../552-resource-schedule-partial-lookup.md | 63 ++++++++++++++++++ .../553-linked-record-chain-budget-walk.md | 66 +++++++++++++++++++ ...-conflicting-source-timeline-reconciler.md | 63 ++++++++++++++++++ .../555-linked-record-foreign-key-join.md | 56 ++++++++++++++++ ...-measurement-source-priority-reconciler.md | 41 ++++++++++++ .../557-parallel-timeline-conflict-flagger.md | 64 ++++++++++++++++++ .../558-nested-catalog-budget-allocator.md | 26 ++++++++ ...559-multilingual-linked-record-manifest.md | 62 +++++++++++++++++ ...nked-record-recency-reconciler-workflow.md | 63 ++++++++++++++++++ 11 files changed, 588 insertions(+), 4 deletions(-) create mode 100644 skills/rig/samples/551-nested-catalog-partial-rollup.md create mode 100644 skills/rig/samples/552-resource-schedule-partial-lookup.md create mode 100644 skills/rig/samples/553-linked-record-chain-budget-walk.md create mode 100644 skills/rig/samples/554-conflicting-source-timeline-reconciler.md create mode 100644 skills/rig/samples/555-linked-record-foreign-key-join.md create mode 100644 skills/rig/samples/556-tabular-measurement-source-priority-reconciler.md create mode 100644 skills/rig/samples/557-parallel-timeline-conflict-flagger.md create mode 100644 skills/rig/samples/558-nested-catalog-budget-allocator.md create mode 100644 skills/rig/samples/559-multilingual-linked-record-manifest.md create mode 100644 skills/rig/samples/560-linked-record-recency-reconciler-workflow.md diff --git a/skills/rig/samples.md b/skills/rig/samples.md index be2276b..6774e1a 100644 --- a/skills/rig/samples.md +++ b/skills/rig/samples.md @@ -2,7 +2,7 @@ Generated by `npm run sample:catalog`; do not edit the inventory by hand. -690 samples: 632 Markdown programs and 58 TypeScript fixtures. +700 samples: 642 Markdown programs and 58 TypeScript fixtures. Every sample has one primary **pattern bucket**, a **topic cluster**, and a normalized task **family**. Buckets come from program syntax, not filenames. A workflow with tools stays in workflows; a coordinator with tools stays in delegation. Markdown and TypeScript versions are kept separately because they exercise different launcher contracts; provider integration fixtures are also intentional. @@ -13,11 +13,11 @@ Legacy filenames are stable identifiers, not an authoritative description of the | Bucket | Samples | Read when | |--------|---------|-----------| | [Schema-first agents](#schemas) | 61 | Typed caller inputs and constrained outputs without workspace intents. | -| [Prompt context and side effects](#context) | 70 | Reading or discovering workspace context and declaring output side effects. | +| [Prompt context and side effects](#context) | 73 | Reading or discovering workspace context and declaring output side effects. | | [Runtime and addons](#runtime) | 43 | Invocation options, engines, timeouts, repair, steering, and addon hooks. | -| [Custom tools](#tools) | 419 | Model-callable deterministic operations, parsing, and Node.js APIs. | +| [Custom tools](#tools) | 423 | Model-callable deterministic operations, parsing, and Node.js APIs. | | [Model-driven delegation](#delegation) | 56 | Named specialists coordinated by a model-driven root agent. | -| [Deterministic workflows](#workflows) | 41 | Explicit sequencing, bounded fan-out, pipelines, budgets, and nested workflows. | +| [Deterministic workflows](#workflows) | 44 | Explicit sequencing, bounded fan-out, pipelines, budgets, and nested workflows. | Use `node scripts/sample-catalog.ts --json` for the complete inventory with role comments, task families, and exercised APIs. For a focused search, append `--family ` or `--bucket `; bucket keys are `schemas`, `context`, `runtime`, `tools`, `delegation`, and `workflows`. @@ -167,6 +167,7 @@ For a focused search, append `--family ` or `--bucket `; bucket keys | genaiscript-list-files-port | [61-genaiscript-list-files-port.md](samples/61-genaiscript-list-files-port.md) | | genaiscript-todo-port | [62-genaiscript-todo-port.md](samples/62-genaiscript-todo-port.md) | | line-count-report-writer | [537-line-count-report-writer.md](samples/537-line-count-report-writer.md) | +| nested-catalog-budget-allocator | [558-nested-catalog-budget-allocator.md](samples/558-nested-catalog-budget-allocator.md) | | prompt-intents | [47-prompt-intents.ts](../../src/samples/47-prompt-intents.ts) | | readonly-investigator | [34-intent-options.ts](../../src/samples/34-intent-options.ts) | | species-observation-digest | [544-species-observation-digest.md](samples/544-species-observation-digest.md) | @@ -192,7 +193,9 @@ For a focused search, append `--family ` or `--bucket `; bucket keys | Family | Samples | |--------|---------| +| conflicting-source-timeline-reconciler | [554-conflicting-source-timeline-reconciler.md](samples/554-conflicting-source-timeline-reconciler.md) | | list-source-files | [06-list-source-files.md](samples/06-list-source-files.md) | +| tabular-measurement-source-priority-reconciler | [556-tabular-measurement-source-priority-reconciler.md](samples/556-tabular-measurement-source-priority-reconciler.md) | | ts-export-reporter | [438-ts-export-reporter.md](samples/438-ts-export-reporter.md) | @@ -349,12 +352,14 @@ For a focused search, append `--family ` or `--bucket `; bucket keys | json-schema-structure-validator | [329-json-schema-structure-validator.md](samples/329-json-schema-structure-validator.md), [402-json-schema-structure-validator.md](samples/402-json-schema-structure-validator.md) | | json-schema-validator | [396-json-schema-validator.md](samples/396-json-schema-validator.md) | | jsonl-file-analyzer | [379-jsonl-file-analyzer.md](samples/379-jsonl-file-analyzer.md), [431-jsonl-file-analyzer.md](samples/431-jsonl-file-analyzer.md), [435-jsonl-file-analyzer.md](samples/435-jsonl-file-analyzer.md) | +| linked-record-foreign-key-join | [555-linked-record-foreign-key-join.md](samples/555-linked-record-foreign-key-join.md) | | markdown-frontmatter-checker | [139-markdown-frontmatter-checker.md](samples/139-markdown-frontmatter-checker.md) | | markdown-frontmatter-extractor | [337-markdown-frontmatter-extractor.md](samples/337-markdown-frontmatter-extractor.md), [403-markdown-frontmatter-extractor.md](samples/403-markdown-frontmatter-extractor.md), [440-markdown-frontmatter-extractor.md](samples/440-markdown-frontmatter-extractor.md) | | markdown-heading-validator | [289-markdown-heading-validator.md](samples/289-markdown-heading-validator.md), [376-markdown-heading-validator.md](samples/376-markdown-heading-validator.md) | | markdown-link-checker | [129-markdown-link-checker.md](samples/129-markdown-link-checker.md), [242-markdown-link-checker.md](samples/242-markdown-link-checker.md) | | markdown-table-writer | [438-markdown-table-writer.md](samples/438-markdown-table-writer.md) | | markdown-toc-generator | [316-markdown-toc-generator.md](samples/316-markdown-toc-generator.md) | +| multilingual-linked-record-manifest | [559-multilingual-linked-record-manifest.md](samples/559-multilingual-linked-record-manifest.md) | | proto-field-extractor | [189-proto-field-extractor.md](samples/189-proto-field-extractor.md), [312-proto-field-extractor.md](samples/312-proto-field-extractor.md) | | readme-badge-analyzer | [350-readme-badge-analyzer.md](samples/350-readme-badge-analyzer.md), [412-readme-badge-analyzer.md](samples/412-readme-badge-analyzer.md), [416-readme-badge-analyzer-v2.md](samples/416-readme-badge-analyzer-v2.md) | | workspace-symlink-inventory | [448-workspace-symlink-inventory.md](samples/448-workspace-symlink-inventory.md), [458-workspace-symlink-inventory.md](samples/458-workspace-symlink-inventory.md) | @@ -437,6 +442,7 @@ For a focused search, append `--family ` or `--bucket `; bucket keys | makefile-phony-extractor | [348-makefile-phony-extractor.md](samples/348-makefile-phony-extractor.md), [414-makefile-phony-extractor-v2.md](samples/414-makefile-phony-extractor-v2.md) | | makefile-target-extractor | [167-makefile-target-extractor.md](samples/167-makefile-target-extractor.md), [284-makefile-target-extractor.md](samples/284-makefile-target-extractor.md), [294-makefile-target-extractor.md](samples/294-makefile-target-extractor.md) | | monorepo-workspace-lister | [249-monorepo-workspace-lister.md](samples/249-monorepo-workspace-lister.md), [356-monorepo-workspace-lister-v2.md](samples/356-monorepo-workspace-lister-v2.md), [364-monorepo-workspace-lister-v3.md](samples/364-monorepo-workspace-lister-v3.md) | +| nested-catalog-partial-rollup | [551-nested-catalog-partial-rollup.md](samples/551-nested-catalog-partial-rollup.md) | | node-module-path-resolver | [477-node-module-path-resolver.md](samples/477-node-module-path-resolver.md) | | openapi-spec-validator | [161-openapi-spec-validator.md](samples/161-openapi-spec-validator.md) | | os-path-structure-analyzer | [415-os-path-structure-analyzer.md](samples/415-os-path-structure-analyzer.md) | @@ -489,6 +495,7 @@ For a focused search, append `--family ` or `--bucket `; bucket keys | json-schema-type-distribution | [500-json-schema-type-distribution.md](samples/500-json-schema-type-distribution.md) | | json-union-type-inferrer | [440-json-union-type-inferrer.md](samples/440-json-union-type-inferrer.md) | | markdown-link-classifier | [428-markdown-link-classifier.md](samples/428-markdown-link-classifier.md) | +| resource-schedule-partial-lookup | [552-resource-schedule-partial-lookup.md](samples/552-resource-schedule-partial-lookup.md) | | source-comment-density-reporter | [350-source-comment-density-reporter.md](samples/350-source-comment-density-reporter.md), [416-source-comment-density-reporter.md](samples/416-source-comment-density-reporter.md) | | source-complexity-heatmap | [199-source-complexity-heatmap.md](samples/199-source-complexity-heatmap.md) | | source-file-checksum-reporter | [478-source-file-checksum-reporter.md](samples/478-source-file-checksum-reporter.md) | @@ -626,6 +633,13 @@ For a focused search, append `--family ` or `--bucket `; bucket keys | npm-script-dependency-workflow | [420-npm-script-dependency-workflow.md](samples/420-npm-script-dependency-workflow.md) | | pkg-scripts-trio-workflow | [399-pkg-scripts-trio-workflow.md](samples/399-pkg-scripts-trio-workflow.md), [461-pkg-scripts-trio-workflow.md](samples/461-pkg-scripts-trio-workflow.md) | +### Docs and structured data + +| Family | Samples | +|--------|---------| +| linked-record-chain-budget-walk | [553-linked-record-chain-budget-walk.md](samples/553-linked-record-chain-budget-walk.md) | +| linked-record-recency-reconciler-workflow | [560-linked-record-recency-reconciler-workflow.md](samples/560-linked-record-recency-reconciler-workflow.md) | + ### Git history and workspace | Family | Samples | @@ -652,6 +666,7 @@ For a focused search, append `--family ` or `--bucket `; bucket keys | parallel-dep-audit-workflow | [488-parallel-dep-audit-workflow.md](samples/488-parallel-dep-audit-workflow.md) | | parallel-multi-tool-workflow | [390-parallel-multi-tool-workflow.md](samples/390-parallel-multi-tool-workflow.md), [444-parallel-multi-tool-workflow.md](samples/444-parallel-multi-tool-workflow.md), [454-parallel-multi-tool-workflow.md](samples/454-parallel-multi-tool-workflow.md) | | parallel-project-health | [439-parallel-project-health.md](samples/439-parallel-project-health.md) | +| parallel-timeline-conflict-flagger | [557-parallel-timeline-conflict-flagger.md](samples/557-parallel-timeline-conflict-flagger.md) | | parallel-wordfreq-aggregator | [529-parallel-wordfreq-aggregator.md](samples/529-parallel-wordfreq-aggregator.md) | | todo-fixme-parallel-workflow | [539-todo-fixme-parallel-workflow.md](samples/539-todo-fixme-parallel-workflow.md) | | workflow-audit-verify | [310-workflow-audit-verify.md](samples/310-workflow-audit-verify.md) | diff --git a/skills/rig/samples/551-nested-catalog-partial-rollup.md b/skills/rig/samples/551-nested-catalog-partial-rollup.md new file mode 100644 index 0000000..c8eaeb5 --- /dev/null +++ b/skills/rig/samples/551-nested-catalog-partial-rollup.md @@ -0,0 +1,65 @@ +# 551 - Nested Catalog Partial Rollup + +```rig +import { agent, defineTool, p, s } from "rig"; + +// Tool: recursively roll up completion stats from a nested catalog, counting +// only "observed" lessons; subtrees with none observed stay out of the +// denominator (missing data) instead of counting as zero progress. +const rollupCatalog = defineTool("rollup_catalog", { + description: "Recursively compute per-program completion percentages from a nested catalog, using only observed lessons.", + parameters: s.object({ catalogJson: s.string }), + handler: ({ catalogJson }: { catalogJson: string }) => { + type Lesson = { observed: boolean; completed: boolean }; + type Node = { lessons?: Lesson[]; submodules?: Record }; + const catalog: Record }> = JSON.parse(catalogJson); + const result: Record = {}; + const walk = (path: string, node: Node, miss: string[]): { obs: number; done: number } => { + const own = (node.lessons ?? []).filter((l: Lesson) => l.observed); + let obs = own.length, done = own.filter((l: Lesson) => l.completed).length; + for (const [name, child] of Object.entries(node.submodules ?? {})) { + const st = walk(`${path}/${name}`, child, miss); + obs += st.obs; done += st.done; + } + if (obs === 0) miss.push(path); + return { obs, done }; + }; + for (const [prog, data] of Object.entries(catalog)) { + const miss: string[] = []; + let obs = 0, done = 0; + for (const [name, mod] of Object.entries(data.modules)) { + const st = walk(name, mod, miss); + obs += st.obs; done += st.done; + } + result[prog] = { completionPct: obs > 0 ? Math.round((done / obs) * 1000) / 10 : 0, incompleteModules: miss }; + } + return { result }; + }, +}); + +// Agent role: read an embedded nested course catalog (programs > modules > +// submodules > lessons, arbitrary depth) and compute per-program completion +// from only observed lessons, leaving unobserved branches out of the math. +const nestedCatalogPartialRollup = agent({ + model: "small", + tools: [rollupCatalog], + instructions: p`Nested language-learning course catalog as JSON (a module's + "lessons" each have an "observed" flag meaning a learner attempted it, + and a "completed" flag; modules may nest "submodules" to any depth): + ${p.bash(`cat <<'EOF' +{"Spanish Fluency":{"modules":{"Foundations":{"lessons":[{"name":"Greetings","observed":true,"completed":true},{"name":"Numbers","observed":true,"completed":false},{"name":"Colors","observed":false,"completed":false}],"submodules":{"Pronunciation":{"lessons":[{"name":"Vowels","observed":true,"completed":true}]}}},"Conversation":{"lessons":[{"name":"Ordering Food","observed":false,"completed":false}],"submodules":{"Travel Phrases":{"lessons":[{"name":"Directions","observed":false,"completed":false}]}}}}},"Japanese for Travel":{"modules":{"Scripts":{"lessons":[{"name":"Hiragana","observed":true,"completed":true},{"name":"Katakana","observed":true,"completed":true}],"submodules":{"Kanji Basics":{"lessons":[{"name":"Radicals","observed":true,"completed":false}],"submodules":{"Stroke Order":{"lessons":[{"name":"Numbers Kanji","observed":false,"completed":false}]}}}}},"Etiquette":{"lessons":[{"name":"Bowing","observed":false,"completed":false}]}}}} +EOF`)} + + Call rollup_catalog once with this entire JSON object serialized exactly + as shown (as the catalogJson string argument), then return its "result" + field directly as your output.`, + output: s.record( + s.object({ + completionPct: s.number, + incompleteModules: s.array(s.string), + }), + ), +}); + +export default nestedCatalogPartialRollup; +``` diff --git a/skills/rig/samples/552-resource-schedule-partial-lookup.md b/skills/rig/samples/552-resource-schedule-partial-lookup.md new file mode 100644 index 0000000..7b2bcab --- /dev/null +++ b/skills/rig/samples/552-resource-schedule-partial-lookup.md @@ -0,0 +1,63 @@ +# 552 - Resource Schedule Partial Lookup + +```rig +import { agent, defineTool, p, s } from "rig"; + +// Tool: look up a reservoir's scheduled release window by station id from an embedded table. +const lookupReleaseWindow = defineTool("lookup_release_window", { + description: "Look up the scheduled release window for a reservoir station by its station id.", + parameters: s.object({ + stationId: s.string, + }), + handler: async ({ stationId }: { stationId: string }) => { + const schedule: Record = { + "res-01": "06:00-08:00", + "res-02": "08:00-10:00", + "res-03": "10:00-12:00", + "res-04": "12:00-14:00", + "res-05": "14:00-16:00", + "res-06": "16:00-18:00", + "res-07": "18:00-20:00", + "res-08": "20:00-22:00", + "res-09": "22:00-00:00", + "res-10": "00:00-02:00", + }; + const window = schedule[stationId]; + return { window: window === undefined ? undefined : window }; + }, +}); + +// Agent role: reconcile a fixed irrigation schedule against only the stations +// it manages to query via the lookup tool before its turn budget runs out. +const resourceScheduleReconciler = agent({ + model: "small", + maxTurns: 4, + tools: [lookupReleaseWindow], + instructions: p`The irrigation resource schedule requires release windows for + these ten station ids, in this exact order: + res-01, res-02, res-03, res-04, res-05, res-06, res-07, res-08, res-09, res-10 + + Every one of these station ids exists in the lookup table, so a resolved + window is always available once you query it. Call lookup_release_window + once per station id, strictly in the listed order, to fetch its window. + You have a strict turn budget and will run out before you can query every + station — this is expected. Do not rush or combine calls: query as many + stations in order as your remaining turns allow, then stop calling the + tool and produce your final answer immediately. + + Return resolvedWindows as one entry per station you successfully queried + (stationId and window). Return unresolvedStationIds as the remaining + station ids, in original order, that you ran out of turns to query before + the budget ended — these are unresolved purely due to the turn budget, not + because they are missing from the table.`, + output: s.object({ + resolvedWindows: s.array(s.object({ + stationId: s.string, + window: s.string, + })), + unresolvedStationIds: s.array(s.string), + }), +}); + +export default resourceScheduleReconciler; +``` diff --git a/skills/rig/samples/553-linked-record-chain-budget-walk.md b/skills/rig/samples/553-linked-record-chain-budget-walk.md new file mode 100644 index 0000000..60fd045 --- /dev/null +++ b/skills/rig/samples/553-linked-record-chain-budget-walk.md @@ -0,0 +1,66 @@ +# 553 - Linked Record Chain Budget Walk + +```rig +import { s, workflow } from "rig"; + +const record = s.object({ + id: s.string, + kind: s.string, + cost: s.number, + nextId: s.nullable(s.string), +}); + +const CHAIN_DATA = + "flight-001: kind=flight, cost=320, nextId=hotel-002 | " + + "hotel-002: kind=hotel, cost=410, nextId=transfer-003 | " + + "transfer-003: kind=local-transfer, cost=85, nextId=excursion-004 | " + + "excursion-004: kind=excursion, cost=150, nextId=transfer-005 | " + + "transfer-005: kind=local-transfer, cost=60, nextId=null"; + +const BUDGET = 900; + +// Workflow role: walk a linked chain of travel records one id at a time, then +// enforce a fixed dollar budget over the resolved chain and report the cut point. +const linkedRecordChainBudgetWalk = workflow({ + meta: { + name: "linked-record-chain-budget-walk", + description: "Resolve a linked travel-record chain, then trim it to a fixed budget", + phases: [{ title: "Resolve" }, { title: "Budget" }], + whenToUse: "Walking cross-referencing records while enforcing a cumulative cost cap.", + }, + body: async ({ call, phase }) => { + phase("Resolve"); + const resolved: { id: string; kind: string; cost: number }[] = []; + let currentId: string | null = "flight-001"; + + while (currentId) { + const step: { id: string; kind: string; cost: number; nextId: string | null } | null = await call.json( + `Travel record chain data: ${CHAIN_DATA}\nLook up the record whose id is "${currentId}" and return its id, kind, cost, and nextId (or null if it is the last record).`, + record, + { label: currentId }, + ); + if (!step) break; + resolved.push({ id: step.id, kind: step.kind, cost: step.cost }); + currentId = step.nextId; + } + + phase("Budget"); + const itinerary: { id: string; kind: string; cost: number }[] = []; + let totalCost = 0; + let cutAtId: string | null = null; + + for (const item of resolved) { + if (totalCost + item.cost > BUDGET) { + cutAtId = item.id; + break; + } + itinerary.push(item); + totalCost += item.cost; + } + + return { itinerary, totalCost, cutAtId }; + }, +}); + +export default linkedRecordChainBudgetWalk; +``` diff --git a/skills/rig/samples/554-conflicting-source-timeline-reconciler.md b/skills/rig/samples/554-conflicting-source-timeline-reconciler.md new file mode 100644 index 0000000..4b57905 --- /dev/null +++ b/skills/rig/samples/554-conflicting-source-timeline-reconciler.md @@ -0,0 +1,63 @@ +# 554 - Conflicting Source Timeline Reconciler + +```rig +import { agent, p, s } from "rig"; + +// Agent role: reconcile three disagreeing recipe-timing sources into one +// resolved cook time per step using a deterministic median/tie-break rule. +const conflictingSourceTimelineReconciler = agent({ + model: "mini", + instructions: p`Three sources describe cook times (in minutes) for the same + recipe steps, and they disagree: + + Cookbook (source: "cookbook"): + ${p.bash(`cat <<'EOF' +Step 1 - Saute onions = 8 +Step 2 - Simmer sauce = 25 +Step 3 - Cook pasta = 11 +Step 4 - Rest before serving = 5 +EOF`)} + + Video transcript (source: "video"): + ${p.bash(`cat <<'EOF' +Step 1 - Saute onions = 10 +Step 2 - Simmer sauce = 20 +Step 3 - Cook pasta = 9 +Step 4 - Rest before serving = 5 +EOF`)} + + User review (source: "review"): + ${p.bash(`cat <<'EOF' +Step 1 - Saute onions = 12 +Step 2 - Simmer sauce = 30 +Step 3 - Cook pasta = 9 +Step 4 - Rest before serving = 7 +EOF`)} + + For each step, you have exactly three numeric values: cookbook, video, + review. Resolve one value per step using this deterministic rule: + 1. Sort the three values. The resolved value is the median (middle) + value. + 2. Exception: if the distance from the lowest value to the middle value + equals the distance from the middle value to the highest value (the + three values are evenly spaced, e.g. 9, 9, 9 is not this case but + 8, 10, 12 is), the median is ambiguous — in that case ignore the + median and instead use the cookbook's own value for that step. + 3. If all three source values are exactly equal, use that common value + and there is no conflict. + + Set conflict to true whenever the three source values are not all + identical (regardless of which resolution rule applied). sourceMinutes + must be a record with keys "cookbook", "video", "review" mapping to each + source's original value for that step. Return one entry per step, in + step order, with a short step label as "step".`, + output: s.array(s.object({ + step: s.string, + resolvedMinutes: s.number, + sourceMinutes: s.record(s.number), + conflict: s.boolean, + })), +}); + +export default conflictingSourceTimelineReconciler; +``` diff --git a/skills/rig/samples/555-linked-record-foreign-key-join.md b/skills/rig/samples/555-linked-record-foreign-key-join.md new file mode 100644 index 0000000..980cf43 --- /dev/null +++ b/skills/rig/samples/555-linked-record-foreign-key-join.md @@ -0,0 +1,56 @@ +# 555 - Linked Record Foreign Key Join + +```rig +import { agent, defineTool, p, s } from "rig"; + +// Tool: join a reading (experimentId+runId) to its calibration by calibrationId; +// return adjusted value, or null with a reason when the foreign key is dangling. +const resolveAdjustedReading = defineTool("resolve_adjusted_reading", { + description: "Join a reading record to its calibration record by calibrationId and compute the adjusted value.", + parameters: s.object({ experimentId: s.string, runId: s.string }), + handler: async ({ experimentId, runId }: { experimentId: string; runId: string }) => { + const readings = [ + { experimentId: "exp-a", runId: "run-1", value: 10.5, calibrationId: "cal-1" }, + { experimentId: "exp-a", runId: "run-2", value: 11.2, calibrationId: "cal-2" }, + { experimentId: "exp-b", runId: "run-1", value: 9.8, calibrationId: "cal-9" }, + { experimentId: "exp-b", runId: "run-2", value: 12.1, calibrationId: "cal-3" }, + { experimentId: "exp-c", runId: "run-1", value: 8.4, calibrationId: "cal-1" }, + ]; + const calibrations: Record = { "cal-1": 0.5, "cal-2": -0.3, "cal-3": 1.1 }; + const reading = readings.find((r) => r.experimentId === experimentId && r.runId === runId); + if (!reading) { + return { rawValue: 0, adjustedValue: null, adjustmentReason: `reading not found for ${experimentId}/${runId}` }; + } + const offset = calibrations[reading.calibrationId]; + if (offset === undefined) { + return { rawValue: reading.value, adjustedValue: null, adjustmentReason: `calibration record not found for calibrationId ${reading.calibrationId}` }; + } + return { rawValue: reading.value, adjustedValue: reading.value + offset, adjustmentReason: null }; + }, +}); + +// Agent role: resolve each embedded experiment/run pair through the foreign-key join tool and summarize results. +const linkedRecordForeignKeyJoiner = agent({ + model: "small", + tools: [resolveAdjustedReading], + instructions: p`Resolve these experiment/run pairs: + - "exp-a"/"run-1", "exp-a"/"run-2", "exp-b"/"run-1", "exp-b"/"run-2", "exp-c"/"run-1" + + For each pair, call resolve_adjusted_reading with its experimentId and runId. + The tool joins the reading's calibrationId against a separate calibrations + table. If that calibrationId is missing (a dangling foreign key), the tool + returns adjustedValue: null plus a non-null adjustmentReason naming the + missing calibrationId; keep that reason verbatim. Otherwise adjustmentReason + must be null. Return one entry per pair with experimentId, runId, rawValue, + adjustedValue, and adjustmentReason exactly as reported by the tool.`, + output: s.array(s.object({ + experimentId: s.string, + runId: s.string, + rawValue: s.number, + adjustedValue: s.nullable(s.number), + adjustmentReason: s.nullable(s.string), + })), +}); + +export default linkedRecordForeignKeyJoiner; +``` diff --git a/skills/rig/samples/556-tabular-measurement-source-priority-reconciler.md b/skills/rig/samples/556-tabular-measurement-source-priority-reconciler.md new file mode 100644 index 0000000..321559e --- /dev/null +++ b/skills/rig/samples/556-tabular-measurement-source-priority-reconciler.md @@ -0,0 +1,41 @@ +# 556 - Tabular Measurement Source Priority Reconciler + +```rig +import { agent, p, s } from "rig"; + +// Agent role: reconcile conflicting shelf and digital copy-count sources per title +// using an asymmetric source-priority override rule, classifying each resolution path. +const tabularMeasurementSourcePriorityReconciler = agent({ + model: "small", + instructions: p`Shelf inventory (physical copy counts), from ${p.bash("cat <<'DATA'\ntitle,shelfCopies\nThe Silent Archive,4\nBeneath Northern Skies,0\nLetters to a Vanished City,2\nThe Cartographer's Ledger,6\nWhispers in the Stacks,3\nA History of Quiet Rooms,0\nDATA")} + +Digital lending system export (digital copy counts, which normally supersede +shelf counts), from ${p.bash("cat <<'DATA'\ntitle,digitalCopies\nThe Silent Archive,4\nBeneath Northern Skies,5\nLetters to a Vanished City,0\nThe Cartographer's Ledger,6\nThe Forgotten Marginalia,2\nDATA")} + +Reconcile the two sources into one row per distinct title using this rule: +- If a title appears only in the shelf list, status is "shelf-only" and + reconciledCopies is the shelf count. +- If a title appears only in the digital list, status is "digital-only" and + reconciledCopies is the digital count. +- If a title appears in both and the digital count is nonzero, the digital + count is used as reconciledCopies. If the digital count equals the shelf + count, status is "agreed"; if it differs from the shelf count, status is + "overridden" (digital's literal reported count is what is used, overriding + a different shelf value). +- If a title appears in both and the digital count is 0 while the shelf count + is nonzero, the digital system's zero likely just means "not tracked + digitally": use the shelf count as reconciledCopies instead, and set status + to "overridden" (the digital system's literal reported count of 0 was NOT + the one used). +- If both report 0, treat that as "agreed" with reconciledCopies 0. +Return one row per title covering all titles seen across both sources, with +no duplicates. Return only the declared output.`, + output: s.array(s.object({ + title: s.string, + reconciledCopies: s.number, + status: s.enum("shelf-only", "digital-only", "agreed", "overridden"), + })), +}); + +export default tabularMeasurementSourcePriorityReconciler; +``` diff --git a/skills/rig/samples/557-parallel-timeline-conflict-flagger.md b/skills/rig/samples/557-parallel-timeline-conflict-flagger.md new file mode 100644 index 0000000..d930ac2 --- /dev/null +++ b/skills/rig/samples/557-parallel-timeline-conflict-flagger.md @@ -0,0 +1,64 @@ +# 557 - Parallel Timeline Conflict Flagger + +```rig +import { agent, p, s, workflow } from "rig"; + +const timelineEvent = s.object({ timestamp: s.string, kwh: s.number }); +// Agent role: parse the smart-meter export timeline into structured kWh readings. +const smartMeterParser = agent({ + model: "small", + instructions: p`Smart-meter export (timestamp, kWh), one reading per line: + ${p.bash(`cat <<'EOF' +2024-05-01 00:00:00Z, 1.20 +2024-05-01 01:00:00Z, 0.95 +2024-05-01 03:00:00Z, 1.10 +2024-05-01 04:00:00Z, 0.80 +2024-05-01 05:00:00Z, 1.50 +EOF`)} + Parse each line into a reading, exactly as logged. Return the declared output.`, + output: s.array(timelineEvent), +}); +// Agent role: parse the utility billing export timeline into structured kWh readings. +const billingParser = agent({ + model: "small", + instructions: p`Utility billing export (timestamp, kWh), one reading per line: + ${p.bash(`cat <<'EOF' +2024-05-01 00:00:00Z, 1.20 +2024-05-01 01:00:00Z, 1.15 +2024-05-01 04:00:00Z, 0.80 +2024-05-01 06:00:00Z, 0.65 +EOF`)} + Parse each line into a reading, exactly as logged. Return the declared output.`, + output: s.array(timelineEvent), +}); +// Workflow role: fan out over both timelines in parallel, then reconcile by +// timestamp (agree-once, flag disagreements, pass through single-source). +const parallelTimelineConflictFlagger = workflow({ + meta: { + name: "parallel-timeline-conflict-flagger", + description: "Fan out over smart-meter and billing timelines, flag timestamp disagreements instead of merging", + phases: ["Parse", "Reconcile"], + }, + body: async ({ call, phase }) => { + phase("Parse"); + const [meterEvents, billEvents] = await Promise.all([ + call(smartMeterParser, "parse smart-meter export"), + call(billingParser, "parse billing export"), + ]); + phase("Reconcile"); + const meterMap = new Map((meterEvents ?? []).map((e) => [e.timestamp, e.kwh])); + const billMap = new Map((billEvents ?? []).map((e) => [e.timestamp, e.kwh])); + const reconciledEvents: { timestamp: string; kwh: number; source: string }[] = []; + const flaggedConflicts: { timestamp: string; smartMeterKwh: number; billingKwh: number }[] = []; + for (const ts of new Set([...meterMap.keys(), ...billMap.keys()])) { + const m = meterMap.get(ts), b = billMap.get(ts); + if (m !== undefined && b !== undefined && m === b) reconciledEvents.push({ timestamp: ts, kwh: m, source: "agreed" }); + else if (m !== undefined && b !== undefined) flaggedConflicts.push({ timestamp: ts, smartMeterKwh: m, billingKwh: b }); + else if (m !== undefined) reconciledEvents.push({ timestamp: ts, kwh: m, source: "smart-meter" }); + else if (b !== undefined) reconciledEvents.push({ timestamp: ts, kwh: b, source: "billing" }); + } + return { reconciledEvents, flaggedConflicts }; + }, +}); +export default parallelTimelineConflictFlagger; +``` diff --git a/skills/rig/samples/558-nested-catalog-budget-allocator.md b/skills/rig/samples/558-nested-catalog-budget-allocator.md new file mode 100644 index 0000000..52400f3 --- /dev/null +++ b/skills/rig/samples/558-nested-catalog-budget-allocator.md @@ -0,0 +1,26 @@ +# 558 - Nested Catalog Budget Allocator + +```rig +import { agent, p, s } from "rig"; + +// Agent role: allocate a fixed 40-hour volunteer-hour budget top-down across each festival's stages. +const nestedCatalogBudgetAllocator = agent({ + model: "large", + instructions: p`Read this nested community-event catalog, three levels deep (festival -> stages -> time-slotted acts): +${p.bash("printf '%s\\n' \"FESTIVAL: Riverside Summer Festival\" \"STAGE: Main Stage\" \" ACT 14:00 The Tideline Trio\" \" ACT 15:30 Harbor Brass Band\" \" ACT 17:00 Coastal Echoes\" \" ACT 18:30 The Driftwood Four\" \" ACT 20:00 Riverside Allstars\" \"STAGE: Acoustic Tent\" \" ACT 13:00 Willow and Cane\" \" ACT 14:30 Maren Holt\" \" ACT 16:00 The Quiet Oars\" \"STAGE: Food Court Stage\" \" ACT 12:00 Lunchtime Strummers\" \" ACT 13:30 The Market Buskers\" \"FESTIVAL: Night Market Busking Festival\" \"STAGE: Lantern Corner\" \" ACT 18:00 Paper Lantern Duo\" \"STAGE: Spice Alley Corner\" \" ACT 18:15 Cinnamon Street Trio\" \"STAGE: Harbor View Corner\" \" ACT 18:30 Tidewatch Singer\" \"STAGE: Old Town Corner\" \" ACT 18:45 Cobblestone Fiddler\" \"STAGE: Clocktower Corner\" \" ACT 19:00 Chime Hour Quartet\" \"STAGE: Market Square Corner\" \" ACT 19:15 Square Dance Combo\" \"STAGE: Riverside Walk Corner\" \" ACT 19:30 Walkway Whistler\"")} + +Each festival has a fixed total volunteer-hour budget of 40 hours, allocated top-down and independently per festival. For each festival: +1. List its stages in the fixed order they appear in the catalog. +2. Compute every stage's proportional share of the 40-hour budget based on its act count among ALL of that festival's stages (not just remaining ones), rounded to the nearest whole hour. +3. Walk the stages in listed order, adding each stage's computed share to a running total. Allocate a stage only while the running total after adding its share would not exceed 40 hours. +4. The instant a stage's share would push the running total over 40 hours, mark that stage and every remaining stage after it (in listed order) as unfunded, regardless of their individual act-count entitlement. Do not allocate any hours to them. + +Return, per festival, the map of stage name to allocated hours for funded stages only (stageAllocations), and the ordered list of stage names that were cut off (unfundedStages).`, + output: s.record(s.object({ + stageAllocations: s.record(s.number), + unfundedStages: s.array(s.string), + })), +}); + +export default nestedCatalogBudgetAllocator; +``` diff --git a/skills/rig/samples/559-multilingual-linked-record-manifest.md b/skills/rig/samples/559-multilingual-linked-record-manifest.md new file mode 100644 index 0000000..d095104 --- /dev/null +++ b/skills/rig/samples/559-multilingual-linked-record-manifest.md @@ -0,0 +1,62 @@ +# 559 - Multilingual Linked Record Manifest + +```rig +import { agent, defineTool, p, s } from "rig"; + +// Tool: resolve a shot's scene, then return labels/missingLocales for the requested locale set. +const resolveSubtitleCues = defineTool("resolve_subtitle_cues", { + description: "Resolve subtitle cue labels for a shot across a set of requested locale codes.", + parameters: s.object({ + shotId: s.string, + locales: s.array(s.string), + }), + handler: async ({ shotId, locales }: { shotId: string; locales: string[] }) => { + const shots: Record = { + "shot-1": "SC-10", + "shot-2": "SC-20", + "shot-3": "SC-10", + "shot-4": "SC-30", + "shot-5": "SC-20", + }; + const scenes: Record> = { + "SC-10": { en: "Dawn arrival", ja: "夜明けの到着", fr: "Arrivee a l'aube" }, + "SC-20": { en: "Market chase" }, + "SC-30": { en: "Final stand", ja: "最後の抵抗" }, + }; + const sceneId = shots[shotId]; + const record = sceneId ? scenes[sceneId] ?? {} : {}; + const labels: Record = {}; + const missingLocales: string[] = []; + for (const locale of locales) { + const label = record[locale]; + if (label === undefined) missingLocales.push(locale); + else labels[locale] = label; + } + return { labels, missingLocales }; + }, +}); + +// Agent role: build a per-shot subtitle manifest by resolving the same locale set for every shot. +const multilingualSubtitleManifestBuilder = agent({ + model: "small", + tools: [resolveSubtitleCues], + instructions: p`Here are the shots to manifest, in order: shot-1, shot-2, shot-3, + shot-4, shot-5. + + For each shot, call resolve_subtitle_cues exactly once with that shotId and + locales set to ["en", "ja"] (always the same two locales for every shot). + Use the tool's returned labels and missingLocales exactly as given; do not + invent labels for locales the tool marked missing. Return one entry per + shot, in the same order, combining shotId with the tool's labels and + missingLocales.`, + output: s.array( + s.object({ + shotId: s.string, + labels: s.record(s.string), + missingLocales: s.array(s.string), + }), + ), +}); + +export default multilingualSubtitleManifestBuilder; +``` diff --git a/skills/rig/samples/560-linked-record-recency-reconciler-workflow.md b/skills/rig/samples/560-linked-record-recency-reconciler-workflow.md new file mode 100644 index 0000000..b2e96f4 --- /dev/null +++ b/skills/rig/samples/560-linked-record-recency-reconciler-workflow.md @@ -0,0 +1,63 @@ +# 560 - Linked Record Recency Reconciler Workflow + +```rig +import { agent, p, s, workflow } from "rig"; + +const enrollmentGradeList = s.array(s.object({ + studentId: s.string, + courseId: s.string, + grade: s.nullable(s.string), +})); + +// Agent role: extract the two conflicting registrar exports into parallel structured enrollment-grade lists. +const extractor = agent({ + model: "small", + instructions: p`Older CSV export of student-course grades: ${p.bash(`cat <<'EOF' +studentId,courseId,grade +S100,C-ALG101,B+ +S100,C-BIO210,A- +S101,C-ALG101,C +S102,C-CHEM150,A +S103,C-HIST220,B +EOF`)} +Newer API snapshot of the SAME pairs (grade may be null for a pair present above): ${p.bash(`cat <<'EOF' +{"studentId":"S100","courseId":"C-ALG101","grade":"A-"} +{"studentId":"S100","courseId":"C-BIO210","grade":"A-"} +{"studentId":"S101","courseId":"C-ALG101","grade":null} +{"studentId":"S102","courseId":"C-CHEM150","grade":"A"} +{"studentId":"S103","courseId":"C-HIST220","grade":"B+"} +EOF`)} +Extract both sources into two parallel lists of the same student-course pairs (keyed by studentId+courseId), each with its grade (null if missing).`, + output: s.object({ olderCsv: enrollmentGradeList, newerSnapshot: enrollmentGradeList }), +}); + +// Agent role: apply the newer-wins-unless-null recency rule per pair, classify each resolution, and write the audit-trail report. +const reconciler = agent({ + model: "small", + input: s.object({ olderCsv: enrollmentGradeList, newerSnapshot: enrollmentGradeList }), + instructions: p`Given input.olderCsv and input.newerSnapshot (parallel lists of the same student-course pairs keyed by studentId+courseId), resolve each pair's grade: the newer snapshot's grade wins unless it is null, in which case fall back to the older CSV's grade. +Classify each pair: "agreed" if both sources have the same non-null grade; "overridden" if both are non-null but differ and the newer grade was used; "fallback" if the newer grade is null so the older grade was used instead. +Write a markdown reconciliation report with counts of each category and the full list of pairs with resolved grade and classification to ${p.write("reconciliation-report.md", "# Enrollment Grade Reconciliation Report")}. +Return the report path and the counts of agreed, overridden, and fallback pairs.`, + output: s.object({ reportPath: s.path, agreedCount: s.int, overriddenCount: s.int, fallbackCount: s.int }), +}); + +// Workflow role: extract conflicting linked-record sources, then reconcile with a recency/null-fallback rule and persist an audit trail. +const linkedRecordRecencyReconcilerWorkflow = workflow({ + meta: { + name: "linked-record-recency-reconciler-workflow", + description: "Extract two conflicting enrollment-grade sources then reconcile with recency-priority and null-fallback classification", + phases: ["Extract", "Reconcile"], + }, + body: async ({ call, phase }) => { + phase("Extract"); + const extracted = await call(extractor, "extract both registrar sources", { label: "extract" }); + + phase("Reconcile"); + const pairs = { olderCsv: extracted?.olderCsv ?? [], newerSnapshot: extracted?.newerSnapshot ?? [] }; + return call(reconciler, pairs, { label: "reconcile" }); + }, +}); + +export default linkedRecordRecencyReconcilerWorkflow; +```