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 config::{
|
||||
CompactionConfigPartial, FileUploadLimitsPartial, PermissionConfigPartial, PodManifestConfig,
|
||||
PodMetaConfig, ResolveError, ToolOutputLimitsPartial, WorkerManifestConfig,
|
||||
PodMetaConfig, ResolveError, SessionConfigPartial, ToolOutputLimitsPartial,
|
||||
WorkerManifestConfig,
|
||||
};
|
||||
pub use model::{
|
||||
AuthRef, ModelCapability, ModelManifest, ReasoningControl, ReasoningEffort, SchemeKind,
|
||||
|
|
@ -395,9 +396,10 @@ pub struct ScopeConfig {
|
|||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
|
||||
pub struct SessionConfig {
|
||||
/// Persist every provider stream event directly to `trace.jsonl` next to the
|
||||
/// segment log. Intended for debugging stalls between stream requests; off
|
||||
/// by default because it can be verbose.
|
||||
/// Persist normalized provider stream events and lifecycle diagnostics to a
|
||||
/// `.trace.jsonl` sidecar next to the segment log. This is not guaranteed to
|
||||
/// 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)]
|
||||
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]
|
||||
fn rejects_both_manifest_and_config_fields() {
|
||||
let err = resolve_profile_artifact(
|
||||
|
|
|
|||
|
|
@ -502,6 +502,7 @@ where
|
|||
let web_config = pod.manifest().web.clone();
|
||||
let spawner_name = pod.manifest().pod.name.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 self_parent_socket = pod.callback_socket().cloned();
|
||||
|
||||
|
|
@ -556,6 +557,7 @@ where
|
|||
spawned_registry.clone(),
|
||||
self_parent_socket,
|
||||
spawner_model,
|
||||
spawner_record_event_trace,
|
||||
scope_handle,
|
||||
));
|
||||
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 manifest::{
|
||||
ModelManifest, Permission, PodManifestConfig, PodMetaConfig, ScopeConfig, ScopeRule,
|
||||
SharedScope, WorkerManifestConfig,
|
||||
SessionConfigPartial, SharedScope, WorkerManifestConfig,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use tokio::net::UnixStream;
|
||||
|
|
@ -119,6 +119,9 @@ pub struct SpawnPodTool {
|
|||
/// configuration. Per-spawn override is
|
||||
/// out of scope here (see `tickets/spawn-inherit-provider.md`).
|
||||
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
|
||||
/// `Permission::Write` rules in the delegated scope are revoked
|
||||
/// from the spawner's in-memory view (a `deny(Write, target)` is
|
||||
|
|
@ -138,6 +141,7 @@ impl SpawnPodTool {
|
|||
registry: Arc<SpawnedPodRegistry>,
|
||||
parent_socket: Option<PathBuf>,
|
||||
spawner_model: ModelManifest,
|
||||
spawner_record_event_trace: bool,
|
||||
spawner_scope: SharedScope,
|
||||
) -> Self {
|
||||
Self {
|
||||
|
|
@ -148,6 +152,7 @@ impl SpawnPodTool {
|
|||
registry,
|
||||
parent_socket,
|
||||
spawner_model,
|
||||
spawner_record_event_trace,
|
||||
spawner_scope,
|
||||
}
|
||||
}
|
||||
|
|
@ -207,6 +212,7 @@ impl Tool for SpawnPodTool {
|
|||
&instruction,
|
||||
&scope_allow,
|
||||
&self.spawner_model,
|
||||
self.spawner_record_event_trace,
|
||||
) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
|
|
@ -384,6 +390,7 @@ fn build_spawn_config_json(
|
|||
instruction: &str,
|
||||
scope_allow: &[ScopeRule],
|
||||
model: &ModelManifest,
|
||||
record_event_trace: bool,
|
||||
) -> Result<String, serde_json::Error> {
|
||||
let config = PodManifestConfig {
|
||||
pod: PodMetaConfig {
|
||||
|
|
@ -399,6 +406,9 @@ fn build_spawn_config_json(
|
|||
allow: scope_allow.to_vec(),
|
||||
deny: Vec::new(),
|
||||
},
|
||||
session: record_event_trace.then_some(SessionConfigPartial {
|
||||
record_event_trace: Some(true),
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
serde_json::to_string(&config)
|
||||
|
|
@ -484,6 +494,7 @@ pub fn spawn_pod_tool(
|
|||
registry: Arc<SpawnedPodRegistry>,
|
||||
parent_socket: Option<PathBuf>,
|
||||
spawner_model: ModelManifest,
|
||||
spawner_record_event_trace: bool,
|
||||
spawner_scope: SharedScope,
|
||||
) -> ToolDefinition {
|
||||
Arc::new(move || {
|
||||
|
|
@ -500,6 +511,7 @@ pub fn spawn_pod_tool(
|
|||
registry.clone(),
|
||||
parent_socket.clone(),
|
||||
spawner_model.clone(),
|
||||
spawner_record_event_trace,
|
||||
spawner_scope.clone(),
|
||||
));
|
||||
(meta, tool)
|
||||
|
|
@ -509,7 +521,7 @@ pub fn spawn_pod_tool(
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use manifest::{AuthRef, SchemeKind};
|
||||
use manifest::{AuthRef, PodManifest, SchemeKind};
|
||||
|
||||
#[test]
|
||||
fn spawn_config_inherits_inline_spawner_model() {
|
||||
|
|
@ -525,7 +537,7 @@ mod tests {
|
|||
};
|
||||
|
||||
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();
|
||||
|
||||
assert_eq!(parsed.model.scheme, Some(SchemeKind::Anthropic));
|
||||
|
|
@ -548,11 +560,51 @@ mod tests {
|
|||
..Default::default()
|
||||
};
|
||||
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();
|
||||
assert_eq!(
|
||||
parsed.model.ref_.as_deref(),
|
||||
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,
|
||||
None,
|
||||
dummy_model(),
|
||||
false,
|
||||
spawner_scope.clone(),
|
||||
);
|
||||
let (_meta, tool) = def();
|
||||
|
|
@ -280,6 +281,7 @@ async fn spawn_pod_rejects_scope_outside_spawner() {
|
|||
registry,
|
||||
None,
|
||||
dummy_model(),
|
||||
false,
|
||||
spawner_scope.clone(),
|
||||
);
|
||||
let (_meta, tool) = def();
|
||||
|
|
@ -351,6 +353,7 @@ async fn spawn_pod_rolls_back_reservation_when_socket_never_appears() {
|
|||
registry,
|
||||
None,
|
||||
dummy_model(),
|
||||
false,
|
||||
spawner_scope.clone(),
|
||||
);
|
||||
let (_meta, tool) = def();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
//! Debug-only LLM request/stream trace recording.
|
||||
//!
|
||||
//! [`TraceEntry`] captures stream lifecycle markers and raw provider stream
|
||||
//! events for debugging stalls. Written to a separate `.trace.jsonl` file,
|
||||
//! [`TraceEntry`] captures stream lifecycle markers and normalized provider
|
||||
//! 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.
|
||||
//!
|
||||
//! 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_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)]
|
||||
pub struct TraceEntry {
|
||||
/// Timestamp in milliseconds since Unix epoch.
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user