Skip to main content

WriteObserver

Trait WriteObserver 

Source
pub trait WriteObserver:
    Send
    + Sync
    + Debug {
    // Required methods
    fn before_write<'life0, 'life1, 'async_trait>(
        &'life0 self,
        path: &'life1 Path,
    ) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait;
    fn after_write<'life0, 'life1, 'async_trait>(
        &'life0 self,
        path: &'life1 Path,
    ) -> Pin<Box<dyn Future<Output = Option<String>> + Send + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait;
}
Expand description

P5-9 (design §2 module 20 checkpoint, §2.1 D-5 “write-path interception seam shared with formatters”): the ONE well-defined interception point around every file-mutating built-in tool (write_file/edit_file/apply_patch) — installed on ToolContext::write_observer, None by default. Both hooks fire AFTER ToolContext::check_write has already approved the call (so an observer never sees a write the sandbox itself refused) and BEFORE/AFTER the actual mutation:

  • Self::before_write — pre-image capture. crate::checkpoint’s crate::checkpoint::CheckpointObserver is the only implementation today: it snapshots path’s current on-disk content (or records “did not exist”) so a later checkpoint restore can undo the write.
  • Self::after_write — post-write. A true no-op in every implementation shipped so far; reserved for formatters (P5-11, design line 510 “shared seam with checkpoint”) to run format-on-write from, without needing a SECOND interception point wired through the same three tools.

None (the default — [capabilities.checkpoint] off and no formatters module yet) means neither hook is ever consulted: every write-tool call-site’s observer check is if let Some(obs) = &ctx.write_observer, a branch that’s simply never taken, so behavior is byte-identical to before this seam existed.

P5-11 (§2 modules 28/29 lsp/formatters, C10): async_trait (rather than the plain sync methods P5-9 originally shipped) because BOTH new observers need real async I/O in after_writeformatters spawns and awaits a subprocess, lsp writes/reads framed JSON-RPC over a child’s stdio — and neither can block the tokio runtime thread the way a synchronous call from inside an already-async fn execute() would. CheckpointObserver’s own hooks stay synchronous internally (plain blocking std::fs calls); wrapping them in async fn changes nothing observable for it, since that blocking work already ran on the calling task before this signature changed. after_write now RETURNS Option<String> — an annotation to append to the calling tool’s result string (formatter diff-back content, or LSP diagnostics) — None when the observer has nothing to report, which is the only value CheckpointObserver::after_write (still a no-op) ever returns, keeping today’s tool-result text byte-identical whenever checkpoint is the only observer installed.

Required Methods§

Source

fn before_write<'life0, 'life1, 'async_trait>( &'life0 self, path: &'life1 Path, ) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

path (already resolved + sandbox-checked) is about to be created/overwritten/deleted. Implementations must be fast and must never propagate a failure as a tool error — a capture failure should degrade the OBSERVER (e.g. disable itself with a one-time warning), never block or fail the user’s actual edit.

Source

fn after_write<'life0, 'life1, 'async_trait>( &'life0 self, path: &'life1 Path, ) -> Pin<Box<dyn Future<Output = Option<String>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

path was just written/deleted successfully. Not called when the tool call itself failed (e.g. the write errored before completing). Returns an optional annotation for the calling tool’s result text — see the trait doc comment above.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§