Skip to content

Package boundaries: training knows no AOP, rewrite knows no training; engine class, observable fan-out loop, rule-based gate - #15

Merged
Tyler-R-Kendrick merged 5 commits into
mainfrom
claude/interface-redundancies-consolidate-8juzw3
Jul 12, 2026
Merged

Tyler-R-Kendrick merged 5 commits into
mainfrom
claude/interface-redundancies-consolidate-8juzw3

Conversation

@Tyler-R-Kendrick

@Tyler-R-Kendrick Tyler-R-Kendrick commented Jul 12, 2026 •

Copy link
Copy Markdown
Owner

Summary

Boundary/design cleanups across packages/training, packages/rewrite, and packages/harness, with all cross-package wiring confined to the root ts-autocode package:

  1. training knows nothing about rewrite/AOP,
  2. rewrite knows nothing about training,
  3. the harness bus knows nothing about the judge, and the harness creates no agents,
  4. the engine is a real class composing its strategy,
  5. the default loop is an observable round sequence with capped fan-out,
  6. the promotion gate is an extensible rule set instead of cascading ifs.

Changes

Training package knows nothing about rewrite/AOP

  • ports.ts (the MethodWeaver/SourcePromoter mirrors of the rewrite API) is deleted. Training exposes two seams in its own vocabulary: captureTrainable(...) and TrainingProviders.promote (a PromotionApplier with rollback()). The trainable decorator, wrapTrainable, and instrumentTrainable moved to the root package.

Rewrite package knows nothing about training

  • promotion.ts is deleted: RewriteApproval and the in-package gate check are gone, along with RewriteCandidate.trainableId. Rewrite is abstract AOP plus guarded rewriting: commitRewrite/revertRewrite with RewriteSnapshot; gating is the consumer's decision. The root package's rewritePromotion enforces the gate correspondence before committing, and configureRewriteCapture() points the interceptor at captureTrainable.

Harness bus is a plain message log; the judge is not special

  • WriteAheadAgentBus no longer enumerates roles, holds an ActionJudge, gates dispatches via setJudge, or keeps a private judgeControl bootstrap path. It is a durable append-only message log: append({actor, kind, payload}), read(actor?), and an optional allow hook deciding who may append or read.
  • The write-ahead convention is layered on top (dispatchAction in dispatch.ts): record intent → ask the gate → record the verdict (the judge is just another actor; its decision is an ordinary agent.decision message) → execute only on pass → record the outcome. The harness dispatches every actor invocation through it with the run's judge callback as the gate; MxcSandbox takes an actor string and optional gate instead of a role.
  • The harness creates no agents: agents.ts (createTrainingAgents with per-role Deep Agent models, system prompts, and role wiring) and parseJudgeDecision are removed. Callbacks are the whole contract. The harness README, root README, and architecture doc are rewritten to describe exactly this surface.

A real engine class, composition over inheritance

  • CandidateEngine owns request validation, implementation cleanup, TypeScript validation, and candidate identity as private methods, composing the injected TrainingEngine strategy.

Observable loop with fan-out

  • trainingRounds() is a cold observable round sequence; sequentialLoop collects the subscription into one run. fanOut caps concurrent candidate pipelines per round; duplicates are skipped, empty rounds stall, and the highest-scoring gated candidate wins. ProposalTurn.slot lets strategies diversify.

Rule-based, extensible promotion gate

  • PromotionGate rules over one shared PromotionGateContext; standard invariants in defaultPromotionGates always run, gates appends extension rules, and policy participates as one more gate.

Testing

  • npm run check (typecheck for all packages, full test suite — 77 tests across 15 files, and the package build) passes.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NG6fSKqgt8nD7JnEY9q245

Summary by CodeRabbit

  • New Features
    • Added observable training rounds with configurable concurrent candidate exploration, duplicate handling, cancellation, and winner selection.
    • Added configurable promotion gates and guarded, reversible rewrites with rollback protection.
    • Added durable message logging with access controls and write-ahead action gating.
    • Added runtime capture and hot-swappable rewrite integration.
  • Documentation
    • Expanded architecture, configuration, harness, training, and rewrite guidance.
  • Breaking Changes
    • Simplified harness callbacks and replaced legacy agent, promotion, and instrumentation APIs with updated interfaces.

