Skip to content

Add optional staged task dependencies - #100

Open
Guidance78 wants to merge 1 commit into
NateBJones-Projects:mainfrom
Guidance78:feature/staged-dependencies
Open

Guidance78 wants to merge 1 commit into
NateBJones-Projects:mainfrom
Guidance78:feature/staged-dependencies

Conversation

@Guidance78

Copy link
Copy Markdown

The problem I hit

Ringer runs tasks as a flat set — everything is eligible at once. I run producer
tasks followed by an independent QC reviewer on a different model family, and
there's no way to say "review this one after it builds."

So every stage becomes two runs with me in the middle, doing nothing but
watching for the first one to finish and then launching the second. The
expensive part isn't the worker tokens, it's the directing model reloading
context to do dispatch work it could have declared up front.

The change

An optional task field:

{"key": "schema-qc", "depends_on": ["schema-producer"]}
  • Tasks without depends_on behave exactly as they do today.
  • A dependent waits before acquiring a max_parallel slot, never while
    holding one, so a two-stage manifest at max_parallel: 1 can't deadlock.
  • It runs only after every prerequisite reaches a final PASS — a producer that
    fails attempt 1 and passes attempt 2 has passed.
  • If a prerequisite doesn't pass, the dependent is skipped: no worker
    launched, no attempt consumed, and no eval row for a model that never ran.
    Skips propagate down the chain.
  • Waiting, skipped and blocked_by show up truthfully in run state, the
    progress bar, the briefings and the summary.
  • Missing keys, self-dependencies, duplicates, non-string entries, bare strings,
    dicts and cycles are all rejected at parse time, with the cycle path named.

Every task publishes its terminal result exactly once from a finally, so
preparation failures, timeouts, exhausted attempts, cancellation and unexpected
exceptions release waiters instead of hanging the run.

Deliberately not in this PR

No cross-run dependencies, no conditional workflow language, no dynamic task
generation, no automatic artifact routing or patch application. depends_on is
a control dependency only. Because a passing task's worktree can be removed
before its dependent starts, anything the dependent needs has to be exported by
the producer's check to a durable path outside the worktree — documented beside
the field.

Proof

  • 27 new tests in tests/test_dependencies.py, all observed failing before the
    implementation existed.
  • Disabling the dependency wait turns 7 of them red, including the fan-in
    ordering test — they detect the feature's absence rather than asserting on end
    status.
  • Full suite: RINGER_NO_SELF_UPDATE=1 python3 -m unittest discover -s tests —
    280 pass on Python 3.12.
  • git diff --check clean.
  • Also exercised end to end on a real run: a producer that failed left its
    dependent skipped with 0 attempts, 0 tokens and no eval row, while a
    separate passing producer released its reviewer normally.

delisma pushed a commit to delisma/ringer that referenced this pull request Sep 2, 2026
… contract (#2576)

The CLI runtime is entirely .mjs, and the root eslint.config.js globally
ignored **/*.mjs (added in NateBJones-Projects#79 to get lint green; the CLI landed in NateBJones-Projects#100
and silently inherited the exclusion). All 162 CLI .mjs files — the entire
package runtime — had never been linted.

This:
- Narrows the global **/*.mjs ignore to opt packages/cli back in (scoped to
  the CLI only; other packages' .mjs stay ignored). Adds a Node language
  environment (sourceType: module + node globals) for the CLI .mjs block, and
  a copyright-header check for .mjs. Test files (*.test.mjs) stay relaxed.
- Fixes the baseline backlog this surfaced (218 errors → 0): 172 no-undef
  (missing node globals, fixed by the language config), 34 no-unused-vars
  (dead imports/vars/params removed or pruned), 5 no-useless-assignment,
  3 no-empty (documented best-effort catches), 2 no-useless-escape,
  1 no-case-declarations, 1 preserve-caught-error (attach cause). No rules
  were mass-disabled.
- Adds @xds/no-raw-console-cli: bans bare console.log in CLI runtime files
  (autofix → humanLog), allows console.error/console.warn (stderr), and
  exempts the sanctioned raw writers lib/json.mjs, index.mjs, bin/xds.mjs.
  This enforces the #2467 JSON-stdout contract at author time.

Closes #2468
Ringer runs manifest tasks as a flat set: every task is eligible at once.
There is no way to say "review this after it builds", so a producer and
its QC reviewer have to be two separate runs with a human or a directing
model in between, doing nothing but watching for the first to finish.

Tasks can now declare depends_on, a list of task keys that must all reach
a final PASS first:

    {"key": "schema-qc", "depends_on": ["schema-producer"], ...}

Manifests without the field keep their current execution path exactly.

A dependent waits before acquiring a max_parallel slot, never while
holding one, so a two-stage manifest at max_parallel=1 cannot deadlock.
When a prerequisite does not pass, the dependent is recorded skipped: no
worker is launched, no attempt is consumed, and no eval row is written
for a model that never ran. Skips propagate down the chain, and waiting,
skipped and blocked_by are shown truthfully in run state, the progress
bar, the briefings and the summary.

Every task publishes its terminal result exactly once from a finally
block, so preparation failures, timeouts, exhausted attempts, cancellation
and unexpected exceptions all release waiters instead of hanging the run.

Missing keys, self-dependencies, duplicates, non-string entries, bare
strings, dicts and cycles are rejected while the manifest is parsed, with
the cycle path named in the error.

depends_on is a control dependency only. A passing task's worktree can be
removed before its dependent starts, so any output a dependent needs must
be exported by the producer's check to a durable path outside the
worktree. That limitation is documented beside the field.

Proof: 27 new tests in tests/test_dependencies.py, all observed failing
before the implementation existed. Disabling the dependency wait turns 7
of them red, including the fan-in ordering test, so they detect the
feature's absence rather than asserting on end status. Full suite 280
pass on Python 3.12.

Co-Authored-By: Claude Opus 5 <[email protected]>
@Guidance78
Guidance78 force-pushed the feature/staged-dependencies branch from b88fc4d to 53acdcd Compare October 5, 2026 18:37
@Guidance78

Copy link
Copy Markdown
Author

Rebased this PR onto the latest upstream main (2026-10-05). The staged-dependency regression suite passes (27 tests), and GitHub now reports the PR mergeable. The feature remains a single commit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant