Commit graph

2468 commits

Author SHA1 Message Date
Bryan Helmkamp
a78750fa97
Update attach snapshot and soften context_values wording
The preamble removal changed the `stage.completed` payload, so the
`attach --json` inline snapshot no longer matched. Drop the stale
`current.preamble` line.

Reword the `context_values` doc row. `stage_context_values` only strips
runtime-only keys; it does not normalize artifact pointers to blob refs
the way `artifact::durable_context_snapshot` does, so calling it a
durable snapshot overstated it. Point readers at `checkpoint.completed`
for the durable projection.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 09:26:13 -04:00
Bryan Helmkamp
bcf9b26b22 Remove prompt offloading from stage payload fix 2026-08-01 09:21:24 -04:00
Bryan Helmkamp
1884a4b072 Reduce large stage event payloads 2026-08-01 09:21:24 -04:00
Bryan Helmkamp
1d1894ccfe
Merge pull request #700 from fabro-sh/fix/canceled-parallel-duration-event-errors
Fix canceled parallel durations and event persistence logging
2026-08-01 09:21:20 -04:00
Scott Werner
e4541f8eb3 Regenerate TypeScript API client 2026-08-01 09:03:10 -04:00
Scott Werner
42dcb410f2 Remove obsolete manifest Docker image argument 2026-08-01 09:03:10 -04:00
Release Repro
8db771bbd0
Simplify parallel duration and event failure logging
The parallel stage summary rendered a Duration tile directly below
StageMetaBar, which already shows the same stage's duration with a live
ticking clock and a started-at tooltip. The two disagreed while running:
the meta bar counted up, the tile showed the static word "running". The
cancelled-stage bug lived only in the duplicate.

Drop the tile. The meta bar owns duration for every stage renderer, and
it was already correct for cancelled, pending and skipped stages. That
removes the three-way duration branch, the "--" sentinel decode, and the
ACTIVE_STAGE_STATES and formatDurationMs imports.

With the tile gone, ParallelOverview.durationMs is dead, as were
successCount, failureCount and isComplete — the renderer counts the
branch rows it draws. ParallelOverview reduces to branch identity.

For run event write failures, log the first at error with run_id and
event name, the rest at debug, and summarize new losses at flush. A
broken sink fails for every event, so a bare error would emit one
"investigate me" line per event for the life of the run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 08:57:47 -04:00
Bryan Helmkamp
f0f1baa813
Merge pull request #701 from fabro-sh/fix/quota-error-classification
fix(llm): classify exceeded quota errors
2026-08-01 08:42:57 -04:00
Bryan Helmkamp
bf913ec58e
Merge pull request #685 from fabro-sh/fabro/run/01KYQMV1VW6139EGNHEM1RGF2G
Reject invalid run events before write; clarify append commit status
2026-08-01 08:21:59 -04:00
Bryan Helmkamp
5111f0e556
Merge pull request #707 from swerner/fix/bedrock-tool-sanitization
Some checks are pending
Rust / Format (push) Waiting to run
Rust / Clippy (push) Waiting to run
Rust / Generated Docs (push) Waiting to run
Rust / Test (Linux) (push) Waiting to run
Rust / Test (macOS) (push) Waiting to run
TypeScript / Typecheck (push) Waiting to run
TypeScript / Test (push) Waiting to run
TypeScript / Build (push) Waiting to run
Sanitize Bedrock tool identifiers during encoding
2026-07-31 18:09:33 -04:00
Scott Werner
594bf6e632 refactor(llm): simplify Bedrock tool sanitization internals
- Return String from the sanitize helpers instead of Cow: every call
  site feeds the result into json!, which allocates anyway, so the
  borrowed fast path only cost extra branches and Cow-variant tests.
- Route all toolUse/toolResult construction through private
  tool_use_block/tool_result_block constructors that own the sanitize
  calls, so the toolUse/toolResult pairing invariant is enforced by
  construction rather than by call-site discipline.
- Drop a test assertion the type system already guarantees (encoding
  takes &Request, so it cannot mutate the input) and assert wiring
  tests against the sanitize helpers instead of re-pinning the exact
  replacement literals in a second file.

No wire-format changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 17:43:22 -04:00
Bryan Helmkamp
9ef8742d5d
Merge pull request #687 from fabro-sh/fabro/run/01KYQN78K19NY7PNSCDYP6CG9G
Extract a source-neutral run-compiler boundary in fabro-server
2026-07-31 17:11:33 -04:00
Scott Werner
913cb190b7 Sanitize Bedrock tool identifiers during encoding 2026-07-31 15:06:05 -04:00
Fabro
8282581ac7 fabro(01KYQN78K19NY7PNSCDYP6CG9G): verify (succeeded)
Fabro-Run: 01KYQN78K19NY7PNSCDYP6CG9G
Fabro-Completed: 10

⚒️ Generated with [Fabro](https://fabro.sh)
2026-07-31 14:14:25 -04:00
Fabro
bca63e75c6 fabro(01KYQN78K19NY7PNSCDYP6CG9G): simplify_sol (succeeded)
Fabro-Run: 01KYQN78K19NY7PNSCDYP6CG9G
Fabro-Completed: 7

⚒️ Generated with [Fabro](https://fabro.sh)
2026-07-31 14:09:32 -04:00
Fabro
76f61f163b fabro(01KYQN78K19NY7PNSCDYP6CG9G): simplify_fable (succeeded)
Fabro-Run: 01KYQN78K19NY7PNSCDYP6CG9G
Fabro-Completed: 6
Fabro-Checkpoint: 1254f4ca92f7b1f822cdfa7ca549ef8595a50fed

⚒️ Generated with [Fabro](https://fabro.sh)
2026-07-31 14:09:32 -04:00
Fabro
ac6e3ced6a fabro(01KYQN78K19NY7PNSCDYP6CG9G): implement (succeeded)
Fabro-Run: 01KYQN78K19NY7PNSCDYP6CG9G
Fabro-Completed: 5
Fabro-Checkpoint: 879008d9d5

⚒️ Generated with [Fabro](https://fabro.sh)
2026-07-31 14:09:32 -04:00
Release Repro
799fac1d3b
style(models): group DeepSeek price digits 2026-07-31 13:17:44 -04:00
Release Repro
40391ac3ac
fix(models): use exact DeepSeek context window 2026-07-31 13:17:08 -04:00
Release Repro
1016f995c4
chore(models): pin DeepSeek agent profile 2026-07-31 13:16:37 -04:00
Release Repro
76dae568f5
feat(reasoning): expose DeepSeek effort controls 2026-07-31 13:14:11 -04:00
Release Repro
f613821bfb
feat(llm): add direct DeepSeek provider 2026-07-31 13:10:08 -04:00
Release Repro
8864e0f0cf
feat(models): refresh DeepSeek V4 Flash metadata 2026-07-31 13:02:58 -04:00
Bryan Helmkamp
7e6d758fa0
fix(llm): classify exceeded quota errors 2026-07-31 12:59:59 -04:00
Release Repro
0afd3a43b6
feat(models): add portable DeepSeek aliases to Fireworks 2026-07-31 12:59:40 -04:00
Release Repro
8bae35398f
fix(models): point DeepSeek aliases to V4 Flash 2026-07-31 12:59:00 -04:00
Bryan Helmkamp
8881bcab3e
Log run event persistence failures as errors 2026-07-31 12:44:24 -04:00
Bryan Helmkamp
a0de8f422b
fix: materialize a fresh sandbox when resuming a forked run
A fork carries a checkpoint from its source run, but its first
run.created event contains only a sandbox plan. Resume previously tried
to reconnect that planned sandbox and failed because no instance
exists. Now a fork resume with a Planned sandbox record builds a fresh
sandbox instead; later fork resumes still reconnect the ready instance,
and a same-run resume with an uninitialized sandbox still fails the
precondition check.

Also consolidates the test module's three near-identical InitOptions
literals into a shared test_init_options helper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 16:18:23 -04:00
Bryan Helmkamp
6a1ec11f86
Fix forked run sandbox initialization 2026-07-30 16:05:18 -04:00
Bryan Helmkamp
d4119cee77
fix: keep stored run events from older releases deserializable
Copilot review flagged two backward-compatibility breaks with events
persisted by pre-model-keyed releases; both are stored data that can
never be rewritten, so accept the old shapes on read:

- FailoverProps: original_provider/original_model/attempt are Option
  again with serde defaults. New events always set them; failover events
  recorded before model-keyed fallbacks lack them. Restores the
  historical-event test.
- RunModelSettings: temporary custom deserializer accepts the legacy
  flat-array fallbacks shape inside stored run.created events, keying
  the chain under the requested model name when one is set. Remove once
  pre-0.311 run logs are out of the support window.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 13:24:08 -04:00
Bryan Helmkamp
84c1545812
docs: state the from-route semantics of agent.failover events
Copilot review flagged that a failover event's from route can be a
candidate that failed during activation and never served traffic. That
is intentional — events chain (one event's to is the next one's from)
so the stream records every candidate tried, with the error explaining
why each was abandoned. Document it at the emit site.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 13:00:17 -04:00
Bryan Helmkamp
e6eb36f852
refactor: simplify model-keyed fallback internals
Consolidation pass over the fallback feature, no intended behavior
changes beyond noted validation and event-shape cleanups:

- Unify the two parallel notice types: FallbackPlanNotice is gone;
  ModelFallbackNotice now owns the runtime NoNearbyReasoningLevel case
  and the shared ChainEmpty wording. Notices emit through a new
  Emitter::notice_scoped with their own level, and each distinct notice
  is emitted once per run instead of on every LLM call.
- Move canonical_model_id onto Catalog so chain keys are written and
  read through one function; reject provider-qualified fallback keys,
  which could never match at dispatch and were silently dead config.
- Type FallbackTarget as ProviderId/ModelId, removing repeated
  ProviderId::new re-wrapping at every use site.
- Derive FallbackPlan's current route from a position index instead of
  storing current/requested_controls copies; advance() no longer has
  unreachable None branches.
- Bundle the agent invocation's live state (session, bridge, lease,
  forwarder, accounting) into LiveAgentInvocation; failover_agent_session
  drops from 21 parameters to 7 and the six copies of the
  abort/discard/classify teardown collapse into two methods.
- Share one route_request builder between one_shot and its failover
  loop; complete_one_shot_request takes the request by value instead of
  deep-cloning the message payload per call.
- Event::Failover carries FailoverProps directly; the props' original
  route and attempt fields are now required, and reasoning efforts are
  typed ReasoningEffort instead of strings.
- Reuse RunModelSettings/RunModelControls in fabro-api via
  with_replacement, add the missing controls property to the OpenAPI
  schema, regenerate the TS client, and add the type-identity/JSON
  parity test.
- Smaller cleanups: ReasoningEffort::closest_supported uses enum
  discriminants; ModelFallbackPolicy gains len(); resolve_model_fallbacks
  takes a provider slice; duplicate-target filtering lives only in the
  resolver.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 12:51:36 -04:00
Bryan Helmkamp
ba82656656
feat: add model-keyed fallback policies 2026-07-30 12:02:41 -04:00
Bryan Helmkamp
2efcc72124
Merge pull request #684 from fabro-sh/feat/portable-model-fallbacks
feat(workflow): make model fallback chains portable
2026-07-30 06:42:53 -04:00
Bryan Helmkamp
8a597ca264
fix(workflow): report the real cause when a fallback cannot resolve
Addresses review feedback on the fallback notice work.

A provider-only fallback such as `openrouter` needs the primary model's
catalog entry to find the closest capability match. When the primary is
itself a passthrough selector there is no entry, so every provider-only
candidate was skipped with "provider `X` has no compatible model" even
when that provider had plenty. Adds a `PrimaryNotInCatalog` notice that
names the missing primary instead of blaming the provider.

Also from review:

- `code()` was a wildcard fallthrough, which docs/internal/events-strategy.md
  forbids for new variants. Now exhaustive.
- `NoConfiguredOffering` discarded the `providers` list that
  `NoEligibleOffering` hands it. The notice now names the providers that do
  offer the model.
- `ModelFallbackNotice::reference` was a rendered `String`; it is now the
  `ModelRef` it came from, which also drops the per-candidate double
  allocation the previous refactor introduced.
- `ResolvedStartLlm` unpacked and repacked `ResolvedFallbackChain`
  field-for-field; it now holds it directly.
- Added `FallbackTarget: Display` as `provider:model`, replacing two
  hand-written `"{}:{}"` format strings.
- `Catalog::select` still inlined the `require_provider` body.
- Emission moved to `ModelFallbackNotice::emit_all`, covered by a new test
  proving notices reach the event stream with the right level, code, and
  message. Nothing tested that hand-off before.

Documented in `resolve_fallback_chain` why an unknown provider stays a hard
error while an unconfigured one is skipped, and that an unqualified unknown
selector pins to the primary's provider.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 22:37:51 -04:00
Bryan Helmkamp
b5885b15dc
Merge pull request #686 from fabro-sh/fix/space-separated-node-classes
Fix space-separated node class parsing
2026-07-29 22:31:48 -04:00
Bryan Helmkamp
727cd52c3b
fix(types): trim class names in Node::add_class
The doc comment promised blank names were ignored, but the guard only
rejected the empty string. Stylesheet selectors match class names exactly,
so a padded name would sit in `classes` and match no rule.

No current caller can pass one: the parser splits on whitespace, and the
subgraph and import paths strip everything but alphanumerics and hyphens.
This makes the public contract on the shared type match what it claims.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 22:25:42 -04:00
Bryan Helmkamp
48fd09aaab
refactor(workflow): simplify fallback chain resolution
Follow-up cleanup on the portable fallback chain work.

- Add `Catalog::require_provider` and `Catalog::provider_id`, replacing the
  `catalog_provider_id` free function in `start.rs` and two copies of the same
  `provider(..).ok_or_else(UnknownProvider)` block inside the catalog.
- Add `FallbackTarget::new` and use it for the six struct literals that each
  stringified a provider and model by hand.
- Extract per-candidate resolution into `resolve_fallback_candidate`, returning
  a `FallbackCandidate` that is either a target or the skip reason. This flattens
  `resolve_fallback_chain` from four levels of nesting to one loop and splits the
  qualified/unqualified model arms into separate match patterns.
- Drop the `seen` HashSet and its per-candidate key clones in favor of a
  `contains` check on the chain being built; fallback chains hold a handful of
  entries.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 22:23:25 -04:00
Bryan Helmkamp
01c4a4a23b
test(graphviz): table-drive the class separator cases
One row per separator rule, so a regression names the input that broke
instead of pointing at a combined fixture string. Also record why
`add_class` keeps insertion order: `fidelity` falls back to the first class
for the thread ID.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 22:17:37 -04:00
Bryan Helmkamp
f932a0763b
refactor(graphviz): simplify stylesheet comment stripping
Replace the hand-rolled byte scanner in strip_css_comments with a
str::find loop over "/*" and "*/".

Drop the quote and backslash tracking. The stylesheet language has no
string literals: parse_declarations ends a value at the first ';' or
'}' with no quote awareness, and values flow into AttrValue::String
verbatim, so a quoted model name is just an unknown model. Tracking
quotes here also created a failure mode the simple scan does not have.
An unpaired apostrophe, as in `model: don't`, disabled comment
stripping for the rest of the input and then blamed a well-formed
comment for the parse error.

Also drop the Cow and its copied_through watermark. They avoided one
allocation on a graph attribute of a few hundred bytes, parsed once per
workflow load, in a function whose caller already clones the attribute
and whose parser allocates a String per property and per value.

Extract excerpt() for the error snippets. The two existing call sites
sliced raw bytes at index 20, which panics when a multi-byte character
straddles the cutoff; model_stylesheet is arbitrary user text, so that
was reachable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 22:16:36 -04:00
Bryan Helmkamp
85586151e5
refactor(graphviz): parse node classes in one place
Node classes were built in two places. The parser split the `class`
attribute on commas and whitespace, but the import transform re-split the
raw attribute on commas only. A space-separated class on an import
placeholder became a single class name, so stylesheet rules did not match.
That included the `class="fast shared"` example in the imports docs.

- add `Node::add_class`, replacing the duplicate append helpers in
  `SemanticState` and `ImportTransform`
- read `node.classes` in `placeholder_config` instead of re-parsing the raw
  attribute, so class splitting happens in exactly one place
- name the separator rule `split_class_attr`, splitting on commas and then
  whitespace so empty entries need no trimming
- drop the unused `Node::class` accessor that invited the re-parse
- keep the comma-compatibility note in the DOT attribute reference only

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 22:15:30 -04:00
Bryan Helmkamp
af9aa53088
fix(graphviz): parse whitespace-separated node classes 2026-07-29 21:51:57 -04:00
Fabro
5f46acdf3f Merge remote-tracking branch 'origin/main' into fabro/run/01KYQMV1VW6139EGNHEM1RGF2G 2026-07-30 00:22:09 +00:00
Fabro
a94233407b fabro(01KYQMV1VW6139EGNHEM1RGF2G): simplify_sol (succeeded)
Fabro-Run: 01KYQMV1VW6139EGNHEM1RGF2G
Fabro-Completed: 7
Fabro-Checkpoint: edcf7467e9

⚒️ Generated with [Fabro](https://fabro.sh)
2026-07-30 00:22:06 +00:00
Fabro
6ce5ea76a2 fabro(01KYQMV1VW6139EGNHEM1RGF2G): simplify_fable (succeeded)
Fabro-Run: 01KYQMV1VW6139EGNHEM1RGF2G
Fabro-Completed: 6
Fabro-Checkpoint: b9c14c247e

⚒️ Generated with [Fabro](https://fabro.sh)
2026-07-29 22:31:26 +00:00
Bryan Helmkamp
e0f73f963a
feat(workflow): make model fallback chains portable 2026-07-29 18:08:26 -04:00
Bryan Helmkamp
692301d867
feat(workflow): support comments in model stylesheets 2026-07-29 17:25:04 -04:00
Bryan Helmkamp
81deda3c69
Merge pull request #682 from fabro-sh/feat/raise-stdin-source-ceiling
Some checks are pending
Rust / Format (push) Waiting to run
Rust / Clippy (push) Waiting to run
Rust / Generated Docs (push) Waiting to run
Rust / Test (Linux) (push) Waiting to run
Rust / Test (macOS) (push) Waiting to run
TypeScript / Typecheck (push) Waiting to run
TypeScript / Test (push) Waiting to run
TypeScript / Build (push) Waiting to run
feat(workflow): raise the stdin_source ceiling to 30 MiB
2026-07-29 17:05:41 -04:00
Bryan Helmkamp
9ef96651cf
feat(workflow): raise the stdin_source ceiling to 30 MiB
The 10 MiB cap on resolved stdin_source values is tight for wide
fan-in: a context.parallel.results batch from a large for_each round
carries tens of structured agent outputs, and a merge step that feeds
them to a deterministic command hits the ceiling as a hard
deterministic failure. Raise the ceiling to 30 MiB; it still bounds
peak memory and remote uploads, just with headroom matched to the
fan-out sizes for_each already allows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 16:50:08 -04:00
Bryan Helmkamp
456b82e9b7
Merge pull request #681 from fabro-sh/fix/subagent-tool-hooks
fix(workflow): pass parent tool hooks to subagent sessions
2026-07-29 16:49:00 -04:00