The training package defined MethodWeaver and SourcePromoter ports that
structurally mirrored the rewrite API, so weaving, dispatch, hot-swap,
and source-splicing concepts leaked into a package that should know
nothing about them. Delete ports.ts and leave training with two seams in
its own vocabulary:

- captureTrainable(id, method, ...) routes one marked-method call through
  runtime capture; how calls get intercepted is not training's concern.
- TrainingProviders.promote (PromotionApplier) applies a gate-approved
  candidate and returns how to undo it; activation checks the gate and
  delegates. Activation no longer exposes rewrite's PromotionResult.

The root package is now the only place the siblings meet: it points the
rewrite interceptor at captureTrainable (configureRewriteCapture), ships
the digest-guarded applier with hot-swap (rewritePromotion), and hosts
the trainable decorator plus load-time instrumentation helpers, which
moved out of training.

Also give the engine a real class: CandidateEngine owns request
validation, implementation cleanup, TypeScript validation, and candidate
identity as private methods, composing the injected TrainingEngine
strategy. The pipeline formerly exposed as free functions
(optimizeCandidate and friends) is internal; overrides remain pure
composition.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NG6fSKqgt8nD7JnEY9q245
@coderabbitai

coderabbitai Bot commented Jul 12, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

@Tyler-R-Kendrick, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 46 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0d0eb96f-768c-4943-a204-5cc628bf864d

📥 Commits

Reviewing files that changed from the base of the PR and between 78946f8 and 4b3e9ad.

📒 Files selected for processing (2)
  • packages/harness/src/dispatch.ts
  • packages/training/src/loop.ts
📝 Walkthrough

Walkthrough

The PR refactors training into observable fan-out rounds, replaces role-based harness dispatch with a gated append-only message bus, introduces guarded rewrite commit/revert APIs and composable promotion gates, and rewires root-package capture and promotion providers.

Changes

Training runtime and rewrite lifecycle

