-
Notifications
You must be signed in to change notification settings - Fork 3k
feat(file_state): guard cross-agent file edits from stale sibling writes #3316
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
senamakel
merged 1 commit into
tinyhumansai:main
from
senamakel:issue/3253-guard-cross-agent-file-edits-from-stale
Jun 3, 2026
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| //! Task-local carrier for the currently-executing agent's identity so | ||
| //! file tools can attribute reads/writes without widening the `Tool` trait. | ||
| //! | ||
| //! Follows the same pattern as `sandbox_context.rs`. Set by the agent | ||
| //! harness around tool execution; tools read via [`current_file_state_agent_id`]. | ||
|
|
||
| tokio::task_local! { | ||
| static FILE_STATE_AGENT_ID: String; | ||
| } | ||
|
|
||
| /// Returns the current agent's identity for file-state tracking, if set. | ||
| /// | ||
| /// Returns `None` outside an agent turn (CLI, JSON-RPC direct, unit tests). | ||
| pub fn current_file_state_agent_id() -> Option<String> { | ||
| FILE_STATE_AGENT_ID.try_with(|id| id.clone()).ok() | ||
| } | ||
|
|
||
| /// Run `future` with `agent_id` installed as the file-state identity. | ||
| pub async fn with_file_state_agent_id<F, R>(agent_id: String, future: F) -> R | ||
| where | ||
| F: std::future::Future<Output = R>, | ||
| { | ||
| FILE_STATE_AGENT_ID.scope(agent_id, future).await | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[tokio::test] | ||
| async fn returns_none_outside_scope() { | ||
| assert_eq!(current_file_state_agent_id(), None); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn installs_and_reads_agent_id() { | ||
| let observed = | ||
| with_file_state_agent_id("agent-1".into(), async { current_file_state_agent_id() }) | ||
| .await; | ||
| assert_eq!(observed, Some("agent-1".to_string())); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn does_not_leak_across_scopes() { | ||
| with_file_state_agent_id("agent-1".into(), async { | ||
| assert_eq!(current_file_state_agent_id(), Some("agent-1".to_string())); | ||
| }) | ||
| .await; | ||
| assert_eq!(current_file_state_agent_id(), None); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn nested_scope_overrides_outer() { | ||
| with_file_state_agent_id("parent".into(), async { | ||
| assert_eq!(current_file_state_agent_id(), Some("parent".to_string())); | ||
| with_file_state_agent_id("child".into(), async { | ||
| assert_eq!(current_file_state_agent_id(), Some("child".to_string())); | ||
| }) | ||
| .await; | ||
| assert_eq!(current_file_state_agent_id(), Some("parent".to_string())); | ||
| }) | ||
| .await; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| //! Process-wide file state coordinator for cross-agent staleness detection. | ||
| //! | ||
| //! Parallel subagents and worker threads share a workspace. Without | ||
| //! coordination one worker can read a file, a sibling can edit it, and | ||
| //! the first worker can later write based on stale content. This module | ||
| //! tracks per-agent read stamps and per-path write stamps so that write | ||
| //! tools can detect the conflict and return a model-facing error | ||
| //! requiring the agent to re-read. | ||
| //! | ||
| //! Disable with `OPENHUMAN_FILE_STATE_GUARD=0` (or `false`). | ||
|
|
||
| mod agent_context; | ||
| mod ops; | ||
| mod types; | ||
|
|
||
| pub use agent_context::{current_file_state_agent_id, with_file_state_agent_id}; | ||
| pub use ops::{ | ||
| acquire_path_lock, check_partial_read, check_stale_read, init_global, parent_stale_files, | ||
| record_read, record_write, try_global, | ||
| }; | ||
| pub use types::{FileStateCoordinator, ReadStamp}; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make the bus file-state ID unique per turn.
"bus:{channel}:{target_agent}"collapses every concurrent turn on the same channel/agent into one logical writer. Since stale-read detection ignores same-agent writes, overlapping root turns can overwrite each other without tripping the new guard. Include a per-turn/session discriminator here (or generate one once per request) so each run gets its own file-state identity.🤖 Prompt for AI Agents