You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by wy...@apache.org on 2026/09/19 15:07:49 UTC
(maka) branch feat/runtime-host-rust updated: refactor(workhub): freeze answer plans in the plugin instance
This is an automated email from the ASF dual-hosted git repository.
M4n5ter pushed a commit to branch feat/runtime-host-rust
in repository https://gitbox.apache.org/repos/asf/maka.git
The following commit(s) were added to refs/heads/feat/runtime-host-rust by this push:
new af479fc755 refactor(workhub): freeze answer plans in the plugin instance
af479fc755 is described below
commit af479fc7557ee4021fe1266ca5c16005c2eea6a3
Author: Wang <m4...@gmail.com>
AuthorDate: Sat Sep 19 23:07:42 2026 +0800
refactor(workhub): freeze answer plans in the plugin instance
Generated-by: OpenAI Codex
---
crates/runtime-host/src/execution/workhub.rs | 119 +-------------
.../runtime-host/src/execution/workhub/answer.rs | 172 +++++++++++++++++++++
.../runtime-host/src/execution/workhub/commands.rs | 26 ++++
.../runtime-host/src/execution/workhub/profile.rs | 8 +-
crates/runtime-host/src/plugins/workhub.rs | 36 +++--
crates/runtime-host/src/plugins/workhub/answer.rs | 95 ++++++++++++
crates/runtime-host/src/plugins/workhub/control.rs | 10 ++
crates/runtime-host/src/server.rs | 1 +
crates/runtime-host/src/server/workhub.rs | 12 +-
docs/rust-parity.md | 2 +-
docs/rust-parity.zh-CN.md | 2 +-
tests/fixtures/client-workhub-answer.mjs | 8 +-
12 files changed, 347 insertions(+), 144 deletions(-)
diff --git a/crates/runtime-host/src/execution/workhub.rs b/crates/runtime-host/src/execution/workhub.rs
index 1de048fbdd..f6823890d6 100644
--- a/crates/runtime-host/src/execution/workhub.rs
+++ b/crates/runtime-host/src/execution/workhub.rs
@@ -17,19 +17,15 @@
* under the License.
*/
-use super::{Executions, Result, failure, internal, provider};
+use super::{Executions, Result, failure, internal};
use crate::session::SessionConfiguration;
-use maka_agent::{RunInput, RunWork};
use maka_event_log::sessions::SessionRecord;
-use maka_protocol::{
- OperationErrorCode as Code,
- workhub::{AnswerInput, TurnResult},
-};
+use maka_protocol::OperationErrorCode as Code;
use maka_runtime::workhub::ActionId;
-use maka_runtime::{artifact::content_digest, event::Invocation, workhub::COORDINATION_SESSION_ID};
+use maka_runtime::{event::Invocation, workhub::COORDINATION_SESSION_ID};
use std::sync::Arc;
-use uuid::Uuid;
+mod answer;
mod commands;
mod coordinator;
mod correction;
@@ -101,113 +97,6 @@ impl Executions {
}
Ok(boundary)
}
-
- /// The WorkHub entry point holds the shared admission gate and proves the
- /// stable Session identity before calling this method.
- pub(crate) async fn answer_workhub(
- self: &Arc<Self>,
- input: AnswerInput,
- session: SessionRecord<SessionConfiguration>,
- connection_id: Uuid,
- root_id: &str,
- ) -> Result<TurnResult> {
- if self.shutdown.is_cancelled() {
- return Err(failure(Code::HostDraining, "Host is draining"));
- }
- if input.text.trim().is_empty() {
- return Err(failure(
- Code::OperationConflict,
- "WorkHub answer text is empty",
- ));
- }
- let fingerprint = content_digest(&serde_json::to_vec(&input).map_err(internal)?);
- if let Some(record) = self
- .recorded(COORDINATION_SESSION_ID, &input.turn_id)
- .await?
- {
- if record.fingerprint.as_deref() != Some(&fingerprint) {
- return Err(failure(
- Code::OperationConflict,
- "WorkHub Turn belongs to another request",
- ));
- }
- return Ok(TurnResult {
- turn_id: input.turn_id,
- });
- }
- if self
- .has_session_work(COORDINATION_SESSION_ID)
- .await
- .map_err(internal)?
- {
- return Err(failure(
- Code::SessionBusy,
- "WorkHub has active or pending work",
- ));
- }
- let content = input.content();
- self.validate_message_content(COORDINATION_SESSION_ID, &content, root_id)
- .await?;
- let policy = profile::resolve(self)?;
- let _policy_admission = policy
- .admit()
- .map_err(|error| failure(Code::OperationUnavailable, &error.to_string()))?;
- let (tools, composition) =
- profile::tools(self, &session.configuration, connection_id, &policy.value)?;
- let provider = provider::resolve(
- &self.configuration,
- &self.oauth,
- COORDINATION_SESSION_ID,
- &session.configuration,
- )
- .await?;
- let mut configuration = session
- .configuration
- .invocation_configuration()
- .await
- .map_err(internal)?;
- configuration.tool_mode = if self
- .configuration
- .runtime_policy()
- .await
- .map_err(internal)?
- .policy
- .chat_defaults
- .code_mode_enabled
- {
- maka_runtime::execution::ToolMode::CodeMode
- } else {
- maka_runtime::execution::ToolMode::Direct
- };
- configuration.system_prompt = Some(policy.value.prompt.clone());
- configuration.tool_composition = Some(composition);
- let run = RunInput {
- invocation: Invocation {
- session_id: COORDINATION_SESSION_ID.into(),
- turn_id: input.turn_id.clone(),
- run_id: Uuid::new_v4().to_string(),
- invocation_id: Uuid::new_v4().to_string(),
- },
- work: RunWork::Message {
- source_messages: Vec::new(),
- skill_invocation: None,
- message: content.into(),
- tools,
- max_steps: 64,
- },
- request_fingerprint: Some(fingerprint),
- provider: provider.config,
- provider_options: provider.options,
- main_output_limit: provider.main_output_limit,
- supports_vision: provider.supports_vision,
- context: Some(provider.context),
- configuration,
- };
- self.launch(run).await?;
- Ok(TurnResult {
- turn_id: input.turn_id,
- })
- }
}
fn execution_available(
diff --git a/crates/runtime-host/src/execution/workhub/answer.rs b/crates/runtime-host/src/execution/workhub/answer.rs
new file mode 100644
index 0000000000..4e6180c3a3
--- /dev/null
+++ b/crates/runtime-host/src/execution/workhub/answer.rs
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+use super::{
+ super::{Executions, Result, failure, internal, provider},
+ profile,
+};
+use crate::{
+ plugins::workhub::answer::{Plan, Request},
+ session::SessionConfiguration,
+};
+use maka_agent::{RunInput, RunWork};
+use maka_event_log::sessions::SessionRecord;
+use maka_plugins::fiber::Context;
+use maka_protocol::{OperationErrorCode as Code, workhub::TurnResult};
+use maka_runtime::{event::Invocation, workhub::COORDINATION_SESSION_ID};
+use std::sync::Arc;
+use uuid::Uuid;
+
+impl Executions {
+ /// A canonical opening is authoritative even after plugin retirement or
+ /// model removal. Reading it never admits a new request.
+ pub(crate) async fn workhub_answer_receipt(
+ &self,
+ request: &Request,
+ ) -> Result<Option<TurnResult>> {
+ let Some(record) = self
+ .recorded(COORDINATION_SESSION_ID, &request.input.turn_id)
+ .await?
+ else {
+ return Ok(None);
+ };
+ if record.fingerprint.as_deref() != Some(&request.fingerprint) {
+ return Err(failure(
+ Code::OperationConflict,
+ "WorkHub Turn belongs to another request",
+ ));
+ }
+ Ok(Some(TurnResult {
+ turn_id: request.input.turn_id.clone(),
+ }))
+ }
+}
+
+pub(super) async fn execute(
+ executions: &Arc<Executions>,
+ caller: Context,
+ plan: Plan,
+ connection: Uuid,
+ root_id: &str,
+) -> Result<TurnResult> {
+ let session = {
+ let _gate = executions.lock_admission().await;
+ if let Some(receipt) = executions.workhub_answer_receipt(&plan.request).await? {
+ return Ok(receipt);
+ }
+ let _call = caller
+ .admit()
+ .map_err(|error| failure(Code::OperationUnavailable, &error.to_string()))?;
+ current(executions, &plan).await?
+ };
+ // No shared admission lock around credentials or workspace initialization.
+ // Preserve errors until the receipt recheck: a concurrent identical request
+ // may already have committed while preparation was in flight.
+ let prepared = async {
+ let provider = provider::observe(
+ &executions.configuration,
+ COORDINATION_SESSION_ID,
+ &session.configuration,
+ )
+ .await?;
+ let configuration = session
+ .configuration
+ .invocation_configuration()
+ .await
+ .map_err(internal)?;
+ Ok::<_, maka_protocol::OperationError>((provider, configuration))
+ }
+ .await;
+ let _gate = executions.lock_admission().await;
+ if let Some(receipt) = executions.workhub_answer_receipt(&plan.request).await? {
+ return Ok(receipt);
+ }
+ let _call = caller
+ .admit()
+ .map_err(|error| failure(Code::OperationUnavailable, &error.to_string()))?;
+ current(executions, &plan).await?;
+ let (provider, mut configuration) = prepared?;
+ let content = plan.request.input.content();
+ executions
+ .validate_message_content(COORDINATION_SESSION_ID, &content, root_id)
+ .await?;
+ let (tools, composition) =
+ profile::tools(executions, &session.configuration, connection, &plan.policy)?;
+ let provider = provider.admit(&executions.oauth)?;
+ configuration.tool_mode = plan.tool_mode;
+ configuration.system_prompt = Some(plan.policy.prompt.clone());
+ configuration.tool_composition = Some(composition);
+ executions
+ .launch(RunInput {
+ invocation: Invocation {
+ session_id: COORDINATION_SESSION_ID.into(),
+ turn_id: plan.request.input.turn_id.clone(),
+ run_id: Uuid::new_v4().to_string(),
+ invocation_id: Uuid::new_v4().to_string(),
+ },
+ work: RunWork::Message {
+ source_messages: Vec::new(),
+ skill_invocation: None,
+ message: content.into(),
+ tools,
+ max_steps: plan.max_steps,
+ },
+ request_fingerprint: Some(plan.request.fingerprint),
+ provider: provider.config,
+ provider_options: provider.options,
+ main_output_limit: provider.main_output_limit,
+ supports_vision: provider.supports_vision,
+ context: Some(provider.context),
+ configuration,
+ })
+ .await?;
+ Ok(TurnResult {
+ turn_id: plan.request.input.turn_id,
+ })
+}
+
+async fn current(
+ executions: &Executions,
+ plan: &Plan,
+) -> Result<SessionRecord<SessionConfiguration>> {
+ if executions.shutdown.is_cancelled() {
+ return Err(failure(Code::HostDraining, "Host is draining"));
+ }
+ let session = executions
+ .workhub_coordinator()
+ .await?
+ .ok_or_else(|| failure(Code::NotFound, "WorkHub Session has not been resolved"))?;
+ if session.configuration_digest != plan.configuration_digest {
+ return Err(failure(
+ Code::OperationConflict,
+ "WorkHub configuration changed during preparation",
+ ));
+ }
+ if executions
+ .has_session_work(COORDINATION_SESSION_ID)
+ .await
+ .map_err(internal)?
+ {
+ return Err(failure(
+ Code::SessionBusy,
+ "WorkHub has active or pending work",
+ ));
+ }
+ Ok(session)
+}
diff --git a/crates/runtime-host/src/execution/workhub/commands.rs b/crates/runtime-host/src/execution/workhub/commands.rs
index 740a0b5dba..4f288d9350 100644
--- a/crates/runtime-host/src/execution/workhub/commands.rs
+++ b/crates/runtime-host/src/execution/workhub/commands.rs
@@ -32,6 +32,7 @@ use std::sync::{Arc, Weak};
pub(crate) struct WorkHubCommands {
executions: Weak<Executions>,
pub(super) project_usage: crate::server::ProjectUsage,
+ root_id: String,
}
impl WorkHubCommands {
fn executions(&self) -> Result<Arc<Executions>> {
@@ -61,14 +62,39 @@ impl WorkHubCommands {
pub(crate) fn new(
executions: &Arc<Executions>,
project_usage: crate::server::ProjectUsage,
+ root_id: String,
) -> Self {
Self {
executions: Arc::downgrade(executions),
project_usage,
+ root_id,
}
}
}
impl Commands for WorkHubCommands {
+ fn chat_defaults(
+ &self,
+ ) -> BoxFuture<'_, Result<maka_runtime::configuration::policy::ChatDefaults>> {
+ Box::pin(async move {
+ self.executions()?
+ .configuration
+ .runtime_policy()
+ .await
+ .map(|snapshot| snapshot.policy.chat_defaults)
+ .map_err(super::super::internal)
+ })
+ }
+ fn answer(
+ &self,
+ caller: Context,
+ plan: crate::plugins::workhub::answer::Plan,
+ connection: uuid::Uuid,
+ ) -> BoxFuture<'_, Result<maka_protocol::workhub::TurnResult>> {
+ Box::pin(async move {
+ super::answer::execute(&self.executions()?, caller, plan, connection, &self.root_id)
+ .await
+ })
+ }
fn resolve_model(
&self,
target: maka_protocol::session::SessionModelTarget,
diff --git a/crates/runtime-host/src/execution/workhub/profile.rs b/crates/runtime-host/src/execution/workhub/profile.rs
index a4878b035a..b4404190e1 100644
--- a/crates/runtime-host/src/execution/workhub/profile.rs
+++ b/crates/runtime-host/src/execution/workhub/profile.rs
@@ -19,7 +19,7 @@
use super::{Executions, Result, SessionConfiguration, failure};
use crate::execution::read;
-use crate::plugins::workhub::{ID, Policy};
+use crate::plugins::workhub::{Control, ID, Policy};
use maka_client_capability::BindingError;
use maka_plugins::{composition::Scope, contributions::Contribution};
use maka_protocol::OperationErrorCode as Code;
@@ -76,13 +76,13 @@ pub(in crate::execution) fn catalog(
let _admission = policy
.admit()
.map_err(|error| failure(Code::OperationUnavailable, &error.to_string()))?;
- bound_catalog(executions, tools, &policy.value)
+ bound_catalog(executions, tools, &policy.value.policy)
}
-pub(super) fn resolve(executions: &Executions) -> Result<Contribution<Policy>> {
+fn resolve(executions: &Executions) -> Result<Contribution<Control>> {
executions
.plugin_catalog
- .snapshot::<Policy>(&Scope::Profile)
+ .snapshot::<Control>(&Scope::Profile)
.entries
.remove(ID)
.ok_or_else(|| failure(Code::OperationUnavailable, "WorkHub policy is unavailable"))
diff --git a/crates/runtime-host/src/plugins/workhub.rs b/crates/runtime-host/src/plugins/workhub.rs
index e9a9407735..b9900d80cb 100644
--- a/crates/runtime-host/src/plugins/workhub.rs
+++ b/crates/runtime-host/src/plugins/workhub.rs
@@ -30,6 +30,7 @@ use maka_runtime::{
};
use serde_json::Value;
use std::sync::Arc;
+pub(crate) mod answer;
pub(crate) mod candidates;
pub(crate) mod control;
pub(crate) mod coordinator;
@@ -67,8 +68,6 @@ pub(crate) fn install(
"built-in WorkHub identity is reserved".into(),
));
}
- catalog.host_only::<Policy>()?;
- catalog.reserve_for::<Policy>(ID, ID)?;
catalog.host_only::<Control>()?;
catalog.reserve_for::<Control>(ID, ID)?;
setup.builtins.insert(
@@ -127,6 +126,23 @@ impl Plugin for WorkHub {
.identity()
.map_err(|error| error.to_string())?;
let mut staged = Staged::default();
+ let policy = Policy {
+ required_clients: &CLIENT_TOOLS,
+ optional_clients: &BROWSER_TOOLS,
+ attachment_description: ATTACHMENT_DESCRIPTION,
+ prompt: SystemPrompt {
+ text: PROMPT.into(),
+ policy_revision: 0,
+ sources: vec![SourceRevision {
+ kind: SourceKind::PromptSection,
+ name: ID.into(),
+ package_id: identity.package_id,
+ entry_id: identity.entry_id,
+ activation: identity.activation,
+ revision: content_digest(PROMPT.as_bytes()),
+ }],
+ },
+ };
staged
.insert(
ID,
@@ -134,27 +150,17 @@ impl Plugin for WorkHub {
commands,
caller: context.lifecycle.clone(),
workspace,
+ policy: Arc::new(policy),
},
)
.map_err(|error| error.to_string())?;
- staged.insert(ID, Policy {
- required_clients: &CLIENT_TOOLS,
- optional_clients: &BROWSER_TOOLS,
- attachment_description: "Read a user attachment belonging to this WorkHub conversation. Only supplied attachment references are accepted. Follow next to continue a bounded page.",
- prompt: SystemPrompt {
- text: PROMPT.into(), policy_revision: 0,
- sources: vec![SourceRevision {
- kind: SourceKind::PromptSection, name: ID.into(),
- package_id: identity.package_id, entry_id: identity.entry_id,
- activation: identity.activation, revision: content_digest(PROMPT.as_bytes()),
- }],
- },
- }).map_err(|error| error.to_string())?;
Ok(staged)
})
}
}
+const ATTACHMENT_DESCRIPTION: &str = "Read a user attachment belonging to this WorkHub conversation. Only supplied attachment references are accepted. Follow next to continue a bounded page.";
+
const CLIENT_TOOLS: [&str; 2] = [
"mcp__desktop_workhub__control",
"mcp__desktop_workhub__tasks",
diff --git a/crates/runtime-host/src/plugins/workhub/answer.rs b/crates/runtime-host/src/plugins/workhub/answer.rs
new file mode 100644
index 0000000000..91a03cc0fe
--- /dev/null
+++ b/crates/runtime-host/src/plugins/workhub/answer.rs
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+use super::{
+ Policy,
+ control::{Result, failure},
+};
+use maka_protocol::{
+ OperationErrorCode as Code,
+ workhub::{AnswerInput, TurnResult},
+};
+use maka_runtime::{artifact::content_digest, execution::ToolMode};
+use std::sync::Arc;
+
+/// Stable user request, independent of current model or plugin availability.
+pub(crate) struct Request {
+ pub input: AnswerInput,
+ pub fingerprint: String,
+}
+impl Request {
+ pub(crate) fn new(input: AnswerInput) -> Result<Self> {
+ if input.text.trim().is_empty() {
+ return Err(failure(
+ Code::OperationConflict,
+ "WorkHub answer text is empty",
+ ));
+ }
+ let fingerprint = serde_json::to_vec(&input)
+ .map(|bytes| content_digest(&bytes))
+ .map_err(|error| failure(Code::InternalFailure, error.to_string()))?;
+ Ok(Self { input, fingerprint })
+ }
+}
+
+/// One plugin generation's decision; Host checks its observed configuration
+/// again and freezes this surface in the canonical opening.
+pub(crate) struct Plan {
+ pub request: Request,
+ pub configuration_digest: String,
+ pub policy: Arc<Policy>,
+ pub tool_mode: ToolMode,
+ pub max_steps: usize,
+}
+
+impl super::Control {
+ pub(crate) async fn answer(
+ &self,
+ request: Request,
+ connection: uuid::Uuid,
+ ) -> Result<TurnResult> {
+ let _call = self
+ .caller
+ .admit()
+ .map_err(|error| failure(Code::OperationUnavailable, error.to_string()))?;
+ let session = self
+ .commands
+ .coordinator(self.caller.clone())
+ .await?
+ .ok_or_else(|| failure(Code::NotFound, "WorkHub Session has not been resolved"))?;
+ let defaults = self.commands.chat_defaults().await?;
+ self.commands
+ .answer(
+ self.caller.clone(),
+ Plan {
+ request,
+ configuration_digest: session.configuration_digest,
+ policy: self.policy.clone(),
+ tool_mode: if defaults.code_mode_enabled {
+ ToolMode::CodeMode
+ } else {
+ ToolMode::Direct
+ },
+ max_steps: 64,
+ },
+ connection,
+ )
+ .await
+ }
+}
diff --git a/crates/runtime-host/src/plugins/workhub/control.rs b/crates/runtime-host/src/plugins/workhub/control.rs
index 93ce842023..66ea74dff3 100644
--- a/crates/runtime-host/src/plugins/workhub/control.rs
+++ b/crates/runtime-host/src/plugins/workhub/control.rs
@@ -50,6 +50,15 @@ pub(crate) struct Stop {
/// Commands own admission, durable receipts and settlement. No locks, arbitrary
/// log writes or Host handles are exposed to the business implementation.
pub(crate) trait Commands: Send + Sync {
+ fn chat_defaults(
+ &self,
+ ) -> BoxFuture<'_, Result<maka_runtime::configuration::policy::ChatDefaults>>;
+ fn answer(
+ &self,
+ caller: Context,
+ plan: super::answer::Plan,
+ connection: uuid::Uuid,
+ ) -> BoxFuture<'_, Result<maka_protocol::workhub::TurnResult>>;
fn resolve_model(
&self,
target: maka_protocol::session::SessionModelTarget,
@@ -140,6 +149,7 @@ pub(crate) struct Control {
pub(super) commands: Arc<dyn Commands>,
pub(super) caller: Context,
pub(super) workspace: std::path::PathBuf,
+ pub(crate) policy: Arc<super::Policy>,
}
impl Control {
pub async fn stop(&self, input: ActInput) -> Result<ActResult> {
diff --git a/crates/runtime-host/src/server.rs b/crates/runtime-host/src/server.rs
index 26e24c3b84..d6aea93a2c 100644
--- a/crates/runtime-host/src/server.rs
+++ b/crates/runtime-host/src/server.rs
@@ -264,6 +264,7 @@ impl Host {
let workhub_commands = Arc::new(crate::execution::WorkHubCommands::new(
&executions,
project_usage.clone(),
+ root.root_id().into(),
));
crate::plugins::workhub::install(
&mut setup,
diff --git a/crates/runtime-host/src/server/workhub.rs b/crates/runtime-host/src/server/workhub.rs
index 5d269968ab..1348df469b 100644
--- a/crates/runtime-host/src/server/workhub.rs
+++ b/crates/runtime-host/src/server/workhub.rs
@@ -156,13 +156,11 @@ async fn answer(
input: workhub::AnswerInput,
connection_id: uuid::Uuid,
) -> Result<workhub::TurnResult, OperationError> {
- let _admission = host.executions.lock_admission().await;
- let session = record(host)
- .await?
- .ok_or_else(|| failure(Code::NotFound, "WorkHub Session has not been resolved"))?;
- host.executions
- .answer_workhub(input, session, connection_id, host.root_id())
- .await
+ let request = crate::plugins::workhub::answer::Request::new(input)?;
+ if let Some(receipt) = host.executions.workhub_answer_receipt(&request).await? {
+ return Ok(receipt);
+ }
+ control(host)?.value.answer(request, connection_id).await
}
fn serialize(value: impl serde::Serialize) -> Result<Value, OperationError> {
diff --git a/docs/rust-parity.md b/docs/rust-parity.md
index 38f75ffbbc..72a3e445ad 100644
--- a/docs/rust-parity.md
+++ b/docs/rust-parity.md
@@ -57,7 +57,7 @@ Client changes are allowed when they remove an obsolete path, as with Graph.
| Domain / current coupling | Target and completion condition |
| --- | --- |
| **Skills:** `maka.skills` owns discovery, input preparation, per-step tool/context snapshots, governance, preference CAS, preview, import and workspace/user publication. Published Client Contributions own Session/new-workspace pickers, management and draft suggestions. | Desktop supplies target-bound Slots, generic Remote transport and authorized native file actions. The old scanner, importer, controller and Skills IPC/preload facade are removed. Host retains thin external protocol ada [...]
-| **WorkHub:** `maka.workhub` owns coordinator resolution/model configuration, prompt/client-tool policy, delegation/correction/selection workflows, Stop/Resume request/results, candidate filtering/references, target/default choices and frozen correction recovery policy. Retirement closes pending selection forms; Host retains canonical outcomes, atomic delivery and accepted settlement. | Move answer composition, native tools, Remote and actual Desktop UI into the built-in domain. Preserv [...]
+| **WorkHub:** `maka.workhub` owns coordinator resolution/model configuration, answer composition, prompt/client-tool policy, delegation/correction/selection workflows, Stop/Resume request/results, candidate filtering/references, target/default choices and frozen correction recovery policy. Retirement closes pending selection forms; Host retains canonical outcomes, atomic delivery and accepted settlement. | Move native tools, Remote and actual Desktop UI into the built-in domain. Preserv [...]
| **Default assistant behavior:** `maka.assistant` publishes the default behavior, persona, personalization and workspace instructions. | Sources are frozen per logical model step; disabling the plugin removes its persona. Explicit Session/child instructions remain independent of replaceable prompt Contributions. Execution/compaction invariants remain Host-owned. |
| **Graph/Swarm and Scheduler:** already built-in plugins; behavior selection uses an open typed identity. Graph uses atomic activate/stop/retire-idle commands and read-only preferences, without `Executions`, configuration writers or Host locks. | Preserve existing orchestration and wakeup behavior. Reuse these narrow commands where semantics match; review Scheduler against the same ownership rule. Typed domain repositories remain legitimate. |
| **Code Mode:** mode selection, nested dispatch and history projection cross several crates. | After the first domain migrations, move its user-facing tool and mode policy where a real contribution boundary helps. Keep V8 ownership, nested-call permissions, dispatch/settlement and canonical history in the runtime. Do not invent a universal executor hook merely to move `exec`. |
diff --git a/docs/rust-parity.zh-CN.md b/docs/rust-parity.zh-CN.md
index ed9e79204a..b5bffb0f96 100644
--- a/docs/rust-parity.zh-CN.md
+++ b/docs/rust-parity.zh-CN.md
@@ -50,7 +50,7 @@
| 领域/当前耦合 | 目标与完成条件 |
| --- | --- |
| **Skills:**`maka.skills` 拥有发现、输入准备、每步工具/上下文快照、治理、偏好 CAS、预览、导入及 workspace/user 发布;已发布的 Client Contribution 拥有 Session/新工作区选择器、管理页与草稿建议。 | Desktop 提供目标绑定的 Slot、通用 Remote 传输及授权的原生文件操作;旧扫描、导入、控制器和 Skills IPC/preload 门面已删除。Host 仅保留薄外部协议适配、准入与不可变回执,不再解析 Skill。 |
-| **WorkHub:**`maka.workhub` 拥有协调会话解析/模型配置、提示词/客户端工具策略、委派/纠正/选择流程、Stop/Resume 请求/结果、候选筛选/引用、目标/默认值决策及冻结纠正意图的恢复策略。退休关闭待答选择表单;Host 保留规范结果、原子投递与已接受操作的结算。 | 回答组合、原生工具、Remote 及实际 Desktop UI 迁入内置领域。保留精确消息定位与原子准入/取消/回执;不能把纠正拆成插件和 Host 各自写入。 |
+| **WorkHub:**`maka.workhub` 拥有协调会话解析/模型配置、回答组合、提示词/客户端工具策略、委派/纠正/选择流程、Stop/Resume 请求/结果、候选筛选/引用、目标/默认值决策及冻结纠正意图的恢复策略。退休关闭待答选择表单;Host 保留规范结果、原子投递与已接受操作的结算。 | 原生工具、Remote 及实际 Desktop UI 迁入内置领域。保留精确消息定位与原子准入/取消/回执;不能把纠正拆成插件和 Host 各自写入。 |
| **默认助手行为:**`maka.assistant` 发布默认 behavior、persona、个性化和工作区指令。 | 每逻辑模型步骤冻结来源;停用后不保留隐藏 persona。显式 Session/子任务指令独立于可替换的提示词 Contribution;执行/压缩不变量仍由 Host 维护。 |
| **Graph/Swarm 与 Scheduler:**已经是内置插件,behavior 按开放的类型化身份选择。Graph 使用原子激活/停止/空闲退休命令及只读偏好,不接收 `Executions`、配置写入器或 Host 锁。 | 保持已有编排与唤醒行为;语义相同时复用这些窄命令,按同一归属规则复核 Scheduler。类型化领域 repository 可以保留。 |
| **Code Mode:**模式选择、嵌套派发及历史投影跨越多个 crate。 | 首批领域迁移后,在有实际 Contribution 边界收益时迁移面向用户的工具和模式策略;V8 所有权、嵌套调用权限、派发/结算及规范历史保留 runtime。不为搬迁 `exec` 发明万能执行 hook。 |
diff --git a/tests/fixtures/client-workhub-answer.mjs b/tests/fixtures/client-workhub-answer.mjs
index d517196b0a..b059473aa2 100644
--- a/tests/fixtures/client-workhub-answer.mjs
+++ b/tests/fixtures/client-workhub-answer.mjs
@@ -227,7 +227,13 @@ export async function verifyWorkhubAnswer(connection, workspace, reopened) {
assert.equal(requests.length, 0, 'A disabled policy must fail before model dispatch');
await togglePolicy(false);
observer = await watchSession(connection, sessionId, { kind: 'tail', maxBytes: 2 });
- assert.deepEqual(await request('workhub.coordination.answer', input), { turnId });
+ assert.deepEqual(
+ await Promise.all([
+ request('workhub.coordination.answer', input),
+ request('workhub.coordination.answer', input),
+ ]),
+ [{ turnId }, { turnId }],
+ );
await observer.waitFor(
(frame) =>
frame.kind === 'subscription.session_projection' &&
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org