Layer / File(s) Summary
Candidate, gate, and rewrite lifecycle
packages/rewrite/src/*, packages/training/src/engine.ts, packages/training/src/promotion.ts, packages/rewrite/test/*, packages/training/test/promotion.test.ts
Candidate generation uses CandidateEngine; promotion uses shared gates; rewrites support guarded commit and rollback snapshots.
Observable training loop and runtime providers
packages/training/src/loop.ts, packages/training/src/training.ts, packages/training/test/*, README.md, docs/architecture.md
Training rounds support fan-out, deduplication, cancellation, terminal outcomes, custom gates, and provider-based activation.
Write-ahead bus and gated actor execution
packages/harness/src/*, packages/harness/test/*, packages/harness/README.md
Harness actions are recorded as append/read messages, gated before execution, and tested across actor, sandbox, access-policy, and failure paths.

Harness integration

Layer / File(s) Summary
Capture, promotion, and public package wiring
src/*, test/instrumentation.test.ts, packages/training/test/wiring.ts, packages/rewrite/test/*
Root wiring connects rewrite capture and promotion providers, while standalone instrumentation helpers and updated exports replace prior cross-package ports.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 51.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is clearly related to the PR’s main refactor themes: package boundaries, candidate engine, fan-out training loop, and rule-based gating.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/interface-redundancies-consolidate-8juzw3

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…romotion gate

Loop: the default loop is now built on trainingRounds(), a cold
observable round sequence — each subscription drives its own run,
reviewed rounds are pushed to the observer as they settle, and
unsubscribing aborts in-flight proposals and reviews through a derived
AbortSignal. sequentialLoop collects the subscription into one
TrainingLoopRun. Within a round, TrainingLoopInput.fanOut (and
TrainInput.fanOut) caps how many candidate propose→review pipelines run
concurrently: slots pipeline independently, duplicate proposals are
skipped rather than re-reviewed, a round that reviews nothing new stalls
the run, and when several candidates pass the gate the highest-scoring
one is emitted last as the winner. ProposalTurn gains the 1-based slot
so strategies can diversify concurrent proposals.

Promotion: the gate is a rule set instead of cascading if statements.
Each PromotionGate is a pure function over one shared
PromotionGateContext (candidate, candidate-bound results, thresholds,
aggregates) returning the failures it sees. The standard invariants live
in defaultPromotionGates and always run; PromotionGateInput.gates (and
TrainInput.gates) appends extension rules, and the configured policy
participates as one more gate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NG6fSKqgt8nD7JnEY9q245
@Tyler-R-Kendrick Tyler-R-Kendrick changed the title Remove rewrite/AOP knowledge from the training package; real engine class Training package boundaries: no AOP knowledge, engine class, observable fan-out loop, rule-based gate Jul 12, 2026
ts-autocode-rewrite is abstract AOP and guarded source rewriting for
TypeScript; it carried training vocabulary anyway. promotion.ts defined
RewriteApproval (a structural subset of training's PromotionDecision)
and enforced the promotion gate inside promoteCandidate, and
RewriteCandidate carried a trainableId. All of it is gone:

- commitRewrite(source, candidate) applies a candidate and records a
  RewriteSnapshot (rewriteId/targetId/updated); revertRewrite restores
  it. Whether a candidate deserves committing is the consumer's call —
  no decision parameter, no gate. promotion.ts is deleted; the guarded
  replacement lives with applyCandidate in apply.ts.
- RewriteCandidate is { id, target, implementation }; error messages and
  comments no longer mention trainables or training, and the rewrite
  package's own tests use neutral markers.

The root package — the consumer — wires training promotion into its
rewrite usage: rewritePromotion now enforces the gate correspondence
(decision.promote and candidateId match) before committing, exactly
where training's decision meets the guarded rewrite. The training test
wiring mirrors the same check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NG6fSKqgt8nD7JnEY9q245
@Tyler-R-Kendrick Tyler-R-Kendrick changed the title Training package boundaries: no AOP knowledge, engine class, observable fan-out loop, rule-based gate Package boundaries: training knows no AOP, rewrite knows no training; engine class, observable fan-out loop, rule-based gate Jul 12, 2026
…rness

The bus knew far too much: it enumerated the four roles, held an
ActionJudge, gated every dispatch through setJudge, and kept a private
judgeControl bootstrap path because a judge could not approve its own
actions. Now WriteAheadAgentBus is a plain durable append-only message
log: append({actor, kind, payload}) records a message with identity,
ordering, and time; read(actor?) returns the trailing window; an
optional allow hook decides whether an append or read may proceed. The
bus attaches no meaning to any actor.

The write-ahead convention moved into dispatchAction (dispatch.ts),
layered on top of the bus: record intent, ask the gate, record the
verdict — the judge is just another actor and its decision is an
ordinary agent.decision message — execute only after a pass, record the
outcome. defineTrainingHarness dispatches every student, teacher, and
adversary invocation through it with the run's judge callback as the
gate; MxcSandbox takes an actor string and an optional gate instead of
a role. No special judge path exists anywhere.

The harness also no longer creates agents: agents.ts (createTrainingAgents
with per-role Deep Agent models, system prompts, and role wiring) is
removed along with parseJudgeDecision. Callbacks are the whole contract,
which is what the documentation already claimed the harness was about.
The harness README, root README, and architecture doc are rewritten to
describe exactly this surface.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NG6fSKqgt8nD7JnEY9q245

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/training/src/loop.ts (1)

103-117: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Optional: harden the floating drive promise against throwing observer callbacks.

drive() routes internal errors to observer.error, but the wrappers here call observer.next?. / observer.complete?. / observer.error?. directly. Since drive is launched as void drive(...), a throwing user-supplied callback (public trainingRounds API) surfaces as an unhandled rejection rather than being contained. sequentialLoop's own callbacks are safe, so this only matters for external subscribers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/training/src/loop.ts` around lines 103 - 117, Harden the observer
callback wrappers in the floating drive invocation within trainingRounds so
exceptions from external observer.next, observer.complete, or observer.error
callbacks are contained rather than becoming unhandled rejections. Preserve the
existing closed checks and settle behavior, while ensuring each user-supplied
callback invocation is safely guarded.
packages/harness/src/index.ts (1)

107-119: 🚀 Performance & Scalability | 🔵 Trivial

Every actor dispatch now also triggers a judge call for subject: "action".

Student/teacher/adversary calls are now all routed through the same gate, so a round can invoke input.judge up to several times (once per actor dispatch, plus the existing candidate/adversary decisions) instead of just the candidate/adversary decisions. If judge is an expensive callback (e.g. an LLM call), custom implementations should short-circuit subject === "action" the way the reference createHarnessLoop provider does, or this refactor can noticeably increase judge invocation volume/cost per round.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/harness/src/index.ts` around lines 107 - 119, Update the judge
handling in the harness loop around the action gate and actor dispatches so
action-level evaluations do not invoke expensive judge logic by default;
short-circuit requests with subject === "action" while preserving the existing
candidate and adversary decision evaluations. Ensure custom judge behavior
follows the reference createHarnessLoop provider’s action handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/harness/src/dispatch.ts`:
- Around line 41-50: Update dispatchAction’s error handling so failures while
appending .failed or .completed records never replace the original execution or
gate outcome. Keep execute() failures classified as execution failures, but
preserve and rethrow the original gate error when the gate failure record cannot
be appended; when execute() succeeds, retain and return its computed result even
if recording completion fails.

---

Nitpick comments:
In `@packages/harness/src/index.ts`:
- Around line 107-119: Update the judge handling in the harness loop around the
action gate and actor dispatches so action-level evaluations do not invoke
expensive judge logic by default; short-circuit requests with subject ===
"action" while preserving the existing candidate and adversary decision
evaluations. Ensure custom judge behavior follows the reference
createHarnessLoop provider’s action handling.

In `@packages/training/src/loop.ts`:
- Around line 103-117: Harden the observer callback wrappers in the floating
drive invocation within trainingRounds so exceptions from external
observer.next, observer.complete, or observer.error callbacks are contained
rather than becoming unhandled rejections. Preserve the existing closed checks
and settle behavior, while ensuring each user-supplied callback invocation is
safely guarded.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ba79a8a3-363f-4c93-8a82-47b89b4fdcab

📥 Commits

Reviewing files that changed from the base of the PR and between 62a9065 and 78946f8.

📒 Files selected for processing (38)
  • README.md
  • docs/architecture.md
  • packages/harness/README.md
  • packages/harness/src/agents.ts
  • packages/harness/src/bus.ts
  • packages/harness/src/dispatch.ts
  • packages/harness/src/index.ts
  • packages/harness/src/sandbox.ts
  • packages/harness/test/harness.test.ts
  • packages/rewrite/README.md
  • packages/rewrite/src/apply.ts
  • packages/rewrite/src/aspect.ts
  • packages/rewrite/src/index.ts
  • packages/rewrite/src/instrument.ts
  • packages/rewrite/src/promotion.ts
  • packages/rewrite/test/apply.test.ts
  • packages/rewrite/test/instrument.test.ts
  • packages/rewrite/test/weaving.test.ts
  • packages/training/README.md
  • packages/training/src/engine.ts
  • packages/training/src/index.ts
  • packages/training/src/loop.ts
  • packages/training/src/ports.ts
  • packages/training/src/promotion.ts
  • packages/training/src/source.ts
  • packages/training/src/training.ts
  • packages/training/test/engine.test.ts
  • packages/training/test/evaluation.test.ts
  • packages/training/test/loop.test.ts
  • packages/training/test/promotion.test.ts
  • packages/training/test/training.test.ts
  • packages/training/test/wiring.ts
  • src/index.ts
  • src/instrumentation.ts
  • src/providers/harness.ts
  • src/providers/rewrite.ts
  • src/register.ts
  • test/instrumentation.test.ts
💤 Files with no reviewable changes (3)
  • packages/rewrite/src/promotion.ts
  • packages/training/src/ports.ts
  • packages/harness/src/agents.ts

Comment thread packages/harness/src/dispatch.ts Resolved
…vers

Review follow-ups on the write-ahead dispatch and the observable loop:

- dispatchAction: a successful execute() whose .completed append fails
  was recorded as .failed and its result discarded. Execution now has
  its own catch; the completion record is appended outside it, so a
  failing append surfaces as a bus error, never as a failed action.
  Failure-record appends in the gate and execution catches are
  best-effort so they cannot mask the original error.
- trainingRounds: subscriber callbacks are guarded — the drive promise
  floats, so a throwing observer.next/complete/error would otherwise
  become an unhandled rejection.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NG6fSKqgt8nD7JnEY9q245
@Tyler-R-Kendrick
Tyler-R-Kendrick merged commit 5abbf34 into main Jul 12, 2026
3 checks passed
@Tyler-R-Kendrick
Tyler-R-Kendrick deleted the claude/interface-redundancies-consolidate-8juzw3 branch July 12, 2026 18:29
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.

2 participants