Commit graph

944 commits

Author SHA1 Message Date
Bryan Helmkamp
1ca9fe977d
fix(sandbox): simplify Bash contract implementation 2026-07-24 21:55:36 -04:00
Bryan Helmkamp
3606ba6a0f
feat(sandbox): standardize command execution on non-login Bash
Fabro advertised Bash while its three backends implemented three
different contracts: Daytona evaluated commands through `sh`, and
Docker's streaming, stdio, and setup paths used a login shell. Bash-only
syntax silently misbehaved depending on provider and code path, and
login profiles could change PATH and command behavior per image.

Make `bash -c` the enforced interpreter for every command string the
Unix sandbox API accepts, on every production backend and through both
buffered and streaming execution. This selects the interpreter only —
no `errexit`, no `pipefail`, no login mode — so `false | true` still
succeeds and a workflow that wants other semantics writes them into its
own command.

Local resolves `bash` through the worker's PATH (NixOS has no
/bin/bash) and reuses that one executable across all three command
paths. Docker and Daytona require /bin/bash with no `sh` fallback.

Fresh initialization and resume/start now verify Bash through a shared
marker-validating probe before reporting the sandbox usable, so a
missing or non-Bash interpreter fails at the lifecycle boundary with
provider-specific remediation instead of on the first command. The
probe also rejects Bash in POSIX mode, which an image whose `bash` is
really `sh` would otherwise pass.

Sandbox MCP scripts and the detached launch wrapper move under the same
contract; host-side stdio MCP scripts, hooks, and interactive terminals
are separate executors and keep their existing `sh` behavior.

The `shell` tool's name and JSON schema are unchanged across providers;
only its prose now identifies `command` as Bash source.

