mirror of
https://github.com/openai/codex.git
synced 2026-11-13 06:45:07 +00:00
Consolidate Guardian extensions into codex-guardian-v2 (#39474)
## What changed - Move the Guardian thread lifecycle contributor and subagent-spawn context into `codex-guardian-v2`. - Install the lifecycle contributor and asynchronous risk scorer through a single extension entry point. - Remove the redundant `codex-guardian` crate and group the existing scorer implementation under `async_scorer`. GitOrigin-RevId: 97141190c843e588b8c09c9ccd3fe00e1fae39a4
This commit is contained in:
1 parent
f5a3dc5540
commit
e741cd9ace
19 files changed
+134
-158
No files matched your search
Generated
-10
@@ -2014,7 +2014,6 @@ dependencies = [
|
|||||||
"codex-git-attribution",
|
"codex-git-attribution",
|
||||||
"codex-git-utils",
|
"codex-git-utils",
|
||||||
"codex-goal-extension",
|
"codex-goal-extension",
|
||||||
"codex-guardian",
|
|
||||||
"codex-guardian-v2",
|
"codex-guardian-v2",
|
||||||
"codex-home",
|
"codex-home",
|
||||||
"codex-hooks",
|
"codex-hooks",
|
||||||
@@ -3294,15 +3293,6 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "codex-guardian"
|
|
||||||
version = "0.0.0"
|
|
||||||
dependencies = [
|
|
||||||
"codex-core",
|
|
||||||
"codex-extension-api",
|
|
||||||
"codex-protocol",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "codex-guardian-v2"
|
name = "codex-guardian-v2"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ members = [
|
|||||||
"ext/extension-api",
|
"ext/extension-api",
|
||||||
"ext/goal",
|
"ext/goal",
|
||||||
"ext/git-attribution",
|
"ext/git-attribution",
|
||||||
"ext/guardian",
|
|
||||||
"ext/guardian-v2",
|
"ext/guardian-v2",
|
||||||
"ext/image-generation",
|
"ext/image-generation",
|
||||||
"ext/items",
|
"ext/items",
|
||||||
@@ -197,7 +196,6 @@ codex-extension-api = { path = "ext/extension-api" }
|
|||||||
codex-extension-items = { path = "ext/items" }
|
codex-extension-items = { path = "ext/items" }
|
||||||
codex-goal-extension = { path = "ext/goal" }
|
codex-goal-extension = { path = "ext/goal" }
|
||||||
codex-git-attribution = { path = "ext/git-attribution" }
|
codex-git-attribution = { path = "ext/git-attribution" }
|
||||||
codex-guardian = { path = "ext/guardian" }
|
|
||||||
codex-guardian-v2 = { path = "ext/guardian-v2" }
|
codex-guardian-v2 = { path = "ext/guardian-v2" }
|
||||||
codex-image-generation-extension = { path = "ext/image-generation" }
|
codex-image-generation-extension = { path = "ext/image-generation" }
|
||||||
codex-external-agent-migration = { path = "external-agent-migration" }
|
codex-external-agent-migration = { path = "external-agent-migration" }
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ codex-external-agent-migration = { workspace = true }
|
|||||||
codex-features = { workspace = true }
|
codex-features = { workspace = true }
|
||||||
codex-goal-extension = { workspace = true }
|
codex-goal-extension = { workspace = true }
|
||||||
codex-git-attribution = { workspace = true }
|
codex-git-attribution = { workspace = true }
|
||||||
codex-guardian = { workspace = true }
|
|
||||||
codex-guardian-v2 = { workspace = true }
|
codex-guardian-v2 = { workspace = true }
|
||||||
codex-git-utils = { workspace = true }
|
codex-git-utils = { workspace = true }
|
||||||
codex-file-watcher = { workspace = true }
|
codex-file-watcher = { workspace = true }
|
||||||
|
|||||||
@@ -94,8 +94,12 @@ where
|
|||||||
git_attribution_base_url,
|
git_attribution_base_url,
|
||||||
http_client_factory,
|
http_client_factory,
|
||||||
);
|
);
|
||||||
codex_guardian::install(&mut builder, guardian_agent_spawner);
|
codex_guardian_v2::install(
|
||||||
codex_guardian_v2::install(&mut builder, auth_manager.clone(), thread_manager);
|
&mut builder,
|
||||||
|
guardian_agent_spawner,
|
||||||
|
auth_manager.clone(),
|
||||||
|
thread_manager,
|
||||||
|
);
|
||||||
codex_memories_extension::install(&mut builder, codex_otel::global());
|
codex_memories_extension::install(&mut builder, codex_otel::global());
|
||||||
codex_mcp_extension::install(&mut builder);
|
codex_mcp_extension::install(&mut builder);
|
||||||
codex_mcp_extension::install_executor_plugins(&mut builder, environment_manager);
|
codex_mcp_extension::install_executor_plugins(&mut builder, environment_manager);
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ load("//:defs.bzl", "codex_rust_crate")
|
|||||||
|
|
||||||
codex_rust_crate(
|
codex_rust_crate(
|
||||||
name = "guardian-v2",
|
name = "guardian-v2",
|
||||||
compile_data = ["src/classifier_instructions.md"],
|
compile_data = ["src/async_scorer/classifier_instructions.md"],
|
||||||
crate_name = "codex_guardian_v2",
|
crate_name = "codex_guardian_v2",
|
||||||
)
|
)
|
||||||
File renamed without changes.
+8
-8
@@ -5,14 +5,14 @@ use codex_features::GuardianV2TranscriptConfigToml;
|
|||||||
use codex_protocol::openai_models::GuardianV2ModelConfig;
|
use codex_protocol::openai_models::GuardianV2ModelConfig;
|
||||||
use codex_protocol::openai_models::ReasoningEffort;
|
use codex_protocol::openai_models::ReasoningEffort;
|
||||||
|
|
||||||
use crate::transcript::MAX_MESSAGE_ENTRY_TOKENS;
|
use super::transcript::MAX_MESSAGE_ENTRY_TOKENS;
|
||||||
use crate::transcript::MAX_MESSAGE_TRANSCRIPT_TOKENS;
|
use super::transcript::MAX_MESSAGE_TRANSCRIPT_TOKENS;
|
||||||
use crate::transcript::MAX_RECENT_NON_USER_ENTRIES;
|
use super::transcript::MAX_RECENT_NON_USER_ENTRIES;
|
||||||
use crate::transcript::MAX_TOOL_ENTRY_TOKENS;
|
use super::transcript::MAX_TOOL_ENTRY_TOKENS;
|
||||||
use crate::transcript::MAX_TOOL_TRANSCRIPT_TOKENS;
|
use super::transcript::MAX_TOOL_TRANSCRIPT_TOKENS;
|
||||||
use crate::transcript::TranscriptConfig;
|
use super::transcript::TranscriptConfig;
|
||||||
use crate::transcript::TranscriptSource;
|
use super::transcript::TranscriptSource;
|
||||||
use crate::transcript::truncate_entry;
|
use super::transcript::truncate_entry;
|
||||||
|
|
||||||
pub(crate) const DEFAULT_MODEL_CONTEXT_ITEM_TOKENS: usize = 10_000;
|
pub(crate) const DEFAULT_MODEL_CONTEXT_ITEM_TOKENS: usize = 10_000;
|
||||||
pub(crate) const DEFAULT_PARENT_COMPACTION_TOKENS: usize = 25_000;
|
pub(crate) const DEFAULT_PARENT_COMPACTION_TOKENS: usize = 25_000;
|
||||||
+1
-1
@@ -6,7 +6,7 @@ use pretty_assertions::assert_eq;
|
|||||||
|
|
||||||
use super::DEFAULT_CLASSIFIER_INSTRUCTIONS;
|
use super::DEFAULT_CLASSIFIER_INSTRUCTIONS;
|
||||||
use super::GuardianV2Config;
|
use super::GuardianV2Config;
|
||||||
use crate::transcript::truncate_entry;
|
use crate::async_scorer::transcript::truncate_entry;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn template_policy_is_substituted_before_the_single_truncation() {
|
fn template_policy_is_substituted_before_the_single_truncation() {
|
||||||
+7
-7
@@ -37,12 +37,12 @@ use codex_protocol::protocol::TruncationPolicy;
|
|||||||
use codex_protocol::security_risk::SecurityRiskScore;
|
use codex_protocol::security_risk::SecurityRiskScore;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
use crate::LunaSampler;
|
use super::config::GuardianV2Config;
|
||||||
use crate::LunaSamplerConfig;
|
use super::sampler::LunaSampler;
|
||||||
use crate::LunaSamplerError;
|
use super::sampler::LunaSamplerConfig;
|
||||||
use crate::LunaSamplingRequest;
|
use super::sampler::LunaSamplerError;
|
||||||
use crate::config::GuardianV2Config;
|
use super::sampler::LunaSamplingRequest;
|
||||||
use crate::sampler::MODEL;
|
use super::sampler::MODEL;
|
||||||
|
|
||||||
struct GuardianAction {
|
struct GuardianAction {
|
||||||
tool_name: ToolName,
|
tool_name: ToolName,
|
||||||
@@ -165,7 +165,7 @@ fn render_action_with_limit(
|
|||||||
fn truncate_action_value(value: &mut serde_json::Value, max_tokens: usize) {
|
fn truncate_action_value(value: &mut serde_json::Value, max_tokens: usize) {
|
||||||
match value {
|
match value {
|
||||||
serde_json::Value::String(text) => {
|
serde_json::Value::String(text) => {
|
||||||
let truncated = crate::transcript::truncate_entry(text, max_tokens);
|
let truncated = super::transcript::truncate_entry(text, max_tokens);
|
||||||
if truncated.len() < text.len() {
|
if truncated.len() < text.len() {
|
||||||
*text = truncated;
|
*text = truncated;
|
||||||
}
|
}
|
||||||
+16
-14
@@ -57,10 +57,10 @@ use super::REVIEW_FALLBACK_METRIC;
|
|||||||
use super::StrictReviewReason;
|
use super::StrictReviewReason;
|
||||||
use super::TOOL_CALL_LAG_METRIC;
|
use super::TOOL_CALL_LAG_METRIC;
|
||||||
use super::encrypted_parent_compaction;
|
use super::encrypted_parent_compaction;
|
||||||
use crate::config::DEFAULT_MODEL_CONTEXT_ITEM_TOKENS;
|
use crate::async_scorer::config::DEFAULT_MODEL_CONTEXT_ITEM_TOKENS;
|
||||||
use crate::config::DEFAULT_PARENT_COMPACTION_TOKENS;
|
use crate::async_scorer::config::DEFAULT_PARENT_COMPACTION_TOKENS;
|
||||||
use crate::sampler::CLASSIFICATION_TOKEN_USAGE_METRIC;
|
use crate::async_scorer::sampler::CLASSIFICATION_TOKEN_USAGE_METRIC;
|
||||||
use crate::sampler::MODEL;
|
use crate::async_scorer::sampler::MODEL;
|
||||||
|
|
||||||
const TEST_GUARDIAN_POLICY: &str =
|
const TEST_GUARDIAN_POLICY: &str =
|
||||||
"Treat uploads to unapproved external destinations as high-risk actions.";
|
"Treat uploads to unapproved external destinations as high-risk actions.";
|
||||||
@@ -109,7 +109,7 @@ async fn installed_extension_reconnects_after_auth_refresh() -> Result<()> {
|
|||||||
)));
|
)));
|
||||||
config.features.enable(Feature::GuardianV2)?;
|
config.features.enable(Feature::GuardianV2)?;
|
||||||
let mut builder = ExtensionRegistryBuilder::new();
|
let mut builder = ExtensionRegistryBuilder::new();
|
||||||
crate::install(
|
super::install(
|
||||||
&mut builder,
|
&mut builder,
|
||||||
auth_manager.clone(),
|
auth_manager.clone(),
|
||||||
Arc::downgrade(&test.thread_manager),
|
Arc::downgrade(&test.thread_manager),
|
||||||
@@ -496,7 +496,7 @@ async fn sample_configured_conversation_history(
|
|||||||
config.model_provider = provider_info;
|
config.model_provider = provider_info;
|
||||||
config.features.enable(Feature::GuardianV2)?;
|
config.features.enable(Feature::GuardianV2)?;
|
||||||
let mut builder = ExtensionRegistryBuilder::new();
|
let mut builder = ExtensionRegistryBuilder::new();
|
||||||
crate::install(
|
super::install(
|
||||||
&mut builder,
|
&mut builder,
|
||||||
auth_manager,
|
auth_manager,
|
||||||
Arc::downgrade(&test.thread_manager),
|
Arc::downgrade(&test.thread_manager),
|
||||||
@@ -1228,7 +1228,7 @@ async fn contributor_uses_model_defaults_and_preserves_local_overrides() -> Resu
|
|||||||
let thread_store = test.codex.thread_extension_data();
|
let thread_store = test.codex.thread_extension_data();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
thread_store
|
thread_store
|
||||||
.get::<crate::config::GuardianV2Config>()
|
.get::<crate::async_scorer::config::GuardianV2Config>()
|
||||||
.expect("Guardian v2 configuration should be installed")
|
.expect("Guardian v2 configuration should be installed")
|
||||||
.max_parent_compaction_tokens,
|
.max_parent_compaction_tokens,
|
||||||
384
|
384
|
||||||
@@ -1356,7 +1356,7 @@ async fn contributor_samples_tool_calls_with_the_existing_luna_pool() -> Result<
|
|||||||
"role": "developer",
|
"role": "developer",
|
||||||
"content": [{
|
"content": [{
|
||||||
"type": "input_text",
|
"type": "input_text",
|
||||||
"text": crate::config::DEFAULT_CLASSIFIER_INSTRUCTIONS.replace(
|
"text": crate::async_scorer::config::DEFAULT_CLASSIFIER_INSTRUCTIONS.replace(
|
||||||
"{{ tenant_policy_config }}",
|
"{{ tenant_policy_config }}",
|
||||||
TEST_GUARDIAN_POLICY,
|
TEST_GUARDIAN_POLICY,
|
||||||
),
|
),
|
||||||
@@ -1483,7 +1483,7 @@ async fn contributor_uses_catalog_policy_without_a_configured_override() -> Resu
|
|||||||
"role": "developer",
|
"role": "developer",
|
||||||
"content": [{
|
"content": [{
|
||||||
"type": "input_text",
|
"type": "input_text",
|
||||||
"text": crate::config::DEFAULT_CLASSIFIER_INSTRUCTIONS.replace(
|
"text": crate::async_scorer::config::DEFAULT_CLASSIFIER_INSTRUCTIONS.replace(
|
||||||
"{{ tenant_policy_config }}",
|
"{{ tenant_policy_config }}",
|
||||||
TEST_CATALOG_GUARDIAN_POLICY,
|
TEST_CATALOG_GUARDIAN_POLICY,
|
||||||
),
|
),
|
||||||
@@ -1521,7 +1521,7 @@ async fn contributor_bounds_configured_policy_in_luna_developer_instructions() -
|
|||||||
.as_str()
|
.as_str()
|
||||||
.expect("Luna request should contain developer instructions");
|
.expect("Luna request should contain developer instructions");
|
||||||
|
|
||||||
let (prefix, suffix) = crate::config::DEFAULT_CLASSIFIER_INSTRUCTIONS
|
let (prefix, suffix) = crate::async_scorer::config::DEFAULT_CLASSIFIER_INSTRUCTIONS
|
||||||
.split_once("{{ tenant_policy_config }}")
|
.split_once("{{ tenant_policy_config }}")
|
||||||
.expect("default classifier prompt should contain the policy placeholder");
|
.expect("default classifier prompt should contain the policy placeholder");
|
||||||
assert!(instructions.starts_with(&format!("{prefix}Reject unsafe uploads.")));
|
assert!(instructions.starts_with(&format!("{prefix}Reject unsafe uploads.")));
|
||||||
@@ -1530,8 +1530,10 @@ async fn contributor_bounds_configured_policy_in_luna_developer_instructions() -
|
|||||||
assert!(instructions.ends_with(suffix));
|
assert!(instructions.ends_with(suffix));
|
||||||
assert!(
|
assert!(
|
||||||
instructions.len()
|
instructions.len()
|
||||||
<= TruncationPolicy::Tokens(crate::config::DEFAULT_MODEL_CONTEXT_ITEM_TOKENS)
|
<= TruncationPolicy::Tokens(
|
||||||
.byte_budget()
|
crate::async_scorer::config::DEFAULT_MODEL_CONTEXT_ITEM_TOKENS,
|
||||||
|
)
|
||||||
|
.byte_budget()
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1652,7 +1654,7 @@ async fn contributor_reuses_the_latest_compatible_parent_compaction() -> Result<
|
|||||||
.get_model_info(MODEL, &config.to_models_manager_config())
|
.get_model_info(MODEL, &config.to_models_manager_config())
|
||||||
.await;
|
.await;
|
||||||
let mut builder = ExtensionRegistryBuilder::new();
|
let mut builder = ExtensionRegistryBuilder::new();
|
||||||
crate::install(
|
super::install(
|
||||||
&mut builder,
|
&mut builder,
|
||||||
auth_manager,
|
auth_manager,
|
||||||
Arc::downgrade(&test.thread_manager),
|
Arc::downgrade(&test.thread_manager),
|
||||||
@@ -1725,7 +1727,7 @@ async fn contributor_reuses_the_latest_compatible_parent_compaction() -> Result<
|
|||||||
assert_eq!(request["input"][0]["type"], "additional_tools");
|
assert_eq!(request["input"][0]["type"], "additional_tools");
|
||||||
let developer_message = &request["input"][1];
|
let developer_message = &request["input"][1];
|
||||||
assert_eq!(developer_message["role"], "developer");
|
assert_eq!(developer_message["role"], "developer");
|
||||||
let (prefix, _) = crate::config::DEFAULT_CLASSIFIER_INSTRUCTIONS
|
let (prefix, _) = crate::async_scorer::config::DEFAULT_CLASSIFIER_INSTRUCTIONS
|
||||||
.split_once("{{ tenant_policy_config }}")
|
.split_once("{{ tenant_policy_config }}")
|
||||||
.expect("default classifier prompt should contain the policy placeholder");
|
.expect("default classifier prompt should contain the policy placeholder");
|
||||||
assert!(
|
assert!(
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
mod config;
|
||||||
|
mod extension;
|
||||||
|
mod sampler;
|
||||||
|
mod transcript;
|
||||||
|
|
||||||
|
pub use extension::StrictReviewReason;
|
||||||
|
pub(crate) use extension::install;
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,11 +1,89 @@
|
|||||||
mod config;
|
use std::sync::Arc;
|
||||||
mod extension;
|
use std::sync::Weak;
|
||||||
mod sampler;
|
|
||||||
mod transcript;
|
|
||||||
|
|
||||||
pub use extension::StrictReviewReason;
|
use codex_core::ThreadManager;
|
||||||
pub use extension::install;
|
use codex_core::config::Config;
|
||||||
pub use sampler::LunaSampler;
|
use codex_extension_api::AgentSpawnFuture;
|
||||||
pub use sampler::LunaSamplerConfig;
|
use codex_extension_api::AgentSpawner;
|
||||||
pub use sampler::LunaSamplerError;
|
use codex_extension_api::ExtensionFuture;
|
||||||
pub use sampler::LunaSamplingRequest;
|
use codex_extension_api::ExtensionRegistryBuilder;
|
||||||
|
use codex_extension_api::ThreadLifecycleContributor;
|
||||||
|
use codex_extension_api::ThreadStartInput;
|
||||||
|
use codex_login::AuthManager;
|
||||||
|
use codex_protocol::ThreadId;
|
||||||
|
|
||||||
|
mod async_scorer;
|
||||||
|
|
||||||
|
pub use async_scorer::StrictReviewReason;
|
||||||
|
|
||||||
|
/// Guardian extension dependencies supplied by the host at construction time.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct GuardianExtension<S> {
|
||||||
|
agent_spawner: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> GuardianExtension<S> {
|
||||||
|
/// Creates a guardian extension with its host-provided agent spawn helper.
|
||||||
|
pub fn new(agent_spawner: S) -> Self {
|
||||||
|
Self { agent_spawner }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delegates one guardian-owned subagent spawn request to the host helper.
|
||||||
|
pub fn spawn_subagent<'a, R>(
|
||||||
|
&'a self,
|
||||||
|
forked_from_thread_id: ThreadId,
|
||||||
|
request: R,
|
||||||
|
) -> AgentSpawnFuture<'a, <S as AgentSpawner<R>>::Spawned, <S as AgentSpawner<R>>::Error>
|
||||||
|
where
|
||||||
|
S: AgentSpawner<R>,
|
||||||
|
{
|
||||||
|
self.agent_spawner
|
||||||
|
.spawn_subagent(forked_from_thread_id, request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Thread-local guardian state captured when the host starts a thread.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct GuardianThreadContext {
|
||||||
|
forked_from_thread_id: ThreadId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GuardianThreadContext {
|
||||||
|
/// Returns the thread that future guardian subagents should fork from by default.
|
||||||
|
pub fn forked_from_thread_id(&self) -> ThreadId {
|
||||||
|
self.forked_from_thread_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> ThreadLifecycleContributor<Config> for GuardianExtension<S>
|
||||||
|
where
|
||||||
|
S: Send + Sync,
|
||||||
|
{
|
||||||
|
fn on_thread_start<'a>(
|
||||||
|
&'a self,
|
||||||
|
input: ThreadStartInput<'a, Config>,
|
||||||
|
) -> ExtensionFuture<'a, ()> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let Ok(forked_from_thread_id) = ThreadId::from_string(input.thread_store.level_id())
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
input.thread_store.insert(GuardianThreadContext {
|
||||||
|
forked_from_thread_id,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Installs the guardian contributors into the extension registry.
|
||||||
|
pub fn install<S>(
|
||||||
|
registry: &mut ExtensionRegistryBuilder<Config>,
|
||||||
|
agent_spawner: S,
|
||||||
|
auth_manager: Arc<AuthManager>,
|
||||||
|
thread_manager: Weak<ThreadManager>,
|
||||||
|
) where
|
||||||
|
S: Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
registry.thread_lifecycle_contributor(Arc::new(GuardianExtension::new(agent_spawner)));
|
||||||
|
async_scorer::install(registry, auth_manager, thread_manager);
|
||||||
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
load("//:defs.bzl", "codex_rust_crate")
|
|
||||||
|
|
||||||
codex_rust_crate(
|
|
||||||
name = "guardian",
|
|
||||||
crate_name = "codex_guardian",
|
|
||||||
)
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
[package]
|
|
||||||
edition.workspace = true
|
|
||||||
license.workspace = true
|
|
||||||
name = "codex-guardian"
|
|
||||||
version.workspace = true
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
name = "codex_guardian"
|
|
||||||
path = "src/lib.rs"
|
|
||||||
test = false
|
|
||||||
doctest = false
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
codex-core = { workspace = true }
|
|
||||||
codex-extension-api = { workspace = true }
|
|
||||||
codex-protocol = { workspace = true }
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use codex_core::config::Config;
|
|
||||||
use codex_extension_api::AgentSpawnFuture;
|
|
||||||
use codex_extension_api::AgentSpawner;
|
|
||||||
use codex_extension_api::ExtensionFuture;
|
|
||||||
use codex_extension_api::ExtensionRegistryBuilder;
|
|
||||||
use codex_extension_api::ThreadLifecycleContributor;
|
|
||||||
use codex_extension_api::ThreadStartInput;
|
|
||||||
use codex_protocol::ThreadId;
|
|
||||||
|
|
||||||
/// Guardian extension dependencies supplied by the host at construction time.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct GuardianExtension<S> {
|
|
||||||
agent_spawner: S,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> GuardianExtension<S> {
|
|
||||||
/// Creates a guardian extension with its host-provided agent spawn helper.
|
|
||||||
pub fn new(agent_spawner: S) -> Self {
|
|
||||||
Self { agent_spawner }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Delegates one guardian-owned subagent spawn request to the host helper.
|
|
||||||
pub fn spawn_subagent<'a, R>(
|
|
||||||
&'a self,
|
|
||||||
forked_from_thread_id: ThreadId,
|
|
||||||
request: R,
|
|
||||||
) -> AgentSpawnFuture<'a, <S as AgentSpawner<R>>::Spawned, <S as AgentSpawner<R>>::Error>
|
|
||||||
where
|
|
||||||
S: AgentSpawner<R>,
|
|
||||||
{
|
|
||||||
self.agent_spawner
|
|
||||||
.spawn_subagent(forked_from_thread_id, request)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Thread-local guardian state captured when the host starts a thread.
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub struct GuardianThreadContext {
|
|
||||||
forked_from_thread_id: ThreadId,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GuardianThreadContext {
|
|
||||||
/// Returns the thread that future guardian subagents should fork from by default.
|
|
||||||
pub fn forked_from_thread_id(&self) -> ThreadId {
|
|
||||||
self.forked_from_thread_id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> ThreadLifecycleContributor<Config> for GuardianExtension<S>
|
|
||||||
where
|
|
||||||
S: Send + Sync,
|
|
||||||
{
|
|
||||||
fn on_thread_start<'a>(
|
|
||||||
&'a self,
|
|
||||||
input: ThreadStartInput<'a, Config>,
|
|
||||||
) -> ExtensionFuture<'a, ()> {
|
|
||||||
Box::pin(async move {
|
|
||||||
let Ok(forked_from_thread_id) = ThreadId::from_string(input.thread_store.level_id())
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
input.thread_store.insert(GuardianThreadContext {
|
|
||||||
forked_from_thread_id,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Installs the guardian contributors into the extension registry.
|
|
||||||
pub fn install<S>(registry: &mut ExtensionRegistryBuilder<Config>, agent_spawner: S)
|
|
||||||
where
|
|
||||||
S: Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
registry.thread_lifecycle_contributor(Arc::new(GuardianExtension::new(agent_spawner)));
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user