Commit graph

4560 commits

Author SHA1 Message Date
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
Bryan Helmkamp
47fcf917b1
Merge pull request #680 from fabro-sh/fix/repeated-stage-artifacts
Fix artifacts from repeated stage visits
2026-07-29 16:41:50 -04:00
Bryan Helmkamp
1e5dc685d2
fix(workflow): pass parent tool hooks to subagent sessions
Workflow agent sessions run at PermissionLevel::Full with the whole
tool registry exposed, so blocking pre_tool_use hooks are the only
policy boundary they have. The child-session factory built for
spawn_agent dropped tool_hooks from the child's SessionOptions, so a
subagent's tool calls never reached the run's hooks: any agent that
could spawn a subagent got an unguarded read-write-shell escape from
every hook-enforced policy.

Clone the parent's tool_hooks into the factory, the same way the
permission level is already carried, so child sessions inherit the
parent's hook boundary. The new end-to-end test drives the real
create_session path against a scripted mock provider: the parent
spawns a child, the child executes read_file, and the hooks must see
both the parent's spawn_agent and the child's read_file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 16:38:30 -04:00
Bryan Helmkamp
0f2b66565a
Address artifact review feedback 2026-07-29 16:37:59 -04:00
Fabro
4746d143fd fabro(01KYQMV1VW6139EGNHEM1RGF2G): implement (succeeded)
Fabro-Run: 01KYQMV1VW6139EGNHEM1RGF2G
Fabro-Completed: 5
Fabro-Checkpoint: c84d4147ba

⚒️ Generated with [Fabro](https://fabro.sh)
2026-07-29 20:35:54 +00:00
Fabro
ed59858bd6 fabro(01KYQMV1VW6139EGNHEM1RGF2G): preflight_lint (succeeded)
Fabro-Run: 01KYQMV1VW6139EGNHEM1RGF2G
Fabro-Completed: 4
Fabro-Checkpoint: e8a60ed97b

⚒️ Generated with [Fabro](https://fabro.sh)
2026-07-29 19:20:56 +00:00
Fabro
d23a9dd17a fabro(01KYQMV1VW6139EGNHEM1RGF2G): preflight_compile (succeeded)
Fabro-Run: 01KYQMV1VW6139EGNHEM1RGF2G
Fabro-Completed: 3
Fabro-Checkpoint: 6ce5170d04

⚒️ Generated with [Fabro](https://fabro.sh)
2026-07-29 19:18:16 +00:00
Fabro
8fba42ed18 fabro(01KYQMV1VW6139EGNHEM1RGF2G): toolchain (succeeded)
Fabro-Run: 01KYQMV1VW6139EGNHEM1RGF2G
Fabro-Completed: 2
Fabro-Checkpoint: 07b27f2f75

⚒️ Generated with [Fabro](https://fabro.sh)
2026-07-29 19:15:49 +00:00
Bryan Helmkamp
5f040d3107
Fix artifacts from repeated stage visits 2026-07-29 14:27:37 -04:00
fabro-releases[bot]
854f71f2c5 Bump version to 0.309.0-nightly.2 2026-07-29 17:41:07 +00:00
Bryan Helmkamp
18f07f4d4e
Merge pull request #679 from swerner/fix/remove-workflows-write-permission
Stop requesting GitHub workflow write permission
2026-07-29 13:25:57 -04:00
Scott Werner
1575cc174f Stop requesting workflow write permission 2026-07-29 13:19:55 -04:00
fabro-releases[bot]
1855362354 Bump version to 0.309.0-nightly.1 2026-07-29 15:55:44 +00:00
Bryan Helmkamp
92db1664e3
Merge pull request #677 from fabro-sh/feat/command-stdin-source
Add context-sourced stdin to command nodes
2026-07-29 11:40:31 -04:00
Bryan Helmkamp
6e2a2ac652
Merge remote-tracking branch 'origin/main' into feat/command-stdin-source
# Conflicts:
#	docs/public/workflows/stages-and-nodes.mdx
#	lib/components/fabro-validate/src/rules/inert_attribute.rs
#	lib/components/fabro-workflow/src/handler/command.rs
2026-07-29 11:37:27 -04:00
Bryan Helmkamp
cd6a614ed7
Make stdin_source validation message handler-neutral
CommandHandler also serves type="tool" nodes, so the runtime message now
says "Node '...'" to match the stdin_source_valid lint wording.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 11:24:13 -04:00
Bryan Helmkamp
0eda219376
Simplify stdin_source plumbing after review
- ExecStreamingRequest: drop #[non_exhaustive] and the six Option-taking
  builder setters; call sites use struct literals over ::new(), matching
  GrepOptions/WalkOptions, and providers can destructure exhaustively
- Docker: pass ExecStreamingRequest through docker_exec_shell_streaming
  instead of seven positional args; revert the no-op StartExecOptions
- Daytona: stdin temp-file cleanup is now best-effort (mirrors
  DaytonaSession::close) so a failed delete cannot fail a completed
  command or double-delete from Drop; upload overlaps session creation;
  one shared DAYTONA_CLEANUP_TIMEOUT
- write_process_stdin tolerates ConnectionReset/ConnectionAborted so a
  command that stops reading stdin does not fail on TCP Docker daemons
- Local sandbox aborts the stdin writer after process exit instead of
  joining unbounded
- Cap stdin_source payloads at 10 MiB, mirroring the for_each bound
- Add Node::context_key_attr() tri-state so the handler and lint rule
  share one definition of a valid context-key attribute
- inert_attribute canonicalizes handler types via StageHandler, fixing
  false warnings for command attrs on tool nodes
- Share resolve_flat_context_value between command stdin and for_each;
  resolve_json_value takes Value by value, removing a deep clone
- Reuse MockSandbox in command handler stdin tests instead of extending
  SpySandbox with a hand-rolled streaming override

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 11:14:48 -04:00
Bryan Helmkamp
8a985c2d54
Merge pull request #656 from fabro-sh/feat/infer-command-node-from-script
feat(workflow): infer command nodes from the script attribute
2026-07-29 11:06:39 -04:00
Bryan Helmkamp
d37fc0027c
fix(workflow): align inferred command behavior 2026-07-29 10:54:43 -04:00
Bryan Helmkamp
cb24f47b59
Merge remote-tracking branch 'origin/main' into feat/infer-command-node-from-script
# Conflicts:
#	docs/public/workflows/stages-and-nodes.mdx
#	lib/foundation/fabro-types/src/graph.rs
2026-07-29 10:40:26 -04:00
Bryan Helmkamp
5d72f9a538
Add context-sourced command stdin 2026-07-29 10:25:51 -04:00
Bryan Helmkamp
de7bb61ef5
Remove nonfunctional run agent permissions setting 2026-07-29 10:23:00 -04:00
Bryan Helmkamp
0e2ee787bc
fix(auth): serialize CLI token refreshes across processes 2026-07-29 10:07:03 -04:00
fabro-releases[bot]
d8434e7672 Bump version to 0.309.0-nightly.0 2026-07-29 10:11:28 +00:00
Bryan Helmkamp
000a253e95
Merge pull request #653 from fabro-sh/feat/for-each-item-injection
Add runtime for_each item injection
2026-07-28 20:42:49 -04:00
Bryan Helmkamp
d5c3436300
Drop local plan drafts committed by mistake
These were untracked working-tree files unrelated to for_each item
injection. They were swept in by an over-broad `git add` and do not
belong on this branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 20:16:35 -04:00
Bryan Helmkamp
8d14b54994
Bound for_each fan-out memory
Addresses a Copilot review comment on #653.

The source array is runtime data, usually produced by a model, so its
length is not something a workflow author reviewed. Two changes, so an
over-long array degrades into a clear error rather than memory pressure.

Cap the item count at 1000. Above that the stage fails deterministically
before `parallel.started`, alongside the other for_each contract
violations, and the message says how to reduce the array.

Fork the parent context inside the branch task, after it acquires a
`max_parallel` slot, instead of at dispatch time. Live context copies now
track `max_parallel` rather than item count. Only the branch's own
preamble entry is moved into the task, so the shared stash is not cloned
per branch either.

The reviewer also suggested replacing spawn-all with `max_parallel`
workers pulling from a queue. Not done here: with the fork deferred, a
pending task holds little beyond its item, and reshaping the dispatch
loop would change cancellation and scope-reservation ordering, which
deserves its own review.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 20:16:20 -04:00
Bryan Helmkamp
a369ea7fc4
Merge remote-tracking branch 'origin/main' into feat/for-each-item-injection
# Conflicts:
#	apps/fabro-web/app/components/stage-renderers/parallel-children.tsx
2026-07-28 20:03:25 -04:00
Bryan Helmkamp
17b3223558
Neutralize runtime for_each labels before display
Addresses three Copilot review comments on #653.

`item_label` comes from a model or a workflow author, and it reaches the
terminal through the CLI progress display. A label could carry ANSI
escapes, newlines, or bidi overrides and rewrite what the operator sees.
It could also be whitespace-only, giving a branch a blank identity.

Add `text::sanitize_display_label`: strip ANSI sequences, drop control
and bidi-reordering characters, trim, and elide past 80 characters.
Return an empty string when nothing printable survives so callers fall
back to an identity they control.

Apply it where the label is created, so events, the store, and the web
UI all get a clean value instead of each consumer having to remember.
`parallel_branch_display` sanitizes again, because a run recorded before
this commit still has raw labels in its event log.

`emit_branch_retrying` now sets `stage.retrying`'s `index` from the
branch stage's execution ordinal, matching the envelope `stage_id` and
the meaning every other emitter gives that field. The branch's position
in the fan-out is already on `parallel.branch.started`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 19:52:59 -04:00
Bryan Helmkamp
826c8be519
Merge pull request #665 from fabro-sh/refactor/remove-env-interpolation
Some checks are pending
TypeScript / Build (push) Waiting to run
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
Remove process-environment interpolation from config
2026-07-28 19:42:29 -04:00
Bryan Helmkamp
4efaf5c3d7
Let --dry-run walk a for_each fan-out
build_branch_plan read the runtime array before run_branches looked at
`simulated`, so every for_each workflow failed under --dry-run with
"for_each source '...' was not found in workflow context". Nothing had
populated the key yet: upstream LLM nodes take Handler::simulate, which
returns no context updates.

A dry run now stands in one placeholder item when the source is absent or
unusable, and simulates the template target once. Graph-shape mistakes
still fail, since catching those is the point of a dry run.

Also from review:
- ITEM_FENCE_PREFIX replaces the bare "untrusted-" literal that
  render_item_data and its test each spelled out.
- ItemRecordingHandler no longer guesses an item label by substring
  search. Nothing asserted it, and the third item's label "2" matched
  stray hex from the random fence tag about two thirds of the time.
- The twin test reads keys::PARALLEL_RESULTS instead of the raw string.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 18:49:41 -04:00
Bryan Helmkamp
ee9189ddb5
Reuse existing helpers in the for_each fan-out
Simplification pass over the for_each branch. No behavior change.

Share what was duplicated:
- Node::prompt_or_label replaces the "prompt, else label" fallback that
  agent, prompt, and the for_each item injector each wrote out.
- context::lookup_flat replaces the "exact key, then strip context."
  lookup that condition.rs had twice and the for_each source had again.
- is_llm_handler_type replaces the inline agent/prompt match, so the
  runtime and the for_each_contract rule agree by construction.
- find_join_node now takes branch ids, so a for_each fan-out passes its
  template target instead of needing find_join_for_target.
- collect_events moves to test_support; parallel and integration tests
  shared one copy already.
- One ScriptedHandler replaces four test handlers that differed only in
  what they returned.

Straighten the branch retry loop:
- Reserve the branch scope once before the loop instead of guarding it
  with an Option, which removes three expect() calls.
- acquire_branch_permit and backoff_or_cancel replace the cancel-aware
  select! blocks the loop repeated verbatim.
- Keep the match arms in Executor::execute_with_retry order so the two
  loops stay easy to compare.

Drop redundant state:
- BranchPlan::is_for_each derives from template_target_id.
- for_each_contract checks node type before source shape, so one
  mistake reports one diagnostic.

Prove the new wire fields survive the OpenAPI boundary: the fabro-api
round-trip fixture now carries index and item_label.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 18:42:23 -04:00
fabro-releases[bot]
5396565cd4 Bump version to 0.308.0-nightly.1 2026-07-28 22:42:08 +00:00
Bryan Helmkamp
f4c09867e5
Merge origin/main into refactor/remove-env-interpolation 2026-07-28 18:31:16 -04:00
Bryan Helmkamp
fa85bc42a2
Merge pull request #643 from fabro-sh/feat/claude-5-profile
feat(agent): add Claude 5 profile
2026-07-28 18:25:21 -04:00
Bryan Helmkamp
68b90ccc61
fix(agent): honor the TaskOutput schema defaults
The schema advertises defaults for `block` and `timeout`, but the
executor errored when either was absent. Apply the advertised defaults
instead, and keep the type check for values that are present.

The required list stays as the Claude 5 contract declares it. Constants
now hold the defaults and the maximum so the schema and the executor
cannot drift.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 18:21:01 -04:00
Bryan Helmkamp
b611d946a6
Merge pull request #673 from fabro-sh/feat/modal-llm-provider
Add Modal as an LLM provider
2026-07-28 18:14:43 -04:00
Bryan Helmkamp
977628000b
Merge pull request #668 from fabro-sh/feat/provider-qualified-fallbacks
Support provider-qualified model fallback selectors
2026-07-28 18:13:43 -04:00
Bryan Helmkamp
6c516ec31d
Merge pull request #660 from fabro-sh/feat/parallel-branch-live-status
Show live status for parallel branches
2026-07-28 18:13:21 -04:00
Bryan Helmkamp
8e4251bb07
refactor(llm): simplify the Modal provider tests and fixtures
Reduce duplication and over-specification introduced with the Modal
provider, without changing shipped behavior.

- Extract enabled_provider_catalog and assert_deep_tool_round_trip in
  the fabro-llm integration tests. The Poolside, Fireworks, OpenRouter,
  and Modal deep round trips were four near-identical copies.
- Add ApiCredential::with_extra_headers for providers that authenticate
  with request headers instead of an API key.
- Replace the unreachable require_env guards in the Modal e2e test with
  the std::env::var form used by every sibling test, and register
  MODAL_TOKEN_ID and MODAL_TOKEN_SECRET in EnvVars.
- Collapse modal_requires_both_vault_proxy_tokens to a single case. The
  loop rebuilt the whole built-in catalog per iteration.
- Drop tautological and over-specified assertions: the api_key_url doc
  URL, the forced default/probe lookups on a single-model provider, and
  the get_on_provider loop that could not fail.
- Inline the single-use modal_env_catalog fixture and note why it
  overrides the shipped secrets templates.
- Sort the MODAL_* keys in .env.example, and record in modal.toml why
  api_id keeps the Hugging Face capitalization.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 18:06:07 -04:00
Bryan Helmkamp
f91e51162f
Describe the parallel branch fields in generated clients
`parallel_group_id` used `oneOf: [$ref StageId, null]`, and the generator
drops a sibling description in that position, so the TypeScript client
documented the field as "Canonical stage execution identifier in
`node_id@visit` form" — the shared StageId text, which says nothing about
what this field means. Switching to `allOf` lets the field's own
description through.

Dropping `type: "null"` also makes the contract match the server, which
omits both fields rather than sending null (`skip_serializing_if` on
`Option`, pinned by list_run_stages_exposes_parallel_branch_identity).
The Rust types are unchanged — still `Option<StageId>` and `Option<u32>`,
which accept an explicit null on input either way — so this only narrows
what clients are told to expect on the wire. Wording updated to match,
and reworded to avoid an apostrophe the generator escapes into the
JSDoc.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 18:05:24 -04:00
Bryan Helmkamp
e1805f4f33
docs: clarify hook variable sensitivity 2026-07-28 18:04:54 -04:00
Bryan Helmkamp
f8325c5c48
Let the first separator decide the model reference form
Addresses Copilot review feedback on #668.

Checking for `:` before the legacy `/` form broke `provider/model`
references whose selector contains a colon, which Bedrock API IDs do:
`bedrock/us.anthropic.claude-haiku-4-5-20251001-v1:0` parsed as the
whole path up to the last colon, then `0`. On main it is a pin to
bedrock with the full ID as the selector.

Now whichever separator appears first decides. A `/` before any `:` is
the legacy pin and its selector may contain colons. Otherwise the token
stays bare and `qualify` promotes it only when the prefix names a
provider, so `openrouter:moonshotai/kimi-k3` still qualifies.

Adds the regression test Copilot asked for, covering both Bedrock-style
legacy input and the colon-before-slash case.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 18:01:05 -04:00