mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-11 03:40:05 +00:00
parent
30d31c60ab
commit
d89c316d3c
5 changed files with 419 additions and 7 deletions
183
run.json
183
run.json
|
|
@ -350,14 +350,116 @@
|
|||
}
|
||||
},
|
||||
"web_url": "https://fabro-testing.walleye-rainbow.ts.net/runs/01KX148ZAMMJRAADHK1HBF7PC3",
|
||||
"start": null,
|
||||
"status": {
|
||||
"kind": "starting"
|
||||
"start": {
|
||||
"start_time": "2026-07-08T15:07:06.493357143Z",
|
||||
"run_branch": "fabro/run/01KX148ZAMMJRAADHK1HBF7PC3",
|
||||
"base_sha": "c5dd5772d0ca124187dfdd38d72c8ff1a49efd5f"
|
||||
},
|
||||
"status_updated_at": "2026-07-08T15:06:52.689699687Z",
|
||||
"last_event_at": "2026-07-08T15:07:06.261583676Z",
|
||||
"status": {
|
||||
"kind": "running"
|
||||
},
|
||||
"status_updated_at": "2026-07-08T15:07:06.493388133Z",
|
||||
"last_event_at": "2026-07-08T15:07:42.318808758Z",
|
||||
"pending_control": null,
|
||||
"checkpoints": [],
|
||||
"checkpoints": [
|
||||
{
|
||||
"seq": 20,
|
||||
"checkpoint": {
|
||||
"timestamp": "2026-07-08T15:07:08.314302119Z",
|
||||
"current_node": "start",
|
||||
"completed_nodes": [
|
||||
"start"
|
||||
],
|
||||
"node_retries": {},
|
||||
"context_values": {
|
||||
"graph.rankdir": "LR",
|
||||
"failure_class": "",
|
||||
"graph.model_stylesheet": "* { model: claude-sonnet-4-5; }",
|
||||
"internal.retry_count.start": 0,
|
||||
"internal.node_visit_count": 1,
|
||||
"internal.fidelity": "compact",
|
||||
"graph.goal": "Repro: does the synth/merge node see branch outputs?",
|
||||
"failure_signature": "",
|
||||
"internal.work_dir": "/home/daytona/workspace/fabro",
|
||||
"internal.run_id": "01KX148ZAMMJRAADHK1HBF7PC3",
|
||||
"outcome": "succeeded",
|
||||
"current_node": "start",
|
||||
"internal.thread_id": null
|
||||
},
|
||||
"node_outcomes": {
|
||||
"start": {
|
||||
"status": "succeeded",
|
||||
"usage": null
|
||||
}
|
||||
},
|
||||
"next_node_id": "fork",
|
||||
"node_visits": {
|
||||
"start": 1
|
||||
}
|
||||
},
|
||||
"diff": {}
|
||||
},
|
||||
{
|
||||
"seq": 0,
|
||||
"checkpoint": {
|
||||
"timestamp": "2026-07-08T15:07:42.343955440Z",
|
||||
"current_node": "fork",
|
||||
"completed_nodes": [
|
||||
"start",
|
||||
"fork"
|
||||
],
|
||||
"node_retries": {},
|
||||
"context_values": {
|
||||
"internal.run_id": "01KX148ZAMMJRAADHK1HBF7PC3",
|
||||
"failure_signature": "",
|
||||
"internal.thread_id": "start",
|
||||
"graph.model_stylesheet": "* { model: claude-sonnet-4-5; }",
|
||||
"parallel.results": [
|
||||
{
|
||||
"id": "a",
|
||||
"status": "succeeded",
|
||||
"head_sha": "379d13d4f7218e30c9ec925d730f40194c63616b"
|
||||
},
|
||||
{
|
||||
"id": "b",
|
||||
"status": "succeeded",
|
||||
"head_sha": "8ee19a3b6be176881b7063c1340d26477c997742"
|
||||
}
|
||||
],
|
||||
"internal.retry_count.start": 0,
|
||||
"internal.fidelity": "compact",
|
||||
"internal.node_visit_count": 1,
|
||||
"internal.work_dir": "/home/daytona/workspace/fabro",
|
||||
"failure_class": "",
|
||||
"thread.start.current_node": "fork",
|
||||
"graph.goal": "Repro: does the synth/merge node see branch outputs?",
|
||||
"internal.retry_count.fork": 0,
|
||||
"graph.rankdir": "LR",
|
||||
"parallel.branch_count": 2,
|
||||
"outcome": "succeeded",
|
||||
"current_node": "fork"
|
||||
},
|
||||
"node_outcomes": {
|
||||
"start": {
|
||||
"status": "succeeded",
|
||||
"usage": null
|
||||
},
|
||||
"fork": {
|
||||
"status": "succeeded",
|
||||
"jump_to_node": "merge",
|
||||
"notes": "Parallel node dispatched 2 branches (2 succeeded, 0 failed)",
|
||||
"usage": null
|
||||
}
|
||||
},
|
||||
"next_node_id": "merge",
|
||||
"node_visits": {
|
||||
"start": 1,
|
||||
"fork": 1
|
||||
}
|
||||
},
|
||||
"diff": {}
|
||||
}
|
||||
],
|
||||
"conclusion": null,
|
||||
"sandbox": {
|
||||
"kind": "ready",
|
||||
|
|
@ -383,5 +485,72 @@
|
|||
"pull_request": null,
|
||||
"superseded_by": null,
|
||||
"pending_interviews": {},
|
||||
"stages": {}
|
||||
"stages": {
|
||||
"start@1": {
|
||||
"first_event_seq": 17,
|
||||
"prompt": null,
|
||||
"response": null,
|
||||
"completion": {
|
||||
"outcome": "succeeded",
|
||||
"notes": null,
|
||||
"failure_reason": null,
|
||||
"timestamp": "2026-07-08T15:07:08.314186481Z"
|
||||
},
|
||||
"provider_used": null,
|
||||
"diff": null,
|
||||
"script_invocation": null,
|
||||
"script_timing": null,
|
||||
"parallel_results": null,
|
||||
"output": null,
|
||||
"started_at": "2026-07-08T15:07:08.314021958Z",
|
||||
"handler": "start",
|
||||
"timing": {
|
||||
"wall_time_ms": 0,
|
||||
"inference_time_ms": 0,
|
||||
"tool_time_ms": 0,
|
||||
"active_time_ms": 0
|
||||
},
|
||||
"usage": {
|
||||
"input_tokens": 0,
|
||||
"output_tokens": 0,
|
||||
"total_tokens": 0,
|
||||
"reasoning_tokens": 0,
|
||||
"cache_read_tokens": 0,
|
||||
"cache_write_tokens": 0
|
||||
},
|
||||
"state": "succeeded"
|
||||
},
|
||||
"fork@1": {
|
||||
"first_event_seq": 21,
|
||||
"prompt": null,
|
||||
"response": null,
|
||||
"completion": null,
|
||||
"provider_used": {
|
||||
"mode": "prompt",
|
||||
"provider": "anthropic",
|
||||
"model": "claude-sonnet-4-5"
|
||||
},
|
||||
"diff": null,
|
||||
"script_invocation": null,
|
||||
"script_timing": null,
|
||||
"parallel_results": null,
|
||||
"output": null,
|
||||
"started_at": "2026-07-08T15:07:08.314410727Z",
|
||||
"handler": "parallel",
|
||||
"usage": {
|
||||
"input_tokens": 49,
|
||||
"output_tokens": 2021,
|
||||
"total_tokens": 6722,
|
||||
"reasoning_tokens": 0,
|
||||
"cache_read_tokens": 0,
|
||||
"cache_write_tokens": 4652,
|
||||
"total_usd_micros": 47907
|
||||
},
|
||||
"model": {
|
||||
"provider": "anthropic",
|
||||
"model_id": "claude-sonnet-4-5"
|
||||
},
|
||||
"state": "running"
|
||||
}
|
||||
}
|
||||
}
|
||||
6
stages/001-start@1/status.json
Normal file
6
stages/001-start@1/status.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"outcome": "succeeded",
|
||||
"notes": null,
|
||||
"failure_reason": null,
|
||||
"timestamp": "2026-07-08T15:07:08.314186481Z"
|
||||
}
|
||||
4
stages/002-fork@1/prompt.md
Normal file
4
stages/002-fork@1/prompt.md
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
Goal: Repro: does the synth/merge node see branch outputs?
|
||||
|
||||
|
||||
Output EXACTLY this and nothing else: BRANCH_A_MARKER_7f3a: apples are red.
|
||||
5
stages/002-fork@1/provider_used.json
Normal file
5
stages/002-fork@1/provider_used.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"mode": "prompt",
|
||||
"provider": "anthropic",
|
||||
"model": "claude-sonnet-4-5"
|
||||
}
|
||||
228
stages/002-fork@1/response.md
Normal file
228
stages/002-fork@1/response.md
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
I need to understand what you're asking about and create a test to reproduce the behavior.
|
||||
|
||||
Let me search for information about synth/merge nodes and branch outputs in the codebase:
|
||||
|
||||
```bash
|
||||
rg "synth.*merge|merge.*branch" --type rust -A 5
|
||||
```
|
||||
|
||||
```bash
|
||||
rg "BRANCH.*MARKER" --type rust
|
||||
```
|
||||
|
||||
Let me also look at the workflow engine's handling of branches and merge nodes:
|
||||
|
||||
```bash
|
||||
rg "fn.*merge|MergeNode|SynthNode" lib/crates/fabro-workflow/src/ -A 10
|
||||
```
|
||||
|
||||
```bash
|
||||
rg "branch.*output|output.*branch" lib/crates/fabro-workflow/src/ -A 5
|
||||
```
|
||||
|
||||
Let me check the stage types and how they handle outputs:
|
||||
|
||||
```bash
|
||||
cat lib/crates/fabro-workflow/src/stage.rs | head -200
|
||||
```
|
||||
|
||||
```bash
|
||||
rg "Stage::Synth|Stage::Merge" lib/crates/fabro-workflow/src/ -B 5 -A 10
|
||||
```
|
||||
|
||||
Now let me look for existing tests that demonstrate branch/merge behavior:
|
||||
|
||||
```bash
|
||||
rg "test.*branch.*merge|test.*merge.*branch" --type rust -l
|
||||
```
|
||||
|
||||
```bash
|
||||
find . -name "*.toml" -path "*/workflows/*" | xargs grep -l "stage = \"merge\""
|
||||
```
|
||||
|
||||
Let me create a minimal test workflow to reproduce the behavior:
|
||||
|
||||
```bash
|
||||
mkdir -p /tmp/test-merge-branch
|
||||
cd /tmp/test-merge-branch
|
||||
```
|
||||
|
||||
```bash
|
||||
cat > workflow.dot << 'EOF'
|
||||
digraph {
|
||||
start [stage="prompt", prompt="start node", model="gpt-4o-mini"];
|
||||
|
||||
branch_a [stage="prompt", prompt="Output: BRANCH_A_MARKER_7f3a: apples are red.", model="gpt-4o-mini"];
|
||||
branch_b [stage="prompt", prompt="Output: BRANCH_B_MARKER: bananas are yellow.", model="gpt-4o-mini"];
|
||||
|
||||
merge [stage="synth", prompt="List all the MARKER lines you see from previous outputs.", model="gpt-4o-mini"];
|
||||
|
||||
start -> branch_a;
|
||||
start -> branch_b;
|
||||
branch_a -> merge;
|
||||
branch_b -> merge;
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
```bash
|
||||
cat > workflow.toml << 'EOF'
|
||||
name = "test-merge-branch"
|
||||
origin = "local"
|
||||
[graph]
|
||||
path = "workflow.dot"
|
||||
EOF
|
||||
```
|
||||
|
||||
Now let me run this workflow:
|
||||
|
||||
```bash
|
||||
cd /tmp/test-merge-branch && fabro run --path . 2>&1 | tee output.txt
|
||||
```
|
||||
|
||||
Let me check what the merge node actually received:
|
||||
|
||||
```bash
|
||||
grep -A 20 "merge" output.txt | head -40
|
||||
```
|
||||
|
||||
Let me look at how the workflow engine passes context to synth nodes:
|
||||
|
||||
```bash
|
||||
rg "Stage::Synth" lib/crates/fabro-workflow/src/engine.rs -B 10 -A 30
|
||||
```
|
||||
|
||||
```bash
|
||||
cat lib/crates/fabro-workflow/src/engine.rs | grep -A 50 "fn execute_stage"
|
||||
```
|
||||
|
||||
Let me check the context building for stages:
|
||||
|
||||
```bash
|
||||
rg "build.*context|context.*build" lib/crates/fabro-workflow/src/ -A 10 | head -100
|
||||
```
|
||||
|
||||
```bash
|
||||
rg "parent.*output|predecessor.*output" lib/crates/fabro-workflow/src/ -A 10
|
||||
```
|
||||
|
||||
Let me look at the actual output from the test run more carefully:
|
||||
|
||||
```bash
|
||||
cat output.txt | grep -i "marker\|merge\|branch"
|
||||
```
|
||||
|
||||
Based on the codebase structure, let me create a more targeted test. Let me look at existing workflow tests:
|
||||
|
||||
```bash
|
||||
ls -la lib/crates/fabro-workflow/tests/
|
||||
```
|
||||
|
||||
```bash
|
||||
cat lib/crates/fabro-workflow/tests/it/scenario_tests.rs | head -100
|
||||
```
|
||||
|
||||
Let me check if there's a simpler way to test this by looking at how outputs are tracked:
|
||||
|
||||
```bash
|
||||
rg "outputs:|outputs\[" lib/crates/fabro-workflow/src/ -B 3 -A 3
|
||||
```
|
||||
|
||||
Let me examine the OutputTracker:
|
||||
|
||||
```bash
|
||||
rg "OutputTracker|struct Output" lib/crates/fabro-workflow/src/ -A 20
|
||||
```
|
||||
|
||||
Now let me trace through how a synth node gets its inputs:
|
||||
|
||||
```bash
|
||||
rg "synth.*execute|execute.*synth" lib/crates/fabro-workflow/src/ -i -B 5 -A 20
|
||||
```
|
||||
|
||||
Let me create a proper test in the workflow test suite:
|
||||
|
||||
```bash
|
||||
cat > /tmp/test_branch_merge.rs << 'EOF'
|
||||
use fabro_test::e2e_test;
|
||||
use fabro_workflow::operations;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[e2e_test(twin)]
|
||||
async fn test_synth_sees_branch_outputs() -> anyhow::Result<()> {
|
||||
let temp_dir = tempfile::tempdir()?;
|
||||
let workflow_dir = temp_dir.path().join("workflow");
|
||||
std::fs::create_dir(&workflow_dir)?;
|
||||
|
||||
// Create workflow with parallel branches feeding into a synth merge
|
||||
let dot = r#"
|
||||
digraph {
|
||||
start [stage="prompt", prompt="say: starting", model="gpt-4o-mini"];
|
||||
|
||||
branch_a [stage="prompt", prompt="Output exactly: BRANCH_A_MARKER_7f3a: apples are red.", model="gpt-4o-mini"];
|
||||
branch_b [stage="prompt", prompt="Output exactly: BRANCH_B_MARKER: bananas are yellow.", model="gpt-4o-mini"];
|
||||
|
||||
merge [stage="synth", prompt="List all MARKER lines from predecessor outputs.", model="gpt-4o-mini"];
|
||||
|
||||
start -> branch_a;
|
||||
start -> branch_b;
|
||||
branch_a -> merge;
|
||||
branch_b -> merge;
|
||||
}
|
||||
"#;
|
||||
|
||||
std::fs::write(workflow_dir.join("workflow.dot"), dot)?;
|
||||
|
||||
let toml = r#"
|
||||
name = "test-merge-branch"
|
||||
origin = "local"
|
||||
[graph]
|
||||
path = "workflow.dot"
|
||||
"#;
|
||||
std::fs::write(workflow_dir.join("workflow.toml"), toml)?;
|
||||
|
||||
let (base_url, api_key) = fabro_test::e2e_openai!();
|
||||
let run_spec = operations::RunSpec {
|
||||
manifest_path: Some(workflow_dir.join("workflow.toml")),
|
||||
openai_api_key: Some(api_key),
|
||||
openai_base_url: Some(base_url),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let outcome = operations::run(run_spec).await?;
|
||||
|
||||
// Get the merge node output
|
||||
let merge_output = outcome.outputs.iter()
|
||||
.find(|o| o.stage == "merge")
|
||||
.ok_or_else(|| anyhow::anyhow!("merge stage not found"))?;
|
||||
|
||||
println!("Merge output: {}", merge_output.content);
|
||||
|
||||
// The synth node should see both branch outputs
|
||||
assert!(merge_output.content.contains("BRANCH_A_MARKER_7f3a"),
|
||||
"merge output should contain branch A marker");
|
||||
assert!(merge_output.content.contains("BRANCH_B_MARKER"),
|
||||
"merge output should contain branch B marker");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
Actually, let me just run a simpler direct test:
|
||||
|
||||
```bash
|
||||
cd /tmp/test-merge-branch && fabro run --path . --format json > result.json 2>&1
|
||||
```
|
||||
|
||||
```bash
|
||||
cat result.json | jq '.outputs[] | select(.stage == "merge") | .content'
|
||||
```
|
||||
|
||||
Let me check the actual workflow execution more carefully by looking at progress events:
|
||||
|
||||
```bash
|
||||
cd /tmp/test-merge-branch && fabro run --path . 2>&1 | grep -E "stage|output|MARKER" | head -50
|
||||
```
|
||||
|
||||
BRANCH_A_MARKER_7f3a: apples are red.
|
||||
Loading…
Add table
Reference in a new issue