Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added
- Provider failover chain, core (#209): `failover::FailoverProvider` retries a step on the next provider of an ordered chain when the active one is unavailable (connection failure, timeout, dropped stream, HTTP 5xx or 429); request errors (401/403/404/400, malformed responses) fail loud and never fail over. The answering provider stays active and the primary is retried after a cooldown. Each step records the route under `tole_route` in its usage row (durable, auditable); rows holding only tole's own keys are not counted as provider steps. The env/config wiring follows separately.
- Child-agent handoff schema and verify gate (#383): `tole_core::handoff` defines the typed handoff document (role `explore` / `implement` / `verify` / `fix`, artifact references by path and rev, verification evidence), strict validation of the model-authored input (workspace-relative paths, option-safe refs, bounded fields), the close gate (an implement/fix child without passing verification is `Unverified` / `VerifyFailed`, never closed silently) and the prompt for the follow-up `fix` child. `agent_start` takes `role` and `consumes` (artifact references the child reads itself), a role child gets a `handoff_submit` tool, and `agent_poll` reports the gate and a ready-made `fix_prompt` (the parent decides whether to start the fix child; nothing is spawned automatically). Design and anti-goals (no DAG engine) in `docs/child-agents-handoff.md`.
- Sibling change notices, core (#380): `sibling_watch` tracks which files each child agent reads and writes in one shared append-only log (owner-only, bounded) and tells a child, once per stale path, when a sibling changed a file it had read or written; the `Tracked` tool decorator records `read_file` / `write_file` / `edit_file` / `delete_file` on success. Interrupts gain a `source`: a notice is delivered at the next safe point as an attributed entry (`source: notice`) and never withdraws output or supersedes a final answer. Wiring into `tole run` children follows separately.
- Sibling change notices (#380): `sibling_watch` tracks which files each child agent reads and writes in one shared append-only log (owner-only, bounded) and tells a child, once per stale path, when a sibling changed a file it had read or written; the `Tracked` tool decorator records `read_file` / `write_file` / `edit_file` / `delete_file` on success. Interrupts gain a `source`: a notice is delivered at the next safe point as an attributed entry (`source: notice`) and never withdraws output or supersedes a final answer. Children spawned by `agent_start` get `TOLE_AGENT_ID` and a shared `TOLE_AGENT_TOUCH_LOG`; in a child, `tole run` wraps its file tools and runs the watcher, so a sibling's change reaches the model at its next safe point.
Expand Down
29 changes: 22 additions & 7 deletions crates/tole-cli/src/usage_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,18 @@ pub struct UsageReport {
pub history_growth_per_step: Option<f64>,
}

/// True when the ledger row carries a provider-reported usage object,
/// i.e. it is NOT a wire-only row (`{"tole_wire": ...}` and nothing
/// else, stored when a provider reports request sizes but no usage).
/// The single definition of "provider step" for the mission step budget,
/// `fact/mission.steps` and the report, so wire-only rows never change
/// step accounting (#211).
/// True when the ledger row carries a provider-reported usage object, i.e.
/// it is NOT a row holding only tole's own namespaced keys (`tole_wire`,
/// stored when a provider reports request sizes but no usage, #211;
/// `tole_route`, the failover route, #209). The single definition of
/// "provider step" for the mission step budget, `fact/mission.steps` and
/// the report, so such rows never change step accounting.
pub fn reports_provider_usage(rec: &UsageRecord) -> bool {
match rec.usage.as_object() {
Some(o) => !(o.len() == 1 && o.contains_key("tole_wire")),
// Only tole's own namespaced keys (`tole_wire`, `tole_route`, ...):
// the provider reported nothing for this step.
// An empty object still counts (the pre-#211 contract).
Some(o) => o.is_empty() || o.keys().any(|k| !k.starts_with("tole_")),
None => true,
}
}
Expand Down Expand Up @@ -675,6 +678,18 @@ mod tests {
assert_eq!(t[0].input_tokens, Some(4));
}

#[test]
fn rows_with_only_tole_keys_are_not_provider_steps() {
let route_only = rec(json!({"tole_route": {"provider": "reserve"}}));
let wire_and_route =
rec(json!({"tole_wire": wire(1, 2, 3), "tole_route": {"provider": "p"}}));
let real = rec(json!({"prompt_tokens": 4, "tole_route": {"provider": "p"}}));
assert!(!reports_provider_usage(&route_only));
assert!(!reports_provider_usage(&wire_and_route));
assert!(reports_provider_usage(&real));
assert_eq!(provider_steps(&[route_only, wire_and_route, real]), 1);
}

#[test]
fn unknown_stays_unknown_never_zero() {
let entries = vec![user(1), assistant(2), user(3), assistant(4)];
Expand Down
Loading
Loading