mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-11 03:40:05 +00:00
parent
d89c316d3c
commit
a12310f3ca
4 changed files with 164 additions and 239 deletions
157
run.json
157
run.json
|
|
@ -359,7 +359,7 @@
|
|||
"kind": "running"
|
||||
},
|
||||
"status_updated_at": "2026-07-08T15:07:06.493388133Z",
|
||||
"last_event_at": "2026-07-08T15:07:42.318808758Z",
|
||||
"last_event_at": "2026-07-08T15:07:46.323790616Z",
|
||||
"pending_control": null,
|
||||
"checkpoints": [
|
||||
{
|
||||
|
|
@ -400,19 +400,89 @@
|
|||
"diff": {}
|
||||
},
|
||||
{
|
||||
"seq": 0,
|
||||
"seq": 44,
|
||||
"checkpoint": {
|
||||
"timestamp": "2026-07-08T15:07:42.343955440Z",
|
||||
"timestamp": "2026-07-08T15:07:46.323516967Z",
|
||||
"current_node": "fork",
|
||||
"completed_nodes": [
|
||||
"start",
|
||||
"fork"
|
||||
],
|
||||
"node_retries": {},
|
||||
"context_values": {
|
||||
"outcome": "succeeded",
|
||||
"internal.thread_id": "start",
|
||||
"thread.start.current_node": "fork",
|
||||
"internal.retry_count.fork": 0,
|
||||
"internal.work_dir": "/home/daytona/workspace/fabro",
|
||||
"current_node": "fork",
|
||||
"failure_class": "",
|
||||
"failure_signature": "",
|
||||
"graph.model_stylesheet": "* { model: claude-sonnet-4-5; }",
|
||||
"graph.rankdir": "LR",
|
||||
"parallel.results": [
|
||||
{
|
||||
"id": "a",
|
||||
"status": "succeeded",
|
||||
"head_sha": "379d13d4f7218e30c9ec925d730f40194c63616b"
|
||||
},
|
||||
{
|
||||
"id": "b",
|
||||
"status": "succeeded",
|
||||
"head_sha": "8ee19a3b6be176881b7063c1340d26477c997742"
|
||||
}
|
||||
],
|
||||
"internal.run_id": "01KX148ZAMMJRAADHK1HBF7PC3",
|
||||
"internal.retry_count.start": 0,
|
||||
"parallel.branch_count": 2,
|
||||
"internal.node_visit_count": 1,
|
||||
"graph.goal": "Repro: does the synth/merge node see branch outputs?",
|
||||
"internal.fidelity": "compact"
|
||||
},
|
||||
"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",
|
||||
"git_commit_sha": "9b85837e7249b74aaedc28a45a1ecbf402d4d63f",
|
||||
"node_visits": {
|
||||
"start": 1,
|
||||
"fork": 1
|
||||
}
|
||||
},
|
||||
"diff": {
|
||||
"summary": {
|
||||
"files_changed": 0,
|
||||
"additions": 0,
|
||||
"deletions": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"seq": 0,
|
||||
"checkpoint": {
|
||||
"timestamp": "2026-07-08T15:07:46.363877667Z",
|
||||
"current_node": "merge",
|
||||
"completed_nodes": [
|
||||
"start",
|
||||
"fork",
|
||||
"merge"
|
||||
],
|
||||
"node_retries": {},
|
||||
"context_values": {
|
||||
"internal.run_id": "01KX148ZAMMJRAADHK1HBF7PC3",
|
||||
"failure_signature": "",
|
||||
"internal.thread_id": "start",
|
||||
"internal.thread_id": "fork",
|
||||
"thread.fork.current_node": "merge",
|
||||
"internal.retry_count.merge": 0,
|
||||
"graph.model_stylesheet": "* { model: claude-sonnet-4-5; }",
|
||||
"parallel.results": [
|
||||
{
|
||||
|
|
@ -435,24 +505,44 @@
|
|||
"graph.goal": "Repro: does the synth/merge node see branch outputs?",
|
||||
"internal.retry_count.fork": 0,
|
||||
"graph.rankdir": "LR",
|
||||
"parallel.fan_in.best_id": "a",
|
||||
"parallel.branch_count": 2,
|
||||
"parallel.fan_in.best_outcome": "succeeded",
|
||||
"parallel.fan_in.best_head_sha": "379d13d4f7218e30c9ec925d730f40194c63616b",
|
||||
"outcome": "succeeded",
|
||||
"current_node": "fork"
|
||||
"current_node": "merge"
|
||||
},
|
||||
"node_outcomes": {
|
||||
"start": {
|
||||
"merge": {
|
||||
"status": "succeeded",
|
||||
"usage": null
|
||||
"context_updates": {
|
||||
"parallel.fan_in.best_id": "a",
|
||||
"parallel.fan_in.best_outcome": "succeeded",
|
||||
"parallel.fan_in.best_head_sha": "379d13d4f7218e30c9ec925d730f40194c63616b"
|
||||
},
|
||||
"notes": "Selected best candidate: a",
|
||||
"usage": null,
|
||||
"timing": {
|
||||
"wall_time_ms": 0,
|
||||
"inference_time_ms": 0,
|
||||
"tool_time_ms": 0,
|
||||
"active_time_ms": 0
|
||||
}
|
||||
},
|
||||
"fork": {
|
||||
"status": "succeeded",
|
||||
"jump_to_node": "merge",
|
||||
"notes": "Parallel node dispatched 2 branches (2 succeeded, 0 failed)",
|
||||
"usage": null
|
||||
},
|
||||
"start": {
|
||||
"status": "succeeded",
|
||||
"usage": null
|
||||
}
|
||||
},
|
||||
"next_node_id": "merge",
|
||||
"next_node_id": "synth",
|
||||
"node_visits": {
|
||||
"merge": 1,
|
||||
"start": 1,
|
||||
"fork": 1
|
||||
}
|
||||
|
|
@ -520,11 +610,39 @@
|
|||
},
|
||||
"state": "succeeded"
|
||||
},
|
||||
"merge@1": {
|
||||
"first_event_seq": 47,
|
||||
"prompt": null,
|
||||
"response": null,
|
||||
"completion": null,
|
||||
"provider_used": null,
|
||||
"diff": null,
|
||||
"script_invocation": null,
|
||||
"script_timing": null,
|
||||
"parallel_results": null,
|
||||
"output": null,
|
||||
"started_at": "2026-07-08T15:07:46.323790616Z",
|
||||
"handler": "parallel.fan_in",
|
||||
"usage": {
|
||||
"input_tokens": 0,
|
||||
"output_tokens": 0,
|
||||
"total_tokens": 0,
|
||||
"reasoning_tokens": 0,
|
||||
"cache_read_tokens": 0,
|
||||
"cache_write_tokens": 0
|
||||
},
|
||||
"state": "running"
|
||||
},
|
||||
"fork@1": {
|
||||
"first_event_seq": 21,
|
||||
"prompt": null,
|
||||
"response": null,
|
||||
"completion": null,
|
||||
"completion": {
|
||||
"outcome": "succeeded",
|
||||
"notes": "Parallel node dispatched 2 branches (2 succeeded, 0 failed)",
|
||||
"failure_reason": null,
|
||||
"timestamp": "2026-07-08T15:07:42.343874651Z"
|
||||
},
|
||||
"provider_used": {
|
||||
"mode": "prompt",
|
||||
"provider": "anthropic",
|
||||
|
|
@ -533,10 +651,27 @@
|
|||
"diff": null,
|
||||
"script_invocation": null,
|
||||
"script_timing": null,
|
||||
"parallel_results": null,
|
||||
"parallel_results": [
|
||||
{
|
||||
"id": "a",
|
||||
"status": "succeeded",
|
||||
"head_sha": "379d13d4f7218e30c9ec925d730f40194c63616b"
|
||||
},
|
||||
{
|
||||
"id": "b",
|
||||
"status": "succeeded",
|
||||
"head_sha": "8ee19a3b6be176881b7063c1340d26477c997742"
|
||||
}
|
||||
],
|
||||
"output": null,
|
||||
"started_at": "2026-07-08T15:07:08.314410727Z",
|
||||
"handler": "parallel",
|
||||
"timing": {
|
||||
"wall_time_ms": 34029,
|
||||
"inference_time_ms": 0,
|
||||
"tool_time_ms": 0,
|
||||
"active_time_ms": 0
|
||||
},
|
||||
"usage": {
|
||||
"input_tokens": 49,
|
||||
"output_tokens": 2021,
|
||||
|
|
@ -550,7 +685,7 @@
|
|||
"provider": "anthropic",
|
||||
"model_id": "claude-sonnet-4-5"
|
||||
},
|
||||
"state": "running"
|
||||
"state": "succeeded"
|
||||
}
|
||||
}
|
||||
}
|
||||
12
stages/002-fork@1/parallel_results.json
Normal file
12
stages/002-fork@1/parallel_results.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[
|
||||
{
|
||||
"id": "a",
|
||||
"status": "succeeded",
|
||||
"head_sha": "379d13d4f7218e30c9ec925d730f40194c63616b"
|
||||
},
|
||||
{
|
||||
"id": "b",
|
||||
"status": "succeeded",
|
||||
"head_sha": "8ee19a3b6be176881b7063c1340d26477c997742"
|
||||
}
|
||||
]
|
||||
|
|
@ -1,228 +0,0 @@
|
|||
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.
|
||||
6
stages/002-fork@1/status.json
Normal file
6
stages/002-fork@1/status.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"outcome": "succeeded",
|
||||
"notes": "Parallel node dispatched 2 branches (2 succeeded, 0 failed)",
|
||||
"failure_reason": null,
|
||||
"timestamp": "2026-07-08T15:07:42.343874651Z"
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue