worker: separate subworkers from workspace workers
This commit is contained in:
@@ -83,7 +83,9 @@ pub struct FeatureConfigPartial {
|
||||
#[serde(default)]
|
||||
pub web: Option<FeatureFlagConfigPartial>,
|
||||
#[serde(default)]
|
||||
pub workers: Option<FeatureFlagConfigPartial>,
|
||||
pub sub_worker: Option<FeatureFlagConfigPartial>,
|
||||
#[serde(default)]
|
||||
pub worker: Option<FeatureFlagConfigPartial>,
|
||||
#[serde(default)]
|
||||
pub objective: Option<FeatureFlagConfigPartial>,
|
||||
#[serde(default)]
|
||||
@@ -100,7 +102,12 @@ impl FeatureConfigPartial {
|
||||
task: merge_option(self.task, other.task, FeatureFlagConfigPartial::merge),
|
||||
memory: merge_option(self.memory, other.memory, MemoryFeatureConfigPartial::merge),
|
||||
web: merge_option(self.web, other.web, FeatureFlagConfigPartial::merge),
|
||||
workers: merge_option(self.workers, other.workers, FeatureFlagConfigPartial::merge),
|
||||
sub_worker: merge_option(
|
||||
self.sub_worker,
|
||||
other.sub_worker,
|
||||
FeatureFlagConfigPartial::merge,
|
||||
),
|
||||
worker: merge_option(self.worker, other.worker, FeatureFlagConfigPartial::merge),
|
||||
objective: merge_option(
|
||||
self.objective,
|
||||
other.objective,
|
||||
@@ -179,8 +186,12 @@ impl From<FeatureConfigPartial> for FeatureConfig {
|
||||
.map(MemoryFeatureConfig::from)
|
||||
.unwrap_or_default(),
|
||||
web: value.web.map(FeatureFlagConfig::from).unwrap_or_default(),
|
||||
workers: value
|
||||
.workers
|
||||
sub_worker: value
|
||||
.sub_worker
|
||||
.map(FeatureFlagConfig::from)
|
||||
.unwrap_or_default(),
|
||||
worker: value
|
||||
.worker
|
||||
.map(FeatureFlagConfig::from)
|
||||
.unwrap_or_default(),
|
||||
objective: value
|
||||
@@ -267,7 +278,8 @@ impl From<FeatureConfig> for FeatureConfigPartial {
|
||||
task: Some(value.task.into()),
|
||||
memory: Some(value.memory.into()),
|
||||
web: Some(value.web.into()),
|
||||
workers: Some(value.workers.into()),
|
||||
sub_worker: Some(value.sub_worker.into()),
|
||||
worker: Some(value.worker.into()),
|
||||
objective: Some(value.objective.into()),
|
||||
manage_workdir: Some(value.manage_workdir.into()),
|
||||
ticket: Some(value.ticket.into()),
|
||||
@@ -417,6 +429,15 @@ pub(crate) fn reject_removed_manifest_fields(s: &str) -> Result<(), toml::de::Er
|
||||
"unknown field in manifest: memory.extract_worker_max_input_tokens (removed)",
|
||||
));
|
||||
}
|
||||
if value
|
||||
.get("feature")
|
||||
.and_then(toml::Value::as_table)
|
||||
.is_some_and(|table| table.contains_key("workers"))
|
||||
{
|
||||
return Err(toml::de::Error::custom(
|
||||
"unknown field in manifest: feature.workers (removed; use feature.sub_worker)",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -424,8 +445,8 @@ impl WorkerManifestConfig {
|
||||
/// Parse a partial manifest from a TOML string. Unknown top-level or
|
||||
/// nested fields emit a `tracing::warn!` and are ignored; use
|
||||
/// `tracing_subscriber` with `WARN` enabled to surface them to the
|
||||
/// operator. Removed fields that must not be silently ignored (currently
|
||||
/// `compaction.prune_protected_turns`) are rejected before deserialization.
|
||||
/// operator. Removed fields with an explicit replacement (including
|
||||
/// `feature.workers`) are rejected before deserialization.
|
||||
pub fn from_toml(s: &str) -> Result<Self, toml::de::Error> {
|
||||
reject_removed_manifest_fields(s)?;
|
||||
let de = toml::Deserializer::parse(s)?;
|
||||
@@ -1814,7 +1835,7 @@ worker_max_turns = 7
|
||||
assert!(!manifest.feature.task.enabled);
|
||||
assert!(!manifest.feature.memory.enabled);
|
||||
assert!(!manifest.feature.web.enabled);
|
||||
assert!(!manifest.feature.workers.enabled);
|
||||
assert!(!manifest.feature.sub_worker.enabled);
|
||||
assert!(!manifest.feature.objective.enabled);
|
||||
assert!(!manifest.feature.manage_workdir.enabled);
|
||||
assert!(!manifest.feature.ticket.enabled);
|
||||
@@ -1949,7 +1970,7 @@ enabled = true
|
||||
assert!(manifest.feature.ticket.orchestration_control);
|
||||
assert!(manifest.feature.objective.enabled);
|
||||
assert!(manifest.feature.web.enabled);
|
||||
assert!(!manifest.feature.workers.enabled);
|
||||
assert!(!manifest.feature.sub_worker.enabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -111,7 +111,9 @@ pub struct FeatureConfig {
|
||||
#[serde(default)]
|
||||
pub web: FeatureFlagConfig,
|
||||
#[serde(default)]
|
||||
pub workers: FeatureFlagConfig,
|
||||
pub sub_worker: FeatureFlagConfig,
|
||||
#[serde(default)]
|
||||
pub worker: FeatureFlagConfig,
|
||||
#[serde(default)]
|
||||
pub objective: FeatureFlagConfig,
|
||||
#[serde(default)]
|
||||
@@ -128,7 +130,8 @@ impl Default for FeatureConfig {
|
||||
task: FeatureFlagConfig::disabled(),
|
||||
memory: MemoryFeatureConfig::disabled(),
|
||||
web: FeatureFlagConfig::disabled(),
|
||||
workers: FeatureFlagConfig::disabled(),
|
||||
sub_worker: FeatureFlagConfig::disabled(),
|
||||
worker: FeatureFlagConfig::disabled(),
|
||||
objective: FeatureFlagConfig::disabled(),
|
||||
manage_workdir: FeatureFlagConfig::disabled(),
|
||||
ticket: TicketFeatureConfig::default(),
|
||||
@@ -405,7 +408,7 @@ pub struct MemoryConfig {
|
||||
/// system-prompt section. `None` ⇒ enabled.
|
||||
#[serde(default)]
|
||||
pub inject_summary: Option<bool>,
|
||||
/// Language used by memory extraction / consolidation workers for durable
|
||||
/// Language used by memory extraction / consolidation sub_worker for durable
|
||||
/// memory text. Free-form so workspaces can use names like
|
||||
/// `English`, `Japanese`, or locale tags. `None` ⇒
|
||||
/// [`defaults::MEMORY_LANGUAGE`].
|
||||
|
||||
@@ -436,7 +436,7 @@ impl ProfileResolver {
|
||||
}
|
||||
}
|
||||
/// Resolve a registry/default selector against an already-discovered
|
||||
/// registry. Callers such as SpawnWorker use this to bind discovery to the
|
||||
/// registry. Callers such as SubWorkerSpawn use this to bind discovery to the
|
||||
/// Worker's cwd instead of the process current directory.
|
||||
pub fn resolve_from_registry(
|
||||
&self,
|
||||
@@ -936,7 +936,7 @@ fn builtin_profile_artifact(label: &str) -> Option<serde_json::Value> {
|
||||
value["feature"]["task"] = serde_json::json!({ "enabled": false });
|
||||
value["feature"]["memory"] = serde_json::json!({ "enabled": true, "staging": true });
|
||||
value["feature"]["web"] = serde_json::json!({ "enabled": false });
|
||||
value["feature"]["workers"] = serde_json::json!({ "enabled": false });
|
||||
value["feature"]["sub_worker"] = serde_json::json!({ "enabled": false });
|
||||
value["feature"]["objective"] = serde_json::json!({ "enabled": false });
|
||||
value["feature"]["ticket"] = serde_json::json!({ "enabled": false, "thread": false });
|
||||
Some(value)
|
||||
@@ -962,7 +962,8 @@ fn builtin_base_profile_artifact() -> serde_json::Value {
|
||||
"task": { "enabled": true },
|
||||
"memory": { "enabled": true },
|
||||
"web": { "enabled": true },
|
||||
"workers": { "enabled": true },
|
||||
"sub_worker": { "enabled": true },
|
||||
"worker": { "enabled": false },
|
||||
"objective": { "enabled": true },
|
||||
"ticket": { "enabled": true, "authoring": true, "thread": true }
|
||||
},
|
||||
@@ -990,14 +991,15 @@ fn apply_role_profile(
|
||||
task: bool,
|
||||
memory: bool,
|
||||
web: bool,
|
||||
workers: bool,
|
||||
sub_worker: bool,
|
||||
) {
|
||||
value["slug"] = serde_json::Value::String(slug.to_string());
|
||||
value["description"] = serde_json::Value::String(description.to_string());
|
||||
value["feature"]["task"] = serde_json::json!({ "enabled": task });
|
||||
value["feature"]["memory"] = serde_json::json!({ "enabled": memory });
|
||||
value["feature"]["web"] = serde_json::json!({ "enabled": web });
|
||||
value["feature"]["workers"] = serde_json::json!({ "enabled": workers });
|
||||
value["feature"]["sub_worker"] = serde_json::json!({ "enabled": sub_worker });
|
||||
value["feature"]["worker"] = serde_json::json!({ "enabled": slug == "orchestrator" });
|
||||
value["feature"]["manage_workdir"] = serde_json::json!({ "enabled": slug == "orchestrator" });
|
||||
let ticket = match slug {
|
||||
"companion" => serde_json::json!({ "enabled": true, "authoring": true, "thread": true }),
|
||||
@@ -1456,7 +1458,8 @@ mod tests {
|
||||
|
||||
let companion = resolve("companion");
|
||||
assert!(companion.feature.task.enabled);
|
||||
assert!(companion.feature.workers.enabled);
|
||||
assert!(companion.feature.sub_worker.enabled);
|
||||
assert!(!companion.feature.worker.enabled);
|
||||
assert!(companion.scope.allow.is_empty());
|
||||
assert!(companion.scope.deny.is_empty());
|
||||
assert!(companion.delegation_scope.allow.is_empty());
|
||||
@@ -1487,7 +1490,8 @@ mod tests {
|
||||
|
||||
let intake = resolve("intake");
|
||||
assert!(intake.feature.task.enabled);
|
||||
assert!(!intake.feature.workers.enabled);
|
||||
assert!(!intake.feature.sub_worker.enabled);
|
||||
assert!(!intake.feature.worker.enabled);
|
||||
assert!(intake.feature.ticket.enabled);
|
||||
assert!(intake.feature.ticket.enabled);
|
||||
assert!(intake.feature.ticket.authoring);
|
||||
@@ -1504,7 +1508,8 @@ mod tests {
|
||||
|
||||
let orchestrator = resolve("orchestrator");
|
||||
assert!(orchestrator.feature.task.enabled);
|
||||
assert!(!orchestrator.feature.workers.enabled);
|
||||
assert!(!orchestrator.feature.sub_worker.enabled);
|
||||
assert!(orchestrator.feature.worker.enabled);
|
||||
assert!(orchestrator.feature.ticket.enabled);
|
||||
assert!(orchestrator.feature.ticket.enabled);
|
||||
assert!(!orchestrator.feature.ticket.authoring);
|
||||
@@ -1524,7 +1529,8 @@ mod tests {
|
||||
|
||||
let coder = resolve("coder");
|
||||
assert!(coder.feature.task.enabled);
|
||||
assert!(!coder.feature.workers.enabled);
|
||||
assert!(!coder.feature.sub_worker.enabled);
|
||||
assert!(!coder.feature.worker.enabled);
|
||||
assert!(coder.scope.allow.is_empty());
|
||||
assert!(coder.delegation_scope.allow.is_empty());
|
||||
assert_eq!(coder.model.ref_.as_deref(), Some("codex-oauth/gpt-5.5"));
|
||||
@@ -1540,7 +1546,8 @@ mod tests {
|
||||
assert!(!coder.feature.ticket.orchestration_control);
|
||||
let reviewer = resolve("reviewer");
|
||||
assert!(reviewer.feature.task.enabled);
|
||||
assert!(!reviewer.feature.workers.enabled);
|
||||
assert!(!reviewer.feature.sub_worker.enabled);
|
||||
assert!(!reviewer.feature.worker.enabled);
|
||||
assert!(reviewer.feature.ticket.enabled);
|
||||
assert!(reviewer.feature.ticket.enabled);
|
||||
assert!(!reviewer.feature.ticket.authoring);
|
||||
@@ -1692,7 +1699,7 @@ enabled = false
|
||||
[feature.web]
|
||||
enabled = true
|
||||
|
||||
[feature.workers]
|
||||
[feature.sub_worker]
|
||||
enabled = true
|
||||
|
||||
[feature.ticket]
|
||||
@@ -1716,7 +1723,7 @@ orchestration_control = false
|
||||
assert!(resolved.manifest.feature.task.enabled);
|
||||
assert!(!resolved.manifest.feature.memory.enabled);
|
||||
assert!(resolved.manifest.feature.web.enabled);
|
||||
assert!(resolved.manifest.feature.workers.enabled);
|
||||
assert!(resolved.manifest.feature.sub_worker.enabled);
|
||||
assert!(resolved.manifest.feature.ticket.enabled);
|
||||
assert!(!resolved.manifest.feature.ticket.authoring);
|
||||
assert!(!resolved.manifest.feature.ticket.thread);
|
||||
|
||||
@@ -331,7 +331,7 @@ impl Scope {
|
||||
|
||||
/// Build a new [`Scope`] equal to `self` with `extra_deny` appended
|
||||
/// to the deny set. Used by dynamic-scope shrink paths
|
||||
/// (e.g. SpawnWorker-style delegation that strips Write from the
|
||||
/// (e.g. SubWorkerSpawn-style delegation that strips Write from the
|
||||
/// spawner without touching its allow rules).
|
||||
pub fn with_added_deny_rules(
|
||||
&self,
|
||||
|
||||
Reference in New Issue
Block a user