merge: provider trace spawn preservation
This commit is contained in:
commit
f927b37b83
|
|
@ -9,7 +9,8 @@ mod scope;
|
||||||
pub use cascade::{LayerLoadError, find_project_manifest_from, load_layer};
|
pub use cascade::{LayerLoadError, find_project_manifest_from, load_layer};
|
||||||
pub use config::{
|
pub use config::{
|
||||||
CompactionConfigPartial, FileUploadLimitsPartial, PermissionConfigPartial, PodManifestConfig,
|
CompactionConfigPartial, FileUploadLimitsPartial, PermissionConfigPartial, PodManifestConfig,
|
||||||
PodMetaConfig, ResolveError, ToolOutputLimitsPartial, WorkerManifestConfig,
|
PodMetaConfig, ResolveError, SessionConfigPartial, ToolOutputLimitsPartial,
|
||||||
|
WorkerManifestConfig,
|
||||||
};
|
};
|
||||||
pub use model::{
|
pub use model::{
|
||||||
AuthRef, ModelCapability, ModelManifest, ReasoningControl, ReasoningEffort, SchemeKind,
|
AuthRef, ModelCapability, ModelManifest, ReasoningControl, ReasoningEffort, SchemeKind,
|
||||||
|
|
@ -395,9 +396,10 @@ pub struct ScopeConfig {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
|
||||||
pub struct SessionConfig {
|
pub struct SessionConfig {
|
||||||
/// Persist every provider stream event directly to `trace.jsonl` next to the
|
/// Persist normalized provider stream events and lifecycle diagnostics to a
|
||||||
/// segment log. Intended for debugging stalls between stream requests; off
|
/// `.trace.jsonl` sidecar next to the segment log. This is not guaranteed to
|
||||||
/// by default because it can be verbose.
|
/// be a byte-for-byte raw SSE capture. Intended for debugging stalls between
|
||||||
|
/// stream requests; off by default because it can be verbose.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub record_event_trace: bool,
|
pub record_event_trace: bool,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -898,6 +898,27 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn profile_artifact_preserves_session_record_event_trace() {
|
||||||
|
let mut raw = artifact();
|
||||||
|
raw["manifest"]["session"] = serde_json::json!({ "record_event_trace": true });
|
||||||
|
|
||||||
|
let resolved = resolve_profile_artifact(
|
||||||
|
ProfileSource::Path {
|
||||||
|
path: PathBuf::from("/profiles/coder.nix"),
|
||||||
|
},
|
||||||
|
Path::new("/workspace/project"),
|
||||||
|
raw,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(resolved.manifest.session.record_event_trace);
|
||||||
|
assert_eq!(
|
||||||
|
resolved.manifest_snapshot["session"]["record_event_trace"],
|
||||||
|
serde_json::json!(true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rejects_both_manifest_and_config_fields() {
|
fn rejects_both_manifest_and_config_fields() {
|
||||||
let err = resolve_profile_artifact(
|
let err = resolve_profile_artifact(
|
||||||
|
|
|
||||||
|
|
@ -502,6 +502,7 @@ where
|
||||||
let web_config = pod.manifest().web.clone();
|
let web_config = pod.manifest().web.clone();
|
||||||
let spawner_name = pod.manifest().pod.name.clone();
|
let spawner_name = pod.manifest().pod.name.clone();
|
||||||
let spawner_model = pod.manifest().model.clone();
|
let spawner_model = pod.manifest().model.clone();
|
||||||
|
let spawner_record_event_trace = pod.manifest().session.record_event_trace;
|
||||||
let pod_store = pod.store().clone();
|
let pod_store = pod.store().clone();
|
||||||
let self_parent_socket = pod.callback_socket().cloned();
|
let self_parent_socket = pod.callback_socket().cloned();
|
||||||
|
|
||||||
|
|
@ -556,6 +557,7 @@ where
|
||||||
spawned_registry.clone(),
|
spawned_registry.clone(),
|
||||||
self_parent_socket,
|
self_parent_socket,
|
||||||
spawner_model,
|
spawner_model,
|
||||||
|
spawner_record_event_trace,
|
||||||
scope_handle,
|
scope_handle,
|
||||||
));
|
));
|
||||||
worker.register_tool(send_to_pod_tool(spawned_registry.clone()));
|
worker.register_tool(send_to_pod_tool(spawned_registry.clone()));
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ use async_trait::async_trait;
|
||||||
use llm_worker::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
|
use llm_worker::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
|
||||||
use manifest::{
|
use manifest::{
|
||||||
ModelManifest, Permission, PodManifestConfig, PodMetaConfig, ScopeConfig, ScopeRule,
|
ModelManifest, Permission, PodManifestConfig, PodMetaConfig, ScopeConfig, ScopeRule,
|
||||||
SharedScope, WorkerManifestConfig,
|
SessionConfigPartial, SharedScope, WorkerManifestConfig,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::net::UnixStream;
|
use tokio::net::UnixStream;
|
||||||
|
|
@ -119,6 +119,9 @@ pub struct SpawnPodTool {
|
||||||
/// configuration. Per-spawn override is
|
/// configuration. Per-spawn override is
|
||||||
/// out of scope here (see `tickets/spawn-inherit-provider.md`).
|
/// out of scope here (see `tickets/spawn-inherit-provider.md`).
|
||||||
spawner_model: ModelManifest,
|
spawner_model: ModelManifest,
|
||||||
|
/// Spawner's session diagnostics policy. Preserved for spawned Pods so
|
||||||
|
/// opt-in provider event traces continue across delegation.
|
||||||
|
spawner_record_event_trace: bool,
|
||||||
/// Spawner's runtime scope. After a successful spawn, the
|
/// Spawner's runtime scope. After a successful spawn, the
|
||||||
/// `Permission::Write` rules in the delegated scope are revoked
|
/// `Permission::Write` rules in the delegated scope are revoked
|
||||||
/// from the spawner's in-memory view (a `deny(Write, target)` is
|
/// from the spawner's in-memory view (a `deny(Write, target)` is
|
||||||
|
|
@ -138,6 +141,7 @@ impl SpawnPodTool {
|
||||||
registry: Arc<SpawnedPodRegistry>,
|
registry: Arc<SpawnedPodRegistry>,
|
||||||
parent_socket: Option<PathBuf>,
|
parent_socket: Option<PathBuf>,
|
||||||
spawner_model: ModelManifest,
|
spawner_model: ModelManifest,
|
||||||
|
spawner_record_event_trace: bool,
|
||||||
spawner_scope: SharedScope,
|
spawner_scope: SharedScope,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -148,6 +152,7 @@ impl SpawnPodTool {
|
||||||
registry,
|
registry,
|
||||||
parent_socket,
|
parent_socket,
|
||||||
spawner_model,
|
spawner_model,
|
||||||
|
spawner_record_event_trace,
|
||||||
spawner_scope,
|
spawner_scope,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -207,6 +212,7 @@ impl Tool for SpawnPodTool {
|
||||||
&instruction,
|
&instruction,
|
||||||
&scope_allow,
|
&scope_allow,
|
||||||
&self.spawner_model,
|
&self.spawner_model,
|
||||||
|
self.spawner_record_event_trace,
|
||||||
) {
|
) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|
@ -384,6 +390,7 @@ fn build_spawn_config_json(
|
||||||
instruction: &str,
|
instruction: &str,
|
||||||
scope_allow: &[ScopeRule],
|
scope_allow: &[ScopeRule],
|
||||||
model: &ModelManifest,
|
model: &ModelManifest,
|
||||||
|
record_event_trace: bool,
|
||||||
) -> Result<String, serde_json::Error> {
|
) -> Result<String, serde_json::Error> {
|
||||||
let config = PodManifestConfig {
|
let config = PodManifestConfig {
|
||||||
pod: PodMetaConfig {
|
pod: PodMetaConfig {
|
||||||
|
|
@ -399,6 +406,9 @@ fn build_spawn_config_json(
|
||||||
allow: scope_allow.to_vec(),
|
allow: scope_allow.to_vec(),
|
||||||
deny: Vec::new(),
|
deny: Vec::new(),
|
||||||
},
|
},
|
||||||
|
session: record_event_trace.then_some(SessionConfigPartial {
|
||||||
|
record_event_trace: Some(true),
|
||||||
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
serde_json::to_string(&config)
|
serde_json::to_string(&config)
|
||||||
|
|
@ -484,6 +494,7 @@ pub fn spawn_pod_tool(
|
||||||
registry: Arc<SpawnedPodRegistry>,
|
registry: Arc<SpawnedPodRegistry>,
|
||||||
parent_socket: Option<PathBuf>,
|
parent_socket: Option<PathBuf>,
|
||||||
spawner_model: ModelManifest,
|
spawner_model: ModelManifest,
|
||||||
|
spawner_record_event_trace: bool,
|
||||||
spawner_scope: SharedScope,
|
spawner_scope: SharedScope,
|
||||||
) -> ToolDefinition {
|
) -> ToolDefinition {
|
||||||
Arc::new(move || {
|
Arc::new(move || {
|
||||||
|
|
@ -500,6 +511,7 @@ pub fn spawn_pod_tool(
|
||||||
registry.clone(),
|
registry.clone(),
|
||||||
parent_socket.clone(),
|
parent_socket.clone(),
|
||||||
spawner_model.clone(),
|
spawner_model.clone(),
|
||||||
|
spawner_record_event_trace,
|
||||||
spawner_scope.clone(),
|
spawner_scope.clone(),
|
||||||
));
|
));
|
||||||
(meta, tool)
|
(meta, tool)
|
||||||
|
|
@ -509,7 +521,7 @@ pub fn spawn_pod_tool(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use manifest::{AuthRef, SchemeKind};
|
use manifest::{AuthRef, PodManifest, SchemeKind};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn spawn_config_inherits_inline_spawner_model() {
|
fn spawn_config_inherits_inline_spawner_model() {
|
||||||
|
|
@ -525,7 +537,7 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
let config_json =
|
let config_json =
|
||||||
build_spawn_config_json("child", "$insomnia/default", &[], &model).unwrap();
|
build_spawn_config_json("child", "$insomnia/default", &[], &model, false).unwrap();
|
||||||
let parsed: PodManifestConfig = serde_json::from_str(&config_json).unwrap();
|
let parsed: PodManifestConfig = serde_json::from_str(&config_json).unwrap();
|
||||||
|
|
||||||
assert_eq!(parsed.model.scheme, Some(SchemeKind::Anthropic));
|
assert_eq!(parsed.model.scheme, Some(SchemeKind::Anthropic));
|
||||||
|
|
@ -548,11 +560,51 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let config_json =
|
let config_json =
|
||||||
build_spawn_config_json("child", "$insomnia/default", &[], &model).unwrap();
|
build_spawn_config_json("child", "$insomnia/default", &[], &model, false).unwrap();
|
||||||
let parsed: PodManifestConfig = serde_json::from_str(&config_json).unwrap();
|
let parsed: PodManifestConfig = serde_json::from_str(&config_json).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parsed.model.ref_.as_deref(),
|
parsed.model.ref_.as_deref(),
|
||||||
Some("anthropic/claude-sonnet-4-6")
|
Some("anthropic/claude-sonnet-4-6")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn spawn_config_preserves_record_event_trace_when_enabled() {
|
||||||
|
let model = ModelManifest {
|
||||||
|
ref_: Some("anthropic/claude-sonnet-4-6".into()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let scope = vec![ScopeRule {
|
||||||
|
target: PathBuf::from("/tmp/child"),
|
||||||
|
permission: Permission::Read,
|
||||||
|
recursive: true,
|
||||||
|
}];
|
||||||
|
|
||||||
|
let config_json =
|
||||||
|
build_spawn_config_json("child", "$insomnia/default", &scope, &model, true).unwrap();
|
||||||
|
let parsed: PodManifestConfig = serde_json::from_str(&config_json).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
parsed.session.as_ref().and_then(|s| s.record_event_trace),
|
||||||
|
Some(true)
|
||||||
|
);
|
||||||
|
|
||||||
|
let manifest: PodManifest = PodManifestConfig::builtin_defaults()
|
||||||
|
.merge(parsed)
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
assert!(manifest.session.record_event_trace);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn spawn_config_omits_record_event_trace_when_disabled() {
|
||||||
|
let model = ModelManifest {
|
||||||
|
ref_: Some("anthropic/claude-sonnet-4-6".into()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let config_json =
|
||||||
|
build_spawn_config_json("child", "$insomnia/default", &[], &model, false).unwrap();
|
||||||
|
let parsed: PodManifestConfig = serde_json::from_str(&config_json).unwrap();
|
||||||
|
|
||||||
|
assert!(parsed.session.is_none());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -192,6 +192,7 @@ async fn spawn_pod_delegates_scope_and_sends_run() {
|
||||||
registry,
|
registry,
|
||||||
None,
|
None,
|
||||||
dummy_model(),
|
dummy_model(),
|
||||||
|
false,
|
||||||
spawner_scope.clone(),
|
spawner_scope.clone(),
|
||||||
);
|
);
|
||||||
let (_meta, tool) = def();
|
let (_meta, tool) = def();
|
||||||
|
|
@ -280,6 +281,7 @@ async fn spawn_pod_rejects_scope_outside_spawner() {
|
||||||
registry,
|
registry,
|
||||||
None,
|
None,
|
||||||
dummy_model(),
|
dummy_model(),
|
||||||
|
false,
|
||||||
spawner_scope.clone(),
|
spawner_scope.clone(),
|
||||||
);
|
);
|
||||||
let (_meta, tool) = def();
|
let (_meta, tool) = def();
|
||||||
|
|
@ -351,6 +353,7 @@ async fn spawn_pod_rolls_back_reservation_when_socket_never_appears() {
|
||||||
registry,
|
registry,
|
||||||
None,
|
None,
|
||||||
dummy_model(),
|
dummy_model(),
|
||||||
|
false,
|
||||||
spawner_scope.clone(),
|
spawner_scope.clone(),
|
||||||
);
|
);
|
||||||
let (_meta, tool) = def();
|
let (_meta, tool) = def();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
//! Debug-only LLM request/stream trace recording.
|
//! Debug-only LLM request/stream trace recording.
|
||||||
//!
|
//!
|
||||||
//! [`TraceEntry`] captures stream lifecycle markers and raw provider stream
|
//! [`TraceEntry`] captures stream lifecycle markers and normalized provider
|
||||||
//! events for debugging stalls. Written to a separate `.trace.jsonl` file,
|
//! stream events for debugging stalls. It is not a byte-for-byte raw SSE
|
||||||
|
//! capture. Written to a separate `.trace.jsonl` file,
|
||||||
//! completely independent of the segment log used for state restoration.
|
//! completely independent of the segment log used for state restoration.
|
||||||
//!
|
//!
|
||||||
//! Disabled by default. Enable via `SessionConfig::record_event_trace`.
|
//! Disabled by default. Enable via `SessionConfig::record_event_trace`.
|
||||||
|
|
@ -10,7 +11,7 @@ use llm_worker::llm_client::event::Event;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
/// A single trace entry recording either a lifecycle marker or raw stream event.
|
/// A single trace entry recording either a lifecycle marker or normalized stream event.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct TraceEntry {
|
pub struct TraceEntry {
|
||||||
/// Timestamp in milliseconds since Unix epoch.
|
/// Timestamp in milliseconds since Unix epoch.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user