Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
141 commits
Select commit Hold shift + click to select a range
f2b820f
docs(adaptation): add adaptation specification
senamakel Aug 29, 2026
1c7f1cf
Merge remote-tracking branch 'origin/main' into loop-adaptation
senamakel Aug 29, 2026
df9e730
docs: link adaptation specification in documentation
senamakel Aug 29, 2026
d6b8f17
Merge remote-tracking branch 'origin/main' into loop-adaptation
senamakel Aug 29, 2026
3429643
docs: add ADR for thresholds addressed from run state
senamakel Aug 29, 2026
8a8a6c5
docs: amend threshold addressing invariant
senamakel Aug 29, 2026
df47c13
docs(adaptation): clarify cadence and bounds ownership
senamakel Aug 29, 2026
a0b3384
docs: clarify adaptation parity bounds and compile-fail coverage
senamakel Aug 29, 2026
23e416c
docs: clarify loop iteration cap constraint
senamakel Aug 29, 2026
bc062bd
docs(adaptation): clarify threshold validation coverage
senamakel Aug 29, 2026
52be351
docs(plans): add adaptation plan
senamakel Aug 29, 2026
1eb5fed
docs: add adaptation tuning plan
senamakel Aug 29, 2026
f9c52ec
feat(policy): expose loop profiles
senamakel Aug 29, 2026
f6664a1
feat(presets): support serde serialization for preset names
senamakel Aug 29, 2026
b2c5008
feat(api): expose LoopProfile publicly
senamakel Aug 29, 2026
d90d4c0
fix(state): preserve loop profile when cloning state
senamakel Aug 29, 2026
4b1a762
feat(state): add profile-aware loop state constructor
senamakel Aug 29, 2026
760774e
refactor(ladder): address thresholds from run state
senamakel Aug 29, 2026
8278927
feat(policy): resolve terminal thresholds from state
senamakel Aug 29, 2026
ab4facd
refactor(policy): remove thresholds from ladder evaluation
senamakel Aug 29, 2026
5d51f37
refactor(policy): use state profiles for threshold decisions
senamakel Aug 29, 2026
f5f7370
refactor(loops): remove thresholds from policy evaluation
senamakel Aug 29, 2026
d951d6e
chore: files changed crates/tinyloops/src/presets/assembled.rs
senamakel Aug 29, 2026
0c3cd9d
fix(step): derive thresholds from loop state
senamakel Aug 29, 2026
f051eb8
feat(loops): seed profiles and configure iteration caps
senamakel Aug 29, 2026
206f08c
refactor(tinyloops): simplify loop builder and registry calls
senamakel Aug 29, 2026
d563c45
fix(assembled): stop passing thresholds to merge step
senamakel Aug 29, 2026
01dccb6
chore(tinyloops): remove unused threshold imports
senamakel Aug 29, 2026
bf2a2dd
test(step): update calls for simplified execution API
senamakel Aug 29, 2026
4ec17ce
test(step): verify steps use state thresholds
senamakel Aug 29, 2026
5571605
test(tinyloops): broaden routing parity coverage
senamakel Aug 29, 2026
ff4996d
test(tinyloops): expand routing parity coverage across threshold space
senamakel Aug 29, 2026
ae642d1
test: use state thresholds in parity checks
senamakel Aug 29, 2026
2f72590
test(policy): verify runtime threshold lookup
senamakel Aug 29, 2026
e5f98b2
test(policy): update tests for threshold-free policy API
senamakel Aug 29, 2026
fff90b4
test(policy): import loop profile in policy tests
senamakel Aug 29, 2026
1b80aad
test(loops): update graph signature and resume tests
senamakel Aug 29, 2026
3b4c424
test(loops): import LoopProfile for loop tests
senamakel Aug 29, 2026
a84e6f5
test(tinyloops): adapt loop fixtures to loop profiles
senamakel Aug 29, 2026
fef9dcd
test: update loop execution tests for profile-based thresholds
senamakel Aug 29, 2026
1f0a20b
chore: files changed crates/tinyloops/src/loops/test.rs,crates/tinylo…
senamakel Aug 29, 2026
f8774a7
test: remove unused thresholds from e2e step harness
senamakel Aug 29, 2026
d38c322
refactor(tinyloops): update tests for the loop profile API
senamakel Aug 29, 2026
8522717
test: update termination condition API calls
senamakel Aug 29, 2026
dd4493f
test(tinyloops): update termination tests for new API
senamakel Aug 29, 2026
e692307
test: update assertions for profile-aware routing
senamakel Aug 29, 2026
a61aa25
chore(tinyloops): remove unused threshold setup from tests
senamakel Aug 29, 2026
b498bfb
fix(tinyloops): update threshold access in research example
senamakel Aug 29, 2026
198700f
test: pin serialized profile state
senamakel Aug 29, 2026
80ca48f
test(loops): verify thresholds remain in the accumulator
senamakel Aug 29, 2026
f8cc2bb
test(tinyloops): access loop head config directly
senamakel Aug 29, 2026
89dc610
docs(tinyloops): update examples for state-owned thresholds
senamakel Aug 29, 2026
a4d1c02
style: format tinyloops code with rustfmt
senamakel Aug 29, 2026
f1e17a3
refactor(tinyloops): simplify policy string formatting
senamakel Aug 29, 2026
635f425
chore: files changed crates/tinyloops/src/loops/builder.rs
senamakel Aug 29, 2026
97b3fb3
fix(budget): allow presets to reach attempt ceilings
senamakel Aug 29, 2026
a99eb5a
test(tinyloops): pin profile and preset serialization behavior
senamakel Aug 29, 2026
ea725ce
test(tinyloops): cover profile-based routing API
senamakel Aug 29, 2026
786253d
docs(adaptation): clarify threshold state and graph invariants
senamakel Aug 29, 2026
eb7eb7f
docs(loop-kernel): clarify threshold addressing and mark plan impleme…
senamakel Aug 29, 2026
312e8a2
feat(policy): address thresholds from run state
senamakel Aug 29, 2026
253fc29
feat(policy): add amendment handling
senamakel Aug 29, 2026
b479a5d
feat(policy): add bounds policy module
senamakel Aug 29, 2026
add7c42
feat(policy): support profile amendments
senamakel Aug 29, 2026
bb81d36
fix(tinyloops): validate loop tuning amendments
senamakel Aug 29, 2026
9e639be
fix: clone profiles before reuse
senamakel Aug 29, 2026
24c3411
test(routing): update loop profile construction in parity tests
senamakel Aug 29, 2026
aec3d16
test(routing): clone profile for parity state setup
senamakel Aug 29, 2026
64a8eac
feat(presets): add revision bounds for shipped presets
senamakel Aug 29, 2026
ec1990f
feat(state): expose proposed amendments
senamakel Aug 29, 2026
f70ad13
feat(state): propagate proposed amendments through contributions
senamakel Aug 29, 2026
1d51b7d
feat(arm): add run profile tuning support
senamakel Aug 29, 2026
7ebd4db
feat(arm): expose tuner arm types
senamakel Aug 29, 2026
0a5aba9
feat(policy): record amendment verdicts and enforce bounds
senamakel Aug 29, 2026
60e9804
feat(tinyloops): fold bounded amendments at pass boundaries
senamakel Aug 29, 2026
c401b4a
fix(presets): configure research advance bounds
senamakel Aug 29, 2026
e0c4eac
test(presets): use default Advance in pass assertion
senamakel Aug 29, 2026
92a0435
test(tinyloops): cover amendment round trips and proposed state
senamakel Aug 29, 2026
37b7a92
fix(tinyloops): run pass step and enforce amended caps
senamakel Aug 29, 2026
dfc8b7a
feat(observe): record configuration amendments and refusals
senamakel Aug 29, 2026
c03fe15
feat(observe): render amendment events
senamakel Aug 29, 2026
5c60438
chore(tinyloops): import the pass step constant
senamakel Aug 29, 2026
6a32124
test(tinyloops): update wire format expectations
senamakel Aug 29, 2026
6d93230
feat(presets): add tuner preset rules
senamakel Aug 29, 2026
22b56e8
feat(tinyloops): add tuned research loop
senamakel Aug 29, 2026
90820dd
feat(tinyloops): expose the completed loop profile
senamakel Aug 29, 2026
5777669
feat(orchestrate): show revision history in summaries
senamakel Aug 29, 2026
25cc294
test(observe): cover all event variants in fixture
senamakel Aug 29, 2026
9c1d5ab
test(error): verify amendment refusal messages
senamakel Aug 29, 2026
2013656
test(policy): cover amendment bounds and profile folding
senamakel Aug 29, 2026
fefd226
test(arm): cover tuner exclusivity and amendment merging
senamakel Aug 29, 2026
0c72dc1
test(presets): cover tuner adaptation and convergence behavior
senamakel Aug 29, 2026
817da30
fix(tinyloops): import tuned research loop test dependencies
senamakel Aug 29, 2026
6bb9a76
test(tinyloops): cover tuned run revisions and reporting
senamakel Aug 29, 2026
a321c56
test(tinyloops): cover repeated machinery failures
senamakel Aug 29, 2026
d96a3bd
test(tinyloops): cover spend reduction after repeated failures
senamakel Aug 29, 2026
1f71bad
docs(arm): document exclusive tuner proposal access
senamakel Aug 29, 2026
4ab66ee
style(tinyloops): format Rust code with rustfmt
senamakel Aug 29, 2026
740d56e
refactor(bounds): extract numeric bound check helper
senamakel Aug 29, 2026
e6f73ca
refactor(observe): split event fixture into smaller helpers
senamakel Aug 29, 2026
3e2caaf
test(policy): verify field and verdict rendering behavior
senamakel Aug 29, 2026
94c0f19
docs(presets): clarify loop variants and tuning rules
senamakel Aug 29, 2026
107e6ae
test(tinyloops): cover the public adaptation API
senamakel Aug 29, 2026
2881205
docs: mark adaptation documents as implemented
senamakel Aug 29, 2026
f71bac1
docs: document the tuned research loop
senamakel Aug 29, 2026
aa47b34
test(tinyloops): verify raised attempt ceilings enable retries
senamakel Aug 29, 2026
c5ee7c2
docs: document deliberate adaptation plan deviations
senamakel Aug 29, 2026
8ea5f26
chore(tinyloops): update tuner preset
senamakel Aug 29, 2026
8b17c39
chore(tinyloops): update tuner preset
senamakel Aug 29, 2026
8b73144
chore(tuner): update tuner preset
senamakel Aug 29, 2026
414c95c
chore(tinyloops): update tuner preset
senamakel Aug 29, 2026
3e85870
chore: update assembled preset
senamakel Aug 29, 2026
a773f6d
chore: update assembled preset
senamakel Aug 29, 2026
0fd7f33
chore(tinyloops): update assembled presets
senamakel Aug 29, 2026
0acdc11
chore: update assembled preset definitions
senamakel Aug 29, 2026
5aaa028
chore: update error module
senamakel Aug 29, 2026
0861303
chore: update tinyloops step presets
senamakel Aug 29, 2026
762299b
test(tinyloops): update test preset
senamakel Aug 29, 2026
f082e1e
refactor(policy): update profile handling
senamakel Aug 29, 2026
fe6b39e
chore(policy): update profile policy implementation
senamakel Aug 29, 2026
6e75a24
fix(arm): stamp tuner amendment provenance
senamakel Aug 29, 2026
34b89ff
chore(observe): update observation types
senamakel Aug 29, 2026
41d64f3
fix(tinyloops): initialize default proposal rules
senamakel Aug 29, 2026
7c51652
docs(tinyloops): document configurable silence window
senamakel Aug 29, 2026
fe9b295
docs(tinyloops): clarify configured silence rule wiring
senamakel Aug 29, 2026
e6e2373
perf(arm): reuse amendment proposer allocation
senamakel Aug 29, 2026
bca8b0f
refactor: simplify declared arm lookup
senamakel Aug 29, 2026
9aeaec9
refactor(tuner): consume rules in helper methods
senamakel Aug 29, 2026
0b4c97f
test(observe): classify amendment events as spine events
senamakel Aug 29, 2026
b9d7545
test(tinyloops): cover rejection of ineligible mutable arms
senamakel Aug 29, 2026
8c5a828
docs: clarify checkpoint graph signature addressing
senamakel Aug 29, 2026
6f4a948
docs: clarify invariant 9 adaptation
senamakel Aug 29, 2026
8bb33e6
docs(adaptation): clarify shared routing program goal
senamakel Aug 29, 2026
9351ccd
test(policy): cover refusal of conflicting cap amendments
senamakel Aug 29, 2026
040e36c
test(error): cover rendering of mutable arm errors
senamakel Aug 29, 2026
6a8cb5a
test(policy): account for amendments in budget contention test
senamakel Aug 29, 2026
f301cce
test(policy): assert unchanged caps after refused amendment
senamakel Aug 29, 2026
552d4a0
style(policy): format test assertions
senamakel Aug 29, 2026
96aaa02
chore: raise minimum supported Rust version
senamakel Aug 29, 2026
cbfa345
refactor(budget): express run timeout in minutes
senamakel Aug 29, 2026
2afe3b3
style(budget): use duration helpers in budget tests
senamakel Aug 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ code:
- [`workspace-and-ledgers.md`](docs/specs/workspace-and-ledgers.md) — what a run
writes down and where
- [`budget.md`](docs/specs/budget.md) — the limits every run carries
- [`adaptation.md`](docs/specs/adaptation.md) — what a run may revise about
itself mid-run, who may propose it, and what bounds it
- [`observability.md`](docs/specs/observability.md) — the events a run emits
- [`prior-art.md`](docs/specs/prior-art.md) — the evidence each default rests
on, what the record contradicts, and what it does not settle; its sources are
Expand Down
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ exclude = ["vendor", "worktrees"]
[workspace.package]
version = "0.2.1"
edition = "2024"
rust-version = "1.88"
# The floor every optional feature must clear too: the MSRV CI job builds
# `--all-features`, and `vendor/tinyagents` (behind the `tinyagents` feature)
# uses `if let` guards, stable since 1.95 and rejected as experimental below
# it. A lower number here would be a number the CI job itself disproves.
rust-version = "1.95"
license = "GPL-3.0-only"
repository = "https://github.com/tinyhumansai/tinyloops"

Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,13 @@ the reference seams, drives it to a terminal state, and prints every pass
boundary, every step, every arm, the merge, the verdict, and the route each pass
took with the counters it was taken on.

`tuned_research_loop` is the same loop with a third arm that may revise the
run's own configuration — its thresholds, its spend, and which arms it is still
paying for — within the room its preset declares. Every revision and every
refusal is an event and a line in the report; nothing here scores them, because
scoring a configuration against outcomes spans runs and lives in
`tinyflows-adaptive`.

Every example runs against TinyFlows' mock capabilities or the reference
implementations, so they are deterministic, offline, and need no provider
credentials. `tinyagents` is optional: the harness example declares
Expand Down
2 changes: 1 addition & 1 deletion crates/tinyloops/examples/research_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ fn main() -> Result<(), Error> {
)?;

println!("preset: {}", assembled.preset());
println!("stuck at: {}", assembled.thresholds().stuck);
println!("stuck at: {}", assembled.profile().thresholds.stuck);
println!("signature: {}\n", assembled.signature()?.as_str());

// A recorder over the console sink: one line per event, in one ordered
Expand Down
2 changes: 1 addition & 1 deletion crates/tinyloops/src/arm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@

mod types;

pub use types::{Arm, ArmOutcome, ArmSet, Edge};
pub use types::{Arm, ArmOutcome, ArmSet, Edge, Tuner, TunerArm};

use crate::state::LoopState;
use crate::{Error, Result};
Expand Down
139 changes: 139 additions & 0 deletions crates/tinyloops/src/arm/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,3 +484,142 @@ fn debug_rendering_names_the_declared_arms() {
assert!(rendered.contains("judge"));
assert!(format!("{:?}", Edge::new("attempt", "judge")).contains("attempt"));
}

