Skip to main content

timon-workflows

Use when a task needs more than one agent run or a check on an agent's work - verify findings, vote, pick the best of several, revise until a gate passes, retry, or run steps in dependency order with timon's flow patterns. Load timon-agents first for agent.run.

Source facts

Repository
joshrotenberg/timon
Last source activity
October 17, 2026 at 04:06
Detected SKILL.md language
English
Stars
0
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
timon-workflows
description
Use when a task needs more than one agent run or a check on an agent's work - verify findings, vote, pick the best of several, revise until a gate passes, retry, or run steps in dependency order with timon's flow patterns. Load timon-agents first for agent.run.
# Workflows with flow Run `timon` as a command of its own. A host that allows only `timon` commands denies the whole line when it has `;`, `&&`, `|`, `$?`, a redirect or a heredoc around timon. Pass multi-line Lua as one single-quoted argument, which may span lines and cannot contain `'`: use `"` for Lua strings, and put code whose text needs an apostrophe in a file run with `timon eval -f FILE`. Read files with `fs.read` in Lua instead of piping them in, and skip `$?`: a failure prints `timon: kind: message` and exits nonzero. `flow` holds patterns over agent runs, commands and plain Lua. A pattern returns a record and never raises for an outcome: - `status` says whether the pattern ran to the end: `ok`, `failed`, `timeout`, `invalid`. - What it concluded is a separate field: `winner`, `accepted`, `kept`. A review that requests changes, a run that failed and an answer that broke its contract stay three different things. - `runs` holds every agent record the steps returned, and `usage` is `{ runs, usd, unknown_cost }`; Codex reports no cost, so its runs count as unknown, not as $0. A step returns an agent record (its `answer`, or its `text`, becomes the value), another pattern's record, or a plain value. Wrap a pattern in `scope(fn, { budget = { runs = N } })` to cap what it spends. ## Gates A gate checks a value. Prefer a deterministic gate when one exists; ask a model when only judgment will do. ```lua local tests = flow.check({ "true" }) -- a command: passes on exit 0 local short = flow.check(function(s) return #s <= 72, "at most 72 characters" end, "fix: a subject") local real = flow.check({ prompt = "Is this finding real?" }, { file = "a.rs", line = 3 }) -- yes_no return tests.passed, short.passed, real.status ``` A gate function returns `true` or `false` (and a reason), `{ passed, reason }`, or an agent record with a `yes_no` or `review` answer. `passed` is nil when the check did not run. ## Verify, then keep what holds ```lua local findings = { { title = "off by one", line = 3 }, { title = "style nit", line = 0 } } local r = flow.verify{ items = findings, checks = { function(f) return f.line > 0, "points at a line" end, { prompt = "Is this finding a real bug? Answer no for style." }, }, keep = "majority", -- or "all", "any" } return #r.kept, r.usage.runs ``` `majority` refutes: it keeps an item unless most of the gates reject it, so a tie keeps it, and a gate that failed to run counts as no rejection. Kept is not verified. `all` keeps an item only when every gate ran and passed; `any` keeps one when at least one gate ran and passed. ```lua local yes = function() return true end local no = function() return false, "not a bug" end local down = function() error("reviewer unavailable", 0) end local function kept(keep, checks) return #flow.verify{ items = { "finding" }, keep = keep, checks = checks }.kept end assert(kept("majority", { yes, no }) == 1) -- a tie keeps assert(kept("majority", { no, down }) == 1) -- one rejection is not most of two gates assert(kept("majority", { down, down }) == 1) -- no gate ran, so none rejected assert(kept("all", { yes, down }) == 0) -- every gate must run and pass assert(kept("any", { no, down }) == 0) -- no gate passed assert(kept("any", { no, yes }) == 1) -- one gate passed ``` `status = "ok"` says the pattern ran to the end, not that a gate accepted anything. Read each item's `passed` and `rejected` counts and its `verdicts` to tell an accepted item from an inconclusive one: ```lua local down = function() error("reviewer unavailable", 0) end local r = flow.verify{ items = { "critical finding" }, checks = { down, down } } local item = r.items[1] local inconclusive = {} for _, v in ipairs(item.verdicts) do if v.status ~= "ok" then inconclusive[#inconclusive + 1] = v.status .. ": " .. v.error.message end end assert(r.status == "ok" and #r.kept == 1 and item.passed == 0 and item.rejected == 0) assert(#inconclusive == 2 and inconclusive[1] == "failed: reviewer unavailable") return inconclusive ``` Use `keep = "all"` when every gate must accept an item; an item no gate could judge is then rejected, not kept. ## Vote ```lua local r = flow.vote{ n = 3, run = function(i) return agent.run{ prompt = "Is the release ready? Voter " .. i, output = "yes_no" } end, } return r.winner, r.agreed, r.tally ``` The vote stops as soon as one key reaches `quorum` (default: a majority of `n`). `key` decides what counts as the same answer; the default reads a `yes_no` answer. ## Best of n ```lua local r = flow.best_of{ n = 3, run = function(i) return ("candidate %d: "):format(i) .. ("x"):rep(i) end, score = function(text) return -#text end, -- deterministic: the shortest wins } return r.winner, r.value ``` `judge = function(values) return index end` picks instead of `score`; it may run an agent. ## Refine until a gate passes ```lua local r = flow.refine{ draft = function() return "feat: add the thing that does the stuff and more besides that" end, check = function(subject) return #subject <= 50, "subjects are at most 50 characters" end, revise = function(subject, verdict, round) return subject:sub(1, 50 - round) end, rounds = 3, } return r.accepted, r.value ``` `revise(value, verdict, round, last)` gets the failed verdict and the last step's agent record, so an agent can revise in its own session with `agent.continue(last, ...)`. ## Pipeline, retry and plan ```lua local p = flow.pipeline({ "src/a.rs", "src/b.rs" }, { { name = "size", run = function(path) return #path end }, { name = "double", run = function(n) return n * 2 end }, }) local attempts = 0 local r = flow.retry(function(attempt, last) attempts = attempt if attempt == 1 then error("flaky", 0) end return "done after " .. last.error.message end, { times = 2 }) local plan = flow.plan{ steps = { find = { run = function() return { "a", "b" } end }, fix = { needs = { "find" }, run = function(deps) return #deps.find end }, } } return p.values[2], r.value, plan.value.fix ``` - `pipeline` runs every item through the stages with no barrier; an item stops at its first stage that is not ok. - `retry` calls again on `failed`, `timeout` or `invalid`; `last.error.data` holds an invalid answer to repair from. - `plan` starts each step when the steps it `needs` succeed; the first failure stops new steps. ## As tasks A workflow is a task or a script. The review a team runs every day: ```lua fragment -- .timon/tasks.lua review = { description = "Review the branch, then keep the findings both verifiers think are real", budget = { runs = 8, usd = 2 }, run = function() local review = agent.run{ agent = "reviewer", prompt = "Review the diff against main", output = "review" } if review.status ~= "ok" then return review end local r = flow.verify{ items = review.answer.findings, keep = "all", checks = { { prompt = "Is this finding real? Read the code it names." }, function(f) return git.diff{ paths = { f.file } } ~= "", "the file changed on this branch" end, } } return { kept = r.kept, dropped = #r.rejected, usd = r.usage.usd + review.usage.usd } end, }, ``` ## From a sequence to a workflow Offer a workflow once you run the same sequence of agent work and checks on a second input. Bug reports, audited by hand: reproduce one with a failing test, check the test fails and shows that bug, report. The offer is a script in the project's `.timon/scripts/`; kept after the person agrees, the steps and the success criteria are fixed and the report is the input: ```lua fragment -- .timon/scripts/audit.lua -- Reproduce a bug report with a failing test, verify it, report: timon audit REPORT.md local report = fs.read(arg[1]) phase("reproduce") local repro = agent.run{ access = "write", worktree = true, output = "investigation", prompt = "Reproduce this bug with a failing test. Do not fix it.\n\n" .. report, } if repro.status ~= "ok" or not (repro.worktree and repro.worktree.kept) then return { reproduced = false, record = repro.id } -- failed, or wrote no test end local dir = repro.worktree.path local function a_test_fails() local t = proc.run({ "cargo", "test" }, { cwd = dir }) if t.code == 0 then return false, "every test passes" end if not t.out:find("test result: FAILED", 1, true) then return false, "cargo test exited " .. t.code .. " before a test failed" end return true, t.out:match("test %S+ %.%.%. FAILED") or "a test fails" end phase("verify") local r = flow.verify{ items = { repro.answer }, keep = "all", checks = { a_test_fails, { cwd = dir, prompt = "Does the new failing test here show this bug and no other?\n\n" .. report }, } } return { reproduced = #r.kept == 1, -- both gates ran and passed branch = repro.worktree.branch, -- the test, to review or build the fix on verdicts = r.items[1].verdicts, -- why, gate by gate usd = (repro.usage.usd or 0) + (r.usage.usd or 0), } ``` ```sh timon audit reports/88.md # the report it was built on: does it agree with the audit by hand? timon audit reports/91.md # the next report ``` A workflow worth keeping does a job the project has, on the project's inputs, and its result says what to do next. A script that calls each `flow` pattern on made-up values is a smoke test of the API: it belongs in tests, if anywhere, not in `.timon/`.
View on GitHub