diff --git a/Cargo.lock b/Cargo.lock index a75421225..59da7c196 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5181,7 +5181,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "petri-attractor-steps" version = "0.1.0" -source = "git+https://github.com/lithoscomputer/petri.git?branch=main#165c6ba4b6e928ec10a01688bf85de4bc5418233" +source = "git+https://github.com/lithoscomputer/petri.git?branch=main#f30a12df140fb60f55a7c6cff3d7f45673c7a7c5" dependencies = [ "async-trait", "globset", @@ -5212,7 +5212,7 @@ dependencies = [ [[package]] name = "petri-driver" version = "0.1.0" -source = "git+https://github.com/lithoscomputer/petri.git?branch=main#165c6ba4b6e928ec10a01688bf85de4bc5418233" +source = "git+https://github.com/lithoscomputer/petri.git?branch=main#f30a12df140fb60f55a7c6cff3d7f45673c7a7c5" dependencies = [ "async-trait", "getrandom 0.3.4", @@ -5232,7 +5232,7 @@ dependencies = [ [[package]] name = "petri-engine" version = "0.1.0" -source = "git+https://github.com/lithoscomputer/petri.git?branch=main#165c6ba4b6e928ec10a01688bf85de4bc5418233" +source = "git+https://github.com/lithoscomputer/petri.git?branch=main#f30a12df140fb60f55a7c6cff3d7f45673c7a7c5" dependencies = [ "petri-ir", "serde", @@ -5244,7 +5244,7 @@ dependencies = [ [[package]] name = "petri-execution" version = "0.1.0" -source = "git+https://github.com/lithoscomputer/petri.git?branch=main#165c6ba4b6e928ec10a01688bf85de4bc5418233" +source = "git+https://github.com/lithoscomputer/petri.git?branch=main#f30a12df140fb60f55a7c6cff3d7f45673c7a7c5" dependencies = [ "async-trait", "petri-driver", @@ -5268,7 +5268,7 @@ dependencies = [ [[package]] name = "petri-executor" version = "0.1.0" -source = "git+https://github.com/lithoscomputer/petri.git?branch=main#165c6ba4b6e928ec10a01688bf85de4bc5418233" +source = "git+https://github.com/lithoscomputer/petri.git?branch=main#f30a12df140fb60f55a7c6cff3d7f45673c7a7c5" dependencies = [ "async-trait", "libc", @@ -5283,7 +5283,7 @@ dependencies = [ [[package]] name = "petri-executor-sandbox" version = "0.1.0" -source = "git+https://github.com/lithoscomputer/petri.git?branch=main#165c6ba4b6e928ec10a01688bf85de4bc5418233" +source = "git+https://github.com/lithoscomputer/petri.git?branch=main#f30a12df140fb60f55a7c6cff3d7f45673c7a7c5" dependencies = [ "async-trait", "petri-executor", @@ -5305,7 +5305,7 @@ dependencies = [ [[package]] name = "petri-frontend" version = "0.1.0" -source = "git+https://github.com/lithoscomputer/petri.git?branch=main#165c6ba4b6e928ec10a01688bf85de4bc5418233" +source = "git+https://github.com/lithoscomputer/petri.git?branch=main#f30a12df140fb60f55a7c6cff3d7f45673c7a7c5" dependencies = [ "marked-yaml", "petri-ir", @@ -5319,7 +5319,7 @@ dependencies = [ [[package]] name = "petri-frontend-attractor" version = "0.1.0" -source = "git+https://github.com/lithoscomputer/petri.git?branch=main#165c6ba4b6e928ec10a01688bf85de4bc5418233" +source = "git+https://github.com/lithoscomputer/petri.git?branch=main#f30a12df140fb60f55a7c6cff3d7f45673c7a7c5" dependencies = [ "minijinja", "petri-frontend", @@ -5336,7 +5336,7 @@ dependencies = [ [[package]] name = "petri-frontend-fabro" version = "0.1.0" -source = "git+https://github.com/lithoscomputer/petri.git?branch=main#165c6ba4b6e928ec10a01688bf85de4bc5418233" +source = "git+https://github.com/lithoscomputer/petri.git?branch=main#f30a12df140fb60f55a7c6cff3d7f45673c7a7c5" dependencies = [ "petri-frontend", "petri-frontend-attractor", @@ -5351,7 +5351,7 @@ dependencies = [ [[package]] name = "petri-frontend-native" version = "0.1.0" -source = "git+https://github.com/lithoscomputer/petri.git?branch=main#165c6ba4b6e928ec10a01688bf85de4bc5418233" +source = "git+https://github.com/lithoscomputer/petri.git?branch=main#f30a12df140fb60f55a7c6cff3d7f45673c7a7c5" dependencies = [ "petri-frontend", "petri-ir", @@ -5362,7 +5362,7 @@ dependencies = [ [[package]] name = "petri-ir" version = "0.1.0" -source = "git+https://github.com/lithoscomputer/petri.git?branch=main#165c6ba4b6e928ec10a01688bf85de4bc5418233" +source = "git+https://github.com/lithoscomputer/petri.git?branch=main#f30a12df140fb60f55a7c6cff3d7f45673c7a7c5" dependencies = [ "regex", "serde", @@ -5375,7 +5375,7 @@ dependencies = [ [[package]] name = "petri-runtime" version = "0.1.0" -source = "git+https://github.com/lithoscomputer/petri.git?branch=main#165c6ba4b6e928ec10a01688bf85de4bc5418233" +source = "git+https://github.com/lithoscomputer/petri.git?branch=main#f30a12df140fb60f55a7c6cff3d7f45673c7a7c5" dependencies = [ "async-trait", "petri-driver", @@ -5396,7 +5396,7 @@ dependencies = [ [[package]] name = "petri-steps" version = "0.1.0" -source = "git+https://github.com/lithoscomputer/petri.git?branch=main#165c6ba4b6e928ec10a01688bf85de4bc5418233" +source = "git+https://github.com/lithoscomputer/petri.git?branch=main#f30a12df140fb60f55a7c6cff3d7f45673c7a7c5" dependencies = [ "async-trait", "petri-executor", @@ -5412,7 +5412,7 @@ dependencies = [ [[package]] name = "petri-store" version = "0.1.0" -source = "git+https://github.com/lithoscomputer/petri.git?branch=main#165c6ba4b6e928ec10a01688bf85de4bc5418233" +source = "git+https://github.com/lithoscomputer/petri.git?branch=main#f30a12df140fb60f55a7c6cff3d7f45673c7a7c5" dependencies = [ "async-trait", "getrandom 0.3.4", @@ -5427,7 +5427,7 @@ dependencies = [ [[package]] name = "petri-testkit" version = "0.1.0" -source = "git+https://github.com/lithoscomputer/petri.git?branch=main#165c6ba4b6e928ec10a01688bf85de4bc5418233" +source = "git+https://github.com/lithoscomputer/petri.git?branch=main#f30a12df140fb60f55a7c6cff3d7f45673c7a7c5" dependencies = [ "async-trait", "petri-driver", diff --git a/docs/public/execution/context.mdx b/docs/public/execution/context.mdx index ead66336d..a5ad6efbc 100644 --- a/docs/public/execution/context.mdx +++ b/docs/public/execution/context.mdx @@ -206,7 +206,7 @@ Goal: Add a /health endpoint to the API server ## Completed stages - **plan**: success - Model: claude-sonnet-4-5, 12.4k tokens in / 3.2k out - - Files: docs/plan.md + - Files: ["docs/plan.md"] - **test**: success - Script: `cargo test 2>&1 || true` - Output: @@ -230,6 +230,18 @@ The preamble includes: Internal keys (prefixed with `internal.`, `current`, `graph.`, `thread.`, `response.`) are excluded from preambles to avoid noise. +Native agent stages include their tracked file paths in `compact`, +`summary:medium`, and `summary:high` preambles. The list includes successful +native write, edit, and patch calls across the stage's turns, including repair +turns and child-agent writes. Paths are sorted and listed once. A moved file +uses its destination; deletions and changes made only through shell commands +are not tracked. ACP agents and prompt-only stages do not produce this list. + +The file list follows the preamble size limit below; the complete list remains +in the recorded stage output. `full`, `truncate`, and `summary:low` do not add a +file-list detail. Continuing a shared conversation does not attribute an +earlier stage's edits to the next stage. + ### Large preamble values In fidelity modes that render context or completed-stage output, one value can contribute at most 8 KiB of serialized JSON inline. Fabro stores larger values as content-addressed blobs, materializes them as readable files, and puts the size, file path, and a 300-character preview in the preamble. The agent can read the file when it needs the full value. diff --git a/lib/components/fabro-petri/tests/stage_files.rs b/lib/components/fabro-petri/tests/stage_files.rs new file mode 100644 index 000000000..771ac6085 --- /dev/null +++ b/lib/components/fabro-petri/tests/stage_files.rs @@ -0,0 +1,123 @@ +//! A tracked native edit reaches both Fabro's public stage view and the next +//! prompt. + +mod support; + +use std::sync::Arc; + +use fabro_petri::check::Launch; +use fabro_petri::engine::{self, RunStatus}; +use fabro_petri::projection::{Item, RunView}; +use fabro_petri::runtime::RuntimeSpec; +use fabro_store::platform_records::{PlatformRecord, RunCreatedRecord, StoredPlatformRecord}; +use fabro_types::{RunId, StageId, test_support as types_support}; +use pebble_coding_agent::test_support::{ + ScriptedCall, message_text, scripted_client, text_response, tool_call_response, +}; +use petri_execution::events::replay_run; +use petri_store::{Access, MemoryRunStore, RunKey, RunStore}; +use serde_json::json; +use support::{Silent, no_questions, run_request}; + +#[tokio::test] +async fn compact_successor_receives_tracked_native_files() { + let root = tempfile::tempdir().expect("runtime directory"); + let (client, provider) = scripted_client(vec![ + ScriptedCall::response(tool_call_response( + "write_file", + "write", + json!({"file_path":"direct-edit.txt", "content":"changed"}), + )), + ScriptedCall::response(text_response("Done.")), + ScriptedCall::response(text_response("Reviewed.")), + ]); + let runtime = RuntimeSpec { + model_client: Some(client), + ..RuntimeSpec::default() + }; + let workflow = r#"digraph Files { + graph [backend="api", default_max_retries=0] + start [shape=Mdiamond] + edit [prompt="Write the file"] + review [prompt="Review the edit", fidelity="compact"] + exit [shape=Msquare] + start -> edit -> review -> exit + }"#; + let graphs = support::admit( + &[ + ("workflow.fabro", workflow), + ("workflow.toml", support::SETTINGS), + ], + Launch { + default_model: Some("test/model".into()), + ..Launch::default() + }, + &runtime, + ); + let store = Arc::new(MemoryRunStore::new()); + let run_id = RunId::new(); + let outcome = engine::run(run_request( + &run_id.to_string(), + &root.path().join("run"), + graphs, + store.clone(), + runtime, + no_questions(Arc::new(Silent)), + )) + .await + .expect("workflow run settles"); + assert_eq!(outcome.status, RunStatus::Success, "{outcome:?}"); + let logs = store + .open(&RunKey::new(run_id.to_string()), Access::Read) + .await + .expect("stored run"); + let events = replay_run(&*logs).await.expect("replayed run"); + let mut spec = types_support::test_run_spec(); + spec.run_id = run_id; + let created = StoredPlatformRecord { + seq: 1, + recorded_at: 0, + position: None, + record: PlatformRecord::RunCreated(RunCreatedRecord { + spec, + title: Some("Tracked files".into()), + parent_id: None, + retried_from: None, + web_url: None, + }), + }; + let mut view = RunView::new(); + view.fold(&Item::Platform(&created), 1); + for (index, event) in events.iter().enumerate() { + view.fold(&Item::Petri(event), index as u64 + 2); + } + let projection = view.projection().expect("public run projection"); + let edit = projection + .stage(&StageId::new("edit", 1)) + .expect("edit stage"); + assert_eq!( + edit.agent + .as_ref() + .expect("native agent") + .totals + .files_touched, + ["direct-edit.txt"] + ); + let review = projection + .stage(&StageId::new("review", 1)) + .expect("review stage"); + assert!( + review + .agent + .as_ref() + .expect("native agent") + .totals + .files_touched + .is_empty() + ); + let requests = provider.requests(); + assert_eq!(requests.len(), 3); + let sent = message_text(requests[2].messages().last().expect("review input")); + assert!(sent.contains("- **edit**: succeeded"), "{sent}"); + assert!(sent.contains("Files: [\"direct-edit.txt\"]"), "{sent}"); +}