/// A tuner that proposes whatever it was constructed with.
#[derive(Debug)]
struct Fixed {
name: &'static str,
change: Option<crate::policy::Change>,
}

impl crate::arm::Tuner for Fixed {
fn name(&self) -> &'static str {
self.name
}

fn propose(
&self,
_base: &LoopState,
_report: &Value,
ctx: StepContext<'_, NoWrite>,
) -> Result<Option<crate::policy::Amendment>> {
Ok(self
.change
.clone()
.map(|change| crate::policy::Amendment::new(self.name, ctx.pass(), change, "because")))
}
}

fn tuner(name: &'static str, change: Option<crate::policy::Change>) -> Arc<dyn Arm> {
Arc::new(crate::arm::TunerArm::new(Arc::new(Fixed { name, change })))
}

#[test]
fn two_tuners_in_one_arm_set_are_refused_by_name() {
// The same shape as the second concluding arm, and for the same reason:
// two proposers means the profile a pass folds depends on which finished
// first, which is arrival order deciding the run's configuration.
let error = ArmSet::new(vec![tuner("tune", None), tuner("also_tune", None)])
.expect_err("two tuners are refused");

assert_eq!(
error,
Error::AmbiguousTuning {
first: "tune",
second: "also_tune",
}
);
}

#[test]
fn a_set_names_its_tuner_and_a_set_without_one_names_nobody() {
let tuned = ArmSet::new(vec![tuner("tune", None)]).expect("one tuner is a valid set");
assert_eq!(tuned.tuning(), Some("tune"));
assert!(tuned.concluding().is_none());
}

#[test]
fn a_tuner_that_proposes_nothing_folds_as_unchanged() {
let base = LoopState::new("goal");
let arm = tuner("tune", None);
let thresholds = crate::Thresholds::default();

let outcome = arm
.evaluate(&base, &Value::Null, StepContext::observing(0, &thresholds))
.expect("a tuner that declines still returns");

assert_eq!(outcome.state, base);
assert!(outcome.contribution.is_empty());
assert!(outcome.state.proposed().is_none());
}

#[test]
fn a_tuner_that_proposes_puts_it_where_the_merge_reads_it() {
let base = LoopState::new("goal");
let change = crate::policy::Change::Threshold {
field: crate::policy::ThresholdField::Stuck,
to: 3,
};
let arm = tuner("tune", Some(change.clone()));
let thresholds = crate::Thresholds::default();

let outcome = arm
.evaluate(&base, &Value::Null, StepContext::observing(2, &thresholds))
.expect("a tuner proposes");

// Both halves: the claim, for the merge, and the state, for the trip
// through the graph. They have to agree or the proposal is dropped at the
// barrier with nothing to report it.
assert_eq!(
outcome.contribution.amendment().map(|a| a.change.clone()),
Some(change.clone())
);
assert_eq!(
outcome.state.proposed().map(|a| a.change.clone()),
Some(change)
);
assert_eq!(outcome.state.proposed().map(|a| a.pass), Some(2));
}

#[test]
fn merge_refuses_two_arms_proposing_an_amendment() {
// The tuner is the only role that *can* propose, but the merge still
// arbitrates: exclusive ownership is a property of the fold, not a promise
// about who calls it.
let base = LoopState::new("goal");
let change = crate::policy::Change::Threshold {
field: crate::policy::ThresholdField::Stuck,
to: 3,
};
let thresholds = crate::Thresholds::default();

let first = tuner("tune", Some(change.clone()));
let second = tuner("also_tune", Some(change));
let outcomes = vec![
first
.evaluate(&base, &Value::Null, StepContext::observing(0, &thresholds))
.unwrap(),
second
.evaluate(&base, &Value::Null, StepContext::observing(0, &thresholds))
.unwrap(),
];

let set = ArmSet::new(vec![tuner("tune", None)]).unwrap();
let deltas: Vec<_> = outcomes
.iter()
.map(|outcome| outcome.state.delta_from(&base))
.collect();
let contributions: Vec<_> = outcomes
.into_iter()
.map(|outcome| outcome.contribution)
.collect();
let _ = set;

assert!(matches!(
base.merge(&deltas, &contributions),
Err(Error::ContestedField {
field: "amendment",
..
})
));
}
Loading