From b10f8e5eec96877427ddd73392f8d58b44b3061a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 31 Aug 2026 17:41:50 +0000 Subject: [PATCH] feat(hotpath): fill agent-hosts hook dispatch/store gaps Co-authored-by: Zack Jackson --- .../src/agents/context_scout_model.rs | 3 +++ .../src/hooks/cursor_compact.rs | 1 + .../src/hooks/daemon_ports.rs | 4 ++++ .../tracedecay-agent-hosts/src/hooks/kiro.rs | 3 +++ crates/tracedecay-agent-hosts/src/hooks/mod.rs | 3 +++ .../src/native_integration/store.rs | 18 ++++++++++++++++++ .../src/ports/hook_runtime.rs | 6 ++++++ 7 files changed, 38 insertions(+) diff --git a/crates/tracedecay-agent-hosts/src/agents/context_scout_model.rs b/crates/tracedecay-agent-hosts/src/agents/context_scout_model.rs index b853e80131..f3267892c0 100644 --- a/crates/tracedecay-agent-hosts/src/agents/context_scout_model.rs +++ b/crates/tracedecay-agent-hosts/src/agents/context_scout_model.rs @@ -35,6 +35,7 @@ pub fn context_scout_backend_from_automation_config( } } +#[hotpath::measure(label = "agent_hosts.context_scout.model_route")] pub fn context_scout_model_assistant_from_automation_config( config: &AutomationConfig, ) -> Arc { @@ -166,6 +167,7 @@ fn scout_model_error_from_agent_task(error: AgentTaskError) -> ContextScoutModel } } +#[hotpath::measure(label = "agent_hosts.context_scout.model_request")] fn backend_request( request: ContextScoutModelRequestV1, max_output_tokens: usize, @@ -213,6 +215,7 @@ fn response_schema() -> Value { }) } +#[hotpath::measure(label = "agent_hosts.context_scout.model_proposal")] fn response_to_proposal( response: AgentTaskResponse, requested_backend: ContextScoutModelBackendV1, diff --git a/crates/tracedecay-agent-hosts/src/hooks/cursor_compact.rs b/crates/tracedecay-agent-hosts/src/hooks/cursor_compact.rs index 9232d31ea1..d51b946259 100644 --- a/crates/tracedecay-agent-hosts/src/hooks/cursor_compact.rs +++ b/crates/tracedecay-agent-hosts/src/hooks/cursor_compact.rs @@ -51,6 +51,7 @@ pub async fn cursor_pre_compact_via_daemon(event_json: &str) -> CursorPreCompact cursor_pre_compact_via_daemon_with_telemetry(event_json, None).await } +#[hotpath::measure(future = true, label = "agent_hosts.hooks.cursor.compact_via_daemon")] pub(super) async fn cursor_pre_compact_via_daemon_with_telemetry( event_json: &str, telemetry: Option<&super::analytics::HookTimingSpan>, diff --git a/crates/tracedecay-agent-hosts/src/hooks/daemon_ports.rs b/crates/tracedecay-agent-hosts/src/hooks/daemon_ports.rs index b5f240e41e..8796c7d2fb 100644 --- a/crates/tracedecay-agent-hosts/src/hooks/daemon_ports.rs +++ b/crates/tracedecay-agent-hosts/src/hooks/daemon_ports.rs @@ -112,6 +112,7 @@ pub(crate) fn now_utc() -> UtcMicros { UtcMicros(micros.max(1)) } +#[hotpath::measure(label = "agent_hosts.hook_ports.admission_decode")] pub(crate) fn daemon_admission_response(response: &serde_json::Value) -> DaemonAdmissionResponseV1 { let unavailable = || DaemonAdmissionResponseV1 { immediate: HookImmediateAdmissionV1::Unavailable, @@ -218,6 +219,7 @@ fn delivery_outcome_from_status(status: Option<&str>) -> HookFeedbackDeliveryOut } } +#[hotpath::measure(future = true, label = "agent_hosts.hook_ports.timed_daemon_action")] async fn timed_daemon_hook_action( project_root: &Path, action: serde_json::Value, @@ -291,6 +293,7 @@ impl<'a> DaemonDeliveryReceiptPort<'a> { Self { project_root } } + #[hotpath::measure(future = true, label = "agent_hosts.hook_ports.post_receipt")] pub(crate) async fn post_receipt( &self, receipt: &ContextScoutDeliveryReceiptV1, @@ -411,6 +414,7 @@ impl<'a> DaemonOpenCodeLspUpdatePort<'a> { } } + #[hotpath::measure(future = true, label = "agent_hosts.hook_ports.opencode_lsp_submit")] pub(crate) async fn submit_updated_event(&self, event: &serde_json::Value) -> bool { let response = super::daemon_hook_action( Some(self.project_root), diff --git a/crates/tracedecay-agent-hosts/src/hooks/kiro.rs b/crates/tracedecay-agent-hosts/src/hooks/kiro.rs index 675d92bf80..5077e54abe 100644 --- a/crates/tracedecay-agent-hosts/src/hooks/kiro.rs +++ b/crates/tracedecay-agent-hosts/src/hooks/kiro.rs @@ -25,6 +25,7 @@ const KIRO_HOT_INGEST_BUDGET: std::time::Duration = std::time::Duration::from_mi /// Kiro `preToolUse` hook handler. /// /// Blocks with exit code 2 and stderr, per Kiro's hook contract. +#[hotpath::measure(label = "agent_hosts.hooks.kiro.pre_tool_use")] pub fn hook_kiro_pre_tool_use() -> i32 { let event = read_hook_event!(); let parsed = serde_json::from_str::(&event).unwrap_or(Value::Null); @@ -139,6 +140,7 @@ fn collect_strings<'a>(value: &'a Value, out: &mut Vec<&'a str>) { /// /// Resets the per-turn counter, catches up transcripts, and injects bounded /// user/project memory relevant to the submitted prompt. +#[hotpath::measure(future = true, label = "agent_hosts.hooks.kiro.prompt_submit")] pub async fn hook_kiro_prompt_submit() -> i32 { let event = read_hook_event!(); let parsed = serde_json::from_str::(&event).unwrap_or(Value::Null); @@ -201,6 +203,7 @@ pub async fn hook_kiro_prompt_submit() -> i32 { /// /// Notifies the daemon after Kiro writes. Missing daemon/index state is /// fail-open. +#[hotpath::measure(future = true, label = "agent_hosts.hooks.kiro.post_tool_use")] pub async fn hook_kiro_post_tool_use() -> i32 { let event = read_hook_event!(); // One parse for the root, the analytics row, and the notification. diff --git a/crates/tracedecay-agent-hosts/src/hooks/mod.rs b/crates/tracedecay-agent-hosts/src/hooks/mod.rs index f43054ff79..97ee23799d 100644 --- a/crates/tracedecay-agent-hosts/src/hooks/mod.rs +++ b/crates/tracedecay-agent-hosts/src/hooks/mod.rs @@ -85,6 +85,7 @@ pub fn aggregate_hook_completed_readiness(rows: &[Value]) -> HookCompletedReadin use tool_hints::{HintAgent, ToolHint}; use tracedecay_policy::hint_delivery::HintDeliveryDecisionV1; +#[hotpath::measure(future = true, label = "agent_hosts.hooks.dispatch_kimi_event")] pub async fn dispatch_kimi_event(event_json: &str, project_root: &Path) -> Option { let telemetry = record_other_hook_invoked(Some(project_root), "kimi_event", event_json); dispatch::dispatch( @@ -98,6 +99,7 @@ pub async fn dispatch_kimi_event(event_json: &str, project_root: &Path) -> Optio .flatten() } +#[hotpath::measure(future = true, label = "agent_hosts.hooks.dispatch_opencode_event")] pub async fn dispatch_opencode_event(event_json: &str, project_root: &Path) -> Option { let telemetry = record_other_hook_invoked(Some(project_root), "opencode_event", event_json); let dispatch = if tracedecay_hooks::decode_opencode_lsp_event(event_json.as_bytes()).is_ok() { @@ -114,6 +116,7 @@ pub async fn dispatch_opencode_event(event_json: &str, project_root: &Path) -> O dispatch.into_recorded_guidance(&telemetry).flatten() } +#[hotpath::measure(future = true, label = "agent_hosts.hooks.dispatch_opencode_tool_after")] pub async fn dispatch_opencode_tool_after(event_json: &str, project_root: &Path) -> Option { let telemetry = record_other_hook_invoked(Some(project_root), "opencode_tool_after", event_json); diff --git a/crates/tracedecay-agent-hosts/src/native_integration/store.rs b/crates/tracedecay-agent-hosts/src/native_integration/store.rs index 163f4d1a62..6db4e4e5ff 100644 --- a/crates/tracedecay-agent-hosts/src/native_integration/store.rs +++ b/crates/tracedecay-agent-hosts/src/native_integration/store.rs @@ -201,6 +201,7 @@ impl DaemonNativeIntegrationStore { } /// Persists one issued approval commitment (approval issuance operation). + #[hotpath::measure(label = "agent_hosts.native_store.save_approval")] pub(crate) fn save_approval( &self, approval: NativeIntegrationApprovalV1, @@ -211,6 +212,7 @@ impl DaemonNativeIntegrationStore { } /// Reads one issued approval commitment for apply resolution. + #[hotpath::measure(label = "agent_hosts.native_store.read_approval")] pub(crate) fn read_approval( &self, approval_id: &NativeIntegrationApprovalId, @@ -247,6 +249,7 @@ impl Drop for DaemonNativeIntegrationStore { } impl NativeIntegrationStore for DaemonNativeIntegrationStore { + #[hotpath::measure(label = "agent_hosts.native_store.save_preview")] fn save_preview( &self, preview: NativeIntegrationPreviewV1, @@ -256,6 +259,7 @@ impl NativeIntegrationStore for DaemonNativeIntegrationStore { Self::await_reply(&receiver) } + #[hotpath::measure(label = "agent_hosts.native_store.read_preview")] fn read_preview( &self, preview_id: &NativeIntegrationPreviewId, @@ -265,6 +269,7 @@ impl NativeIntegrationStore for DaemonNativeIntegrationStore { Self::await_reply(&receiver) } + #[hotpath::measure(label = "agent_hosts.native_store.begin_or_replay")] fn begin_or_replay( &self, record: NativeIntegrationRecordV1, @@ -274,6 +279,7 @@ impl NativeIntegrationStore for DaemonNativeIntegrationStore { Self::await_reply(&receiver) } + #[hotpath::measure(label = "agent_hosts.native_store.read_status")] fn read_status( &self, transaction_id: &NativeIntegrationTransactionId, @@ -283,6 +289,7 @@ impl NativeIntegrationStore for DaemonNativeIntegrationStore { Self::await_reply(&receiver) } + #[hotpath::measure(label = "agent_hosts.native_store.read_record")] fn read_record( &self, transaction_id: &NativeIntegrationTransactionId, @@ -292,6 +299,7 @@ impl NativeIntegrationStore for DaemonNativeIntegrationStore { Self::await_reply(&receiver) } + #[hotpath::measure(label = "agent_hosts.native_store.read_receipt")] fn read_receipt( &self, transaction_id: &NativeIntegrationTransactionId, @@ -301,6 +309,7 @@ impl NativeIntegrationStore for DaemonNativeIntegrationStore { Self::await_reply(&receiver) } + #[hotpath::measure(label = "agent_hosts.native_store.compare_and_swap_status")] fn compare_and_swap_status( &self, transaction_id: &NativeIntegrationTransactionId, @@ -317,6 +326,7 @@ impl NativeIntegrationStore for DaemonNativeIntegrationStore { Self::await_reply(&receiver) } + #[hotpath::measure(label = "agent_hosts.native_store.write_terminal")] fn write_terminal( &self, transaction_id: &NativeIntegrationTransactionId, @@ -333,6 +343,7 @@ impl NativeIntegrationStore for DaemonNativeIntegrationStore { Self::await_reply(&receiver) } + #[hotpath::measure(label = "agent_hosts.native_store.pending_transactions")] fn pending_transactions( &self, repository_id: Option<&RepositoryId>, @@ -345,6 +356,7 @@ impl NativeIntegrationStore for DaemonNativeIntegrationStore { Self::await_reply(&receiver) } + #[hotpath::measure(label = "agent_hosts.native_store.approval_consumed")] fn approval_consumed( &self, approval_id: &NativeIntegrationApprovalId, @@ -354,6 +366,7 @@ impl NativeIntegrationStore for DaemonNativeIntegrationStore { Self::await_reply(&receiver) } + #[hotpath::measure(label = "agent_hosts.native_store.quarantine_repository")] fn quarantine_repository( &self, repository_id: &RepositoryId, @@ -368,6 +381,7 @@ impl NativeIntegrationStore for DaemonNativeIntegrationStore { Self::await_reply(&receiver) } + #[hotpath::measure(label = "agent_hosts.native_store.begin_worktree_cleanup")] fn begin_worktree_cleanup( &self, transaction: NativeWorktreeCleanupTransactionV1, @@ -380,6 +394,7 @@ impl NativeIntegrationStore for DaemonNativeIntegrationStore { Self::await_reply(&receiver) } + #[hotpath::measure(label = "agent_hosts.native_store.read_worktree_cleanup")] fn read_worktree_cleanup( &self, confirmation_digest: &ManifestDigest, @@ -392,6 +407,7 @@ impl NativeIntegrationStore for DaemonNativeIntegrationStore { Self::await_reply(&receiver) } + #[hotpath::measure(label = "agent_hosts.native_store.pending_worktree_cleanups")] fn pending_worktree_cleanups( &self, repository_id: &RepositoryId, @@ -406,6 +422,7 @@ impl NativeIntegrationStore for DaemonNativeIntegrationStore { Self::await_reply(&receiver) } + #[hotpath::measure(label = "agent_hosts.native_store.compare_and_swap_worktree_cleanup")] fn compare_and_swap_worktree_cleanup( &self, confirmation_digest: &ManifestDigest, @@ -422,6 +439,7 @@ impl NativeIntegrationStore for DaemonNativeIntegrationStore { Self::await_reply(&receiver) } + #[hotpath::measure(label = "agent_hosts.native_store.write_worktree_cleanup_terminal")] fn write_worktree_cleanup_terminal( &self, confirmation_digest: &ManifestDigest, diff --git a/crates/tracedecay-agent-hosts/src/ports/hook_runtime.rs b/crates/tracedecay-agent-hosts/src/ports/hook_runtime.rs index 876fa8ece8..0f7cde1fc3 100644 --- a/crates/tracedecay-agent-hosts/src/ports/hook_runtime.rs +++ b/crates/tracedecay-agent-hosts/src/ports/hook_runtime.rs @@ -121,6 +121,7 @@ pub fn register_cursor_catch_up_ingest_max_bytes(max_bytes: CursorCatchUpIngestM /// Errors when the root never registered an invoker. Callers already treat a /// daemon request failure as "defer this work and warn", which is the correct /// handling for an unwired build too. +#[hotpath::measure(future = true, label = "agent_hosts.hook_runtime.daemon_tool")] pub async fn daemon_tool_json( project_root: Option<&Path>, tool_name: &str, @@ -137,11 +138,13 @@ pub async fn daemon_tool_json( invoker(project_root, tool_name, arguments, require_project_identity).await } +#[hotpath::measure(future = true, label = "agent_hosts.hook_runtime.resolve_root")] pub async fn resolve_project_root_with_identity(start: &Path) -> Option { let resolver = PROJECT_ROOT_RESOLVER.get()?; resolver(start).await } +#[hotpath::measure(label = "agent_hosts.hook_runtime.resolve_scope")] pub fn resolve_hook_scope( project_root: &Path, project_id: &ProjectId, @@ -152,6 +155,7 @@ pub fn resolve_hook_scope( ) } +#[hotpath::measure(future = true, label = "agent_hosts.hook_runtime.notify_event")] pub async fn notify_hook_event(project_root: &Path, event: DaemonHookEvent) { if let Some(notifier) = HOOK_EVENT_NOTIFIER.get() { notifier(project_root, event).await; @@ -164,6 +168,7 @@ pub fn hook_timings_enabled(project_root: &Path) -> Option { } #[must_use] +#[hotpath::measure(label = "agent_hosts.hook_runtime.project_initialized")] pub fn is_project_initialized(project_root: &Path) -> bool { PROJECT_INITIALIZATION_GATE.get().map_or_else( || { @@ -174,6 +179,7 @@ pub fn is_project_initialized(project_root: &Path) -> bool { ) } +#[hotpath::measure(future = true, label = "agent_hosts.hook_runtime.store_layout")] pub async fn resolve_store_layout(project_root: &Path) -> Result { let Some(resolver) = STORE_LAYOUT_RESOLVER.get() else { return Err(TraceDecayError::Config {