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