BREAKING CHANGE: sandbox commands no longer load login-shell profiles,
so environment set in /etc/profile.d/*.sh, ~/.bash_profile, or
nvm/rbenv/sdkman initializers is gone. Move those exports into the
Dockerfile's ENV or the Daytona snapshot image.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 21:26:05 -04:00
Bryan Helmkamp
ae3b6702e2
Merge pull request #626 from fabro-sh/feat/passive-reasoning-capture
feat(reasoning): passive reasoning capture in agent.message
2026-07-24 17:49:46 -04:00
Release Repro
512ab50f9c
fix(reasoning): align stream and client invariants 2026-07-24 17:42:44 -04:00
Bryan Helmkamp
2c6fd5d798
Merge pull request #625 from fabro-sh/fix/brave-search-profile-config
Fix Brave Search tool secret propagation
2026-07-24 17:36:48 -04:00
Bryan Helmkamp
4ce57f8aae
refactor: simplify profile builder and drop dead tool plumbing
Follow-up cleanup on the profile-builder refactor.

AgentProfileBuilder::build now borrows instead of consuming, removing the
builder.clone().build() dance at all seven call sites. Deletes
with_command_timeouts, which had no caller but its own test, and the
with_summarizer constructors on all three profiles, whose only remaining
caller was each profile's own new().

Replaces the fifth copy of the profile-kind match (guardrails.rs) with the
builder, and swaps the parity matrix's hand-maintained provider list for
Catalog::effective_agent_profile so a new catalog provider cannot silently
skip the matrix. Collapses web_search_provider_test! into a secrets = arm
on provider_test! and uses EnvVars::BRAVE_SEARCH_API_KEY over a literal.

Drops the Brave key from the Ask Fabro session: AskFabroToolAccessPolicy
denies web_search, and both tools() and the prompt are filtered through
that policy, so the vault read only registered an uncallable tool.

Makes NativeToolOptions::for_profile match exhaustively so a new profile
kind must state its timeout, restores Anthropic's borrowed prompt sections
and Gemini's static prompt (placeholder substitution rather than format!
over 110 lines with doubled braces), and introduces WEB_SEARCH_TOOL_NAME
for the registry lookups that keep tool availability and prompt guidance
in sync.

Updates the product docs, which still described web_search as always
registered and as erroring at call time when unconfigured.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 17:32:33 -04:00
Release Repro
4d3de5f564
fix(reasoning): tighten capture normalization 2026-07-24 17:31:14 -04:00
Bryan Helmkamp
e7740b4acb
feat(reasoning): capture provider reasoning in agent.message
Normalize the readable reasoning providers already return into a
canonical `ReasoningOutput` and carry it through the `agent.message`
run event to storage, SSE, and JSONL.

The shape is derived from the final response's canonical message
content rather than stored a second time, so there is no duplicate
source of truth and retried or replaced streaming buffers never
become durable reasoning. OpenAI-compatible `reasoning_details` are
now preserved verbatim as an opaque content part; only known readable
members are normalized out of them, leaving encrypted entries for a
later provider-aware replay phase.

This phase is passive: no request parameters change, no capability
guessing, and no newly observed provider field is replayed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 14:21:15 -04:00
Release Repro
0b58d087ee
feat(model): add Claude Opus 5 2026-07-24 13:43:08 -04:00
Bryan Helmkamp
d0d1dac3ac
docs: sync product documentation 2026-07-24 11:39:16 -04:00
Bryan Helmkamp
39664cd966
docs(changelog): refresh recent product changes 2026-07-24 10:47:14 -04:00
Bryan Helmkamp
c9900b2bfa
Merge branch 'main' into feat/backward-event-pagination 2026-07-24 10:14:57 -04:00
Bryan Helmkamp
eb83539a18
Merge origin/main into feat/stage-execution-identity-on-resume
Resolves conflicts with the shared-checkout parallel rewrite (#607) and the
cached-run/billing dedup (de60eb900):

- handler/parallel.rs: rebuilt on main's shared-checkout version. Branch
  ordinals are still reserved inside the branch task right before
  ParallelBranchStarted (with graph_visit/resumed_from_stage_id), and the
  reserved StageScope is shared with post-await error paths via a OnceLock
  slot instead of main's dispatch-time visit=1 scope, so completion events
  are never emitted under a guessed ordinal.
- billing.rs: keep this branch's run_stage_from_projection (RunStage grew
  graph_visit/resumed_from_stage_id and a typed id), adopt main's
  state.cached_run() and drop the removed run_stage_from_stage_id import.
- run_projection.rs: adopt main's typed parallel_results
  (Option<Vec<ParallelBranchResult>>).
- run_event/misc.rs: union of both sides' imports.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 10:07:35 -04:00
Bryan Helmkamp
ec100fca2b
Merge remote-tracking branch 'origin/main' into feat/backward-event-pagination
Resolved conflicts against main's shared-projection-cache rework:
- projection_cache.rs: kept main's projection_snapshot and dropped this
  branch's last_seq accessor, which it subsumes; latest_event_seq now
  reads the sequence from projection_snapshot.
- run_store.rs: kept main's EventScan cursor and added a seek_before
  constructor so the backward-pagination range scan bounds its end key
  through the same abstraction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 09:57:15 -04:00
Bryan Helmkamp
8394eb2723
Merge pull request #619 from fabro-sh/feat/fireworks-provider
feat(llm): add Fireworks AI as an opt-in provider
2026-07-24 09:46:03 -04:00
Bryan Helmkamp
ad9b3810c2
docs(fireworks): fix CLI examples flagged in review
- The catalog comment showed `fabro provider login fireworks`, but
  `--provider` is a required flag: `fabro provider login --provider fireworks`.
- The remote-server `fabro model test` example omitted `--provider fireworks`,
  which could resolve the slug against a different provider.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 09:37:18 -04:00
Bryan Helmkamp
78fea736e3
fix: harden stage execution identity on resume 2026-07-24 09:37:05 -04:00
Bryan Helmkamp
396f75578a
Merge remote-tracking branch 'origin/main' into feat/backward-event-pagination
Resolved conflict in run_store.rs tests: kept both the new
list_events_before_with_limit tests from this branch and the
append_event_rejects_sequences_beyond_key_order_limit test from main.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 09:35:34 -04:00
Bryan Helmkamp
415e9e5cd8
Merge pull request #617 from fabro-sh/feat/expose-model-controls
Expose model reasoning effort controls
2026-07-24 09:34:55 -04:00
Bryan Helmkamp
3c6a26e8c2
Merge pull request #607 from fabro-sh/feat/shared-checkout-parallel
Shared-checkout parallel execution
2026-07-24 09:33:03 -04:00
Bryan Helmkamp
3beaddc224
Merge remote-tracking branch 'origin/main' into feat/expose-model-controls
# Conflicts:
#	lib/foundation/fabro-api/src/lib.rs
2026-07-24 09:28:01 -04:00
Bryan Helmkamp
ac62585eb7
fix parallel result artifact handling 2026-07-24 09:26:22 -04:00
Bryan Helmkamp
1b9275f4a8
refactor(llm): align Fireworks provider with catalog conventions
- Rewrite the Fireworks tool round-trip E2E test on the shared
  run_model_test deep-test pattern used by the OpenRouter and Poolside
  opt-in provider tests, instead of a fourth hand-rolled copy of the
  multiply-tool scaffold.
- Drop the "(via Fireworks)" display-name suffix from slugs that have no
  first-party provider (kimi-k2.6, deepseek-v4-*, minimax-m2.7),
  matching the OpenRouter convention; rename "Qwen 3.7 Plus" to
  "Qwen3.7 Plus" to match existing Qwen entries.
- Fix kimi-k2.6 vision flag to false, matching the OpenRouter entry for
  the same slug (the portability test asserts they are the same model).
- Assert small_default_for_provider and per-model family/vision/
  reasoning in the catalog tests, mirroring sibling provider tests.
- Add Troubleshooting and Further reading sections to the Fireworks
  docs page, matching the other opt-in provider pages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 09:08:32 -04:00
Bryan Helmkamp
f2a8b4e989
Merge pull request #616 from fabro-sh/codex/expose-completion-token-usage
Expose detailed completion token usage
2026-07-24 09:01:21 -04:00
Bryan Helmkamp
cd706646c6
feat: treat resumed in-flight nodes as new stage executions
A node cancelled (or lost to a crash) mid-flight and then resumed now
starts a new stage execution with the next StageId ordinal (work@2)
instead of reusing and clearing the cancelled execution's projection.
The old execution stays immutable with its own events, session, output,
timing, billing, and termination state.

Engine:
- Add a run-scoped StageExecutionTracker on RunServices with per-node
  high-water marks. Ordinals are reserved after the StageStart hook
  passes on the first attempt (retries reuse the reservation), ensured
  at the composite checkpoint pre-step for hook-skips, and reserved in
  on_terminal_reached for terminal nodes' synthetic events.
- Keep three concepts distinct: graph visit (max_visits/checkpoints,
  unchanged), stage execution ordinal (the @N in StageId), and handler
  attempt. The tracker is not checkpointed; the append-only stage event
  history is its durable source of truth.
- resume() seeds the allocator from the run projection and computes a
  node -> StageId provenance map of executions observed after the
  selected checkpoint, threaded through execute_persisted_run,
  RunSession, and InitOptions.

Events and projections:
- stage.started, parallel.branch.started, and checkpoint.completed
  carry optional graph_visit and resumed_from_stage_id; StageProjection
  stores both. Old events deserialize with None and legacy duplicate
  stage.started replays keep last-attempt behavior.
- The CheckpointCompleted reducer is envelope-first: diffs and
  skipped-stage synthesis attach to the exact execution StageId, an
  existing Retrying projection finalizes as Skipped without losing
  identity, and historical node_outcomes no longer create or collide
  with newer ordinals (node_visits remains a legacy fallback).

Handlers:
- Parallel fan-out reserves child ordinals through the shared tracker,
  derives worktree pass{N} from the parent's execution ordinal, and
  seeds branch contexts with explicit child stage scopes so branch
  lifecycle and nested handler events agree.
- Artifact capture and manager-loop child logs follow the ordinal.

API and UI:
- RunStage documents visit as the execution ordinal and adds optional
  graph_visit and resumed_from_stage_id; Rust and TypeScript clients
  regenerated.
- The web sidebar lists both executions chronologically; resumed stages
  show a "Resumed from" link in the stage detail header and hover
  popover, with the graph visit surfaced when it diverges.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 09:00:37 -04:00
Bryan Helmkamp
7b7606e4f0
Merge pull request #609 from fabro-sh/fix/typed-completion-reasoning-effort
Validate completion reasoning effort values
2026-07-24 08:19:01 -04:00
Bryan Helmkamp
9708ca8177
feat(llm): add Fireworks AI as an opt-in provider
Adds a disabled-by-default `fireworks` provider to the built-in catalog,
served through the existing openai_compatible adapter/codec. The curated
roster covers Kimi K2.7 Code (default), Kimi K2.6, DeepSeek V4 Pro/Flash,
GLM 5.2, MiniMax M2.7, Qwen 3.7 Plus, and GPT-OSS 120B/20B (small
default + probe), with serverless pricing including cached-input rates.

All api_ids were verified live against /chat/completions (Fireworks'
GET /v1/models only returns a featured subset), and serverless responses
were confirmed to report prompt_tokens_details.cached_tokens, so cache
billing works through the existing codec path.

FIREWORKS_API_KEY is registered as an optional vault secret; provider
login, vault storage, and diagnostics probing are catalog-driven and
need no code changes. Includes catalog/install tests, two live e2e
tests, an integrations docs page, and a provider logo for the web UI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 08:03:39 -04:00
Bryan Helmkamp
4bd9753217
Expose model reasoning effort controls 2026-07-24 07:44:40 -04:00
Bryan Helmkamp
142862f342
Expose detailed completion token usage 2026-07-24 07:36:56 -04:00
Bryan Helmkamp
bb1afae363
feat(events): add backward cursor pagination 2026-07-24 07:23:23 -04:00
Bryan Helmkamp
05fa485637
feat: add portable GLM and DeepSeek aliases 2026-07-24 07:07:56 -04:00
Bryan Helmkamp
673a7064fe
Validate completion reasoning effort 2026-07-24 07:04:31 -04:00
Bryan Helmkamp
7216c49e44
docs: align parallel strategy with typed StageOutcome results
Review follow-ups: the ParallelBranchResult snippet showed status as
String (it is StageOutcome), and the cancellation section implied a
cancelled branch status that the type does not have — cancelled-while-
waiting branches record a failed outcome (reason "branch cancelled")
and the handler returns Error::Cancelled to the run executor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 07:00:44 -04:00
Bryan Helmkamp
4621149b6e
Merge remote-tracking branch 'origin/main' into feat/shared-checkout-parallel 2026-07-24 06:54:32 -04:00
Bryan Helmkamp
85f3286c66
Merge branch 'main' into feat/shared-checkout-parallel 2026-07-24 06:29:57 -04:00
Bryan Helmkamp
0a39ba9e06
Shared-checkout parallel execution (recovered from run 01KY7YH7RYCJ1BDVTTP96ZA4HV)
Cumulative implement + simplify_fable diff recovered from the run's meta
branch (fabro/meta/01KY7YH7RYCJ1BDVTTP96ZA4HV, stage 006 diff.patch).
The run validated this tree clean: cargo nextest (7,007 passed), clippy,
fmt, TS client regen + typecheck, web tests (679 passed), docs check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 06:19:11 -04:00
Bryan Helmkamp
84c5468722
Merge remote-tracking branch 'origin/main' into fix/cancellation-interrupt-lifecycle
# Conflicts:
#	lib/components/fabro-agent/src/subagent.rs
#	lib/components/fabro-agent/tests/it/parity_matrix.rs
2026-07-23 20:55:25 -04:00
Bryan Helmkamp
5cf1c7d183
Harden cancellation and interrupt lifecycles 2026-07-23 20:40:22 -04:00
Bryan Helmkamp
db473da2f3
Merge pull request #605 from fabro-sh/fix/remove-dead-agent-turn-limits
Remove dead agent execution limits
2026-07-23 20:15:39 -04:00
Bryan Helmkamp
67644c7c3c
Merge pull request #602 from fabro-sh/fix/openrouter-anthropic-prompt-caching
fix(llm): send cache_control breakpoints for Claude via OpenRouter
2026-07-23 20:06:10 -04:00
Bryan Helmkamp
cdf1dce4f8
fix(agent): remove dead execution limits 2026-07-23 19:36:29 -04:00
Bryan Helmkamp
78718c101a
Merge remote-tracking branch 'origin/main' into fix/root-agent-todo-projection 2026-07-23 19:30:35 -04:00
Bryan Helmkamp
3c33e0fb69
Merge origin/main (three-layer crate layout) into fix/openrouter-anthropic-prompt-caching
The crate reorganization renamed lib/crates/ to lib/apps|components|foundation/.
Git followed all modified files across the rename; the only conflict was the
newly added codec/cache.rs, now placed at lib/components/fabro-llm/src/codec/cache.rs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 19:30:20 -04:00
Bryan Helmkamp
d8d1c14116
Clarify stage TODO ownership and plan guidance 2026-07-23 18:23:37 -04:00
Scott Werner
47bc772f7b refactor: organize crates into three layers 2026-07-23 17:59:34 -04:00
Bryan Helmkamp
e5f0290229
fix(llm): send cache_control breakpoints for Claude via OpenRouter
Anthropic prompt caching is opt-in per request: without explicit
ephemeral cache_control breakpoints in the body, no cache writes or
reads ever happen. The OpenAI-compatible codec never emitted them, so
every run on openrouter Claude models billed the full conversation at
the uncached input rate on every turn (0 cache tokens on the billing
page, confirmed by OpenRouter's activity portal).

- Add a `cache_control_breakpoints` model feature declaring that a
  route only caches when the request marks the cacheable prefix; set it
  on the builtin OpenRouter Claude rows. Catalog build rejects the flag
  without `prompt_cache`.
- Teach the Chat Completions wire shape a parts-form content variant so
  a message can carry the annotation; unmarked messages keep the
  plain-string form for compatibility with strict servers.
- Mark the last system message (covers tools + system upstream) and the
  second-to-last user turn, counting tool results as user turns —
  mirroring the anthropic codec's placement so agent loops get
  incremental cache hits.
- Extract the shared placement/opt-out policy into codec::cache and
  refactor the anthropic codec onto it; anthropic wire snapshots are
  unchanged.
- Honor `provider_options.<name>.auto_cache = false` as an opt-out and
  consume the control key instead of merging it into the body.
- Mirror the new feature through settings (fabro-config), the OpenAPI
  schema, and the generated TypeScript client.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 17:55:41 -04:00
Bryan Helmkamp
7bdee5b494
Merge remote-tracking branch 'origin/main' into pr-599
# Conflicts:
#	docs/public/agents/outputs.mdx
#	docs/public/reference/dot-language.mdx
2026-07-23 17:24:33 -04:00
Bryan Helmkamp
b558af070f
Merge pull request #601 from fabro-sh/fabro/run/01KY7Y01REECZ24XXTMBZ3PPV9
Provider-scoped model catalog: `(provider, model slug)` as stable ident…
2026-07-23 17:14:51 -04:00
Fabro
b3e085dc45 Merge remote-tracking branch 'origin/main' into fabro/run/01KY7Y01REECZ24XXTMBZ3PPV9 2026-07-23 20:29:21 +00:00
Bryan Helmkamp
886371e474
refactor(workflow): clean up last-file routing fallback review nits
Document the contract of read_last_file_routing_json (terminal JSON
extraction only; routing validation happens downstream), extract a
shared sandbox_with_file test helper, and drop the misleading
"standalone" wording from the fallback docs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 15:47:17 -04:00