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 14:21:06 UTC

(maka) branch feat/runtime-host-rust updated: refactor(workhub): move ordinary delegation behind typed plugin commands

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 4d9668c782 refactor(workhub): move ordinary delegation behind typed plugin commands
4d9668c782 is described below

commit 4d9668c78256d489b4f5f56736a2fbdde6bb8666
Author: Wang <m4...@gmail.com>
AuthorDate: Sat Sep 19 22:20:52 2026 +0800

    refactor(workhub): move ordinary delegation behind typed plugin commands
    
    Generated-by: OpenAI Codex
---
 crates/runtime-host/src/execution/workhub.rs       |   1 +
 .../runtime-host/src/execution/workhub/commands.rs |  54 ++++-
 .../src/execution/workhub/delegation.rs            | 192 ++++++++++++++++++
 .../src/execution/workhub/delegation/receipt.rs    |  78 ++++++++
 crates/runtime-host/src/plugins/workhub.rs         |   1 +
 crates/runtime-host/src/plugins/workhub/control.rs |  10 +
 .../runtime-host/src/plugins/workhub/delegation.rs |  99 ++++++++++
 .../runtime-host/src/plugins/workhub/selection.rs  |   1 +
 crates/runtime-host/src/server.rs                  |  15 +-
 crates/runtime-host/src/server/projects.rs         |  15 +-
 crates/runtime-host/src/server/projects/usage.rs   |  73 +++++++
 crates/runtime-host/src/server/workhub/action.rs   | 219 +--------------------
 docs/rust-parity.md                                |   2 +-
 docs/rust-parity.zh-CN.md                          |   2 +-
 14 files changed, 526 insertions(+), 236 deletions(-)

diff --git a/crates/runtime-host/src/execution/workhub.rs b/crates/runtime-host/src/execution/workhub.rs
index 3a7af54372..c6c88c285a 100644
--- a/crates/runtime-host/src/execution/workhub.rs
+++ b/crates/runtime-host/src/execution/workhub.rs
@@ -31,6 +31,7 @@ use std::sync::Arc;
 use uuid::Uuid;
 
 mod commands;
+mod delegation;
 pub(super) mod profile;
 mod resume;
 pub(crate) use commands::WorkHubCommands;
diff --git a/crates/runtime-host/src/execution/workhub/commands.rs b/crates/runtime-host/src/execution/workhub/commands.rs
index ea9f6b0015..7556413b2d 100644
--- a/crates/runtime-host/src/execution/workhub/commands.rs
+++ b/crates/runtime-host/src/execution/workhub/commands.rs
@@ -30,10 +30,50 @@ use maka_event_log::{
 };
 use maka_plugins::fiber::Context;
 use maka_protocol::OperationErrorCode as Code;
-use std::sync::Weak;
+use std::sync::{Arc, Weak};
 
-pub(crate) struct WorkHubCommands(pub Weak<Executions>);
+pub(crate) struct WorkHubCommands {
+    executions: Weak<Executions>,
+    pub(super) project_usage: crate::server::ProjectUsage,
+}
+impl WorkHubCommands {
+    pub(crate) fn new(
+        executions: &Arc<Executions>,
+        project_usage: crate::server::ProjectUsage,
+    ) -> Self {
+        Self {
+            executions: Arc::downgrade(executions),
+            project_usage,
+        }
+    }
+}
 impl Commands for WorkHubCommands {
+    fn delegation(
+        &self,
+        caller: Context,
+        identity: crate::plugins::workhub::delegation::Identity,
+    ) -> BoxFuture<'_, Result<Option<maka_runtime::workhub::Delegation>>> {
+        Box::pin(async move {
+            let executions = self
+                .executions
+                .upgrade()
+                .ok_or_else(|| failure(Code::HostDraining, "Host is closed"))?;
+            super::delegation::probe(&executions, caller, identity).await
+        })
+    }
+    fn delegate(
+        &self,
+        caller: Context,
+        request: crate::plugins::workhub::delegation::Request,
+    ) -> BoxFuture<'_, Result<maka_runtime::workhub::Delegation>> {
+        Box::pin(async move {
+            let executions = self
+                .executions
+                .upgrade()
+                .ok_or_else(|| failure(Code::HostDraining, "Host is closed"))?;
+            super::delegation::execute(self, &executions, caller, request).await
+        })
+    }
     fn target(
         &self,
         session: String,
@@ -42,7 +82,7 @@ impl Commands for WorkHubCommands {
     {
         Box::pin(async move {
             let executions = self
-                .0
+                .executions
                 .upgrade()
                 .ok_or_else(|| failure(Code::HostDraining, "Host is closed"))?;
             executions.workhub_target(&session, eligible).await
@@ -54,7 +94,7 @@ impl Commands for WorkHubCommands {
     ) -> BoxFuture<'_, Result<crate::execution::Creation>> {
         Box::pin(async move {
             let executions = self
-                .0
+                .executions
                 .upgrade()
                 .ok_or_else(|| failure(Code::HostDraining, "Host is closed"))?;
             executions.prepare_session(request).await
@@ -69,7 +109,7 @@ impl Commands for WorkHubCommands {
     ) -> BoxFuture<'_, Result<crate::plugins::workhub::resume::Receipt>> {
         Box::pin(async move {
             let executions = self
-                .0
+                .executions
                 .upgrade()
                 .ok_or_else(|| failure(Code::HostDraining, "Host is closed"))?;
             super::resume::execute(&executions, caller, request, connection, eligible).await
@@ -81,7 +121,7 @@ impl Commands for WorkHubCommands {
     ) -> BoxFuture<'_, Result<Vec<Candidate<SessionConfiguration>>>> {
         Box::pin(async move {
             let executions = self
-                .0
+                .executions
                 .upgrade()
                 .ok_or_else(|| failure(Code::HostDraining, "Host is closed"))?;
             if executions.shutdown.is_cancelled() {
@@ -97,7 +137,7 @@ impl Commands for WorkHubCommands {
     fn stop(&self, caller: Context, request: Stop) -> BoxFuture<'_, Result<StopRecord>> {
         Box::pin(async move {
             let executions = self
-                .0
+                .executions
                 .upgrade()
                 .ok_or_else(|| failure(Code::HostDraining, "Host is closed"))?;
             let (record, completed, _call) = {
diff --git a/crates/runtime-host/src/execution/workhub/delegation.rs b/crates/runtime-host/src/execution/workhub/delegation.rs
new file mode 100644
index 0000000000..210c256dfd
--- /dev/null
+++ b/crates/runtime-host/src/execution/workhub/delegation.rs
@@ -0,0 +1,192 @@
+/*
+ * 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.
+ */
+
+mod receipt;
+use super::super::{Executions, Result, failure};
+use super::commands::WorkHubCommands;
+use crate::plugins::workhub::{
+    delegation::{Identity, Request},
+    target::Target,
+};
+use maka_plugins::fiber::Context;
+use maka_protocol::OperationErrorCode as Code;
+use maka_runtime::{
+    event::{CommitError, EventWrite, Fact, Invocation, RuntimeEvent},
+    input::InvocationInput,
+    workhub::{Delegation, DelegationDelivery},
+};
+use std::sync::Arc;
+use uuid::Uuid;
+
+pub(super) async fn probe(
+    executions: &Arc<Executions>,
+    caller: Context,
+    identity: Identity,
+) -> Result<Option<Delegation>> {
+    let _gate = executions.lock_admission().await;
+    let _call = caller
+        .admit()
+        .map_err(|error| failure(Code::OperationUnavailable, &error.to_string()))?;
+    let receipt = receipt::read(executions, &identity).await?;
+    if receipt.is_none() {
+        if executions.shutdown.is_cancelled() {
+            return Err(failure(Code::HostDraining, "Host is draining"));
+        }
+        let source = executions.workhub_source(&identity.turn_id).await?;
+        if !matches!(source.root_input(), InvocationInput::Message { .. }) {
+            return Err(failure(
+                Code::OperationConflict,
+                "WorkHub action requires a user message",
+            ));
+        }
+    }
+    Ok(receipt)
+}
+
+pub(super) async fn execute(
+    commands: &WorkHubCommands,
+    executions: &Arc<Executions>,
+    caller: Context,
+    request: Request,
+) -> Result<Delegation> {
+    let mut admission = Some(executions.lock_admission().await);
+    let _call = caller
+        .admit()
+        .map_err(|error| failure(Code::OperationUnavailable, &error.to_string()))?;
+    if let Some(receipt) = receipt::read(executions, &request.identity).await? {
+        return Ok(receipt);
+    }
+    if executions.shutdown.is_cancelled() {
+        return Err(failure(Code::HostDraining, "Host is draining"));
+    }
+    let source = executions.workhub_source(&request.identity.turn_id).await?;
+    if let Some(selected) = &request.selected {
+        if selected.invocation != source.invocation {
+            return Err(failure(
+                Code::OperationConflict,
+                "The selecting Run is no longer active",
+            ));
+        }
+        crate::plugins::workhub::target::validate_selection(selected)?;
+    }
+    let InvocationInput::Message { content, .. } = source.root_input() else {
+        return Err(failure(
+            Code::OperationConflict,
+            "WorkHub action requires a user message",
+        ));
+    };
+    let target = request.target;
+    if let Target::Created { creation, .. } = &target {
+        executions.validate_creation(creation).await?;
+        commands
+            .project_usage
+            .record(&creation.configuration.workspace)
+            .await?;
+    }
+    let owner = executions.active_session_owner(target.id());
+    let delivery = match (&target, &owner) {
+        (
+            Target::Existing {
+                configuration_digest,
+                ..
+            },
+            Some(_),
+        ) => DelegationDelivery::Steering {
+            configuration_digest: configuration_digest.clone(),
+        },
+        (_, None) => DelegationDelivery::NewTurn,
+        (Target::Created { .. }, Some(_)) => {
+            return Err(failure(
+                Code::OperationConflict,
+                "Created target already has an execution owner",
+            ));
+        }
+    };
+    let delegation = Delegation {
+        kind: target.kind(),
+        description: Some(target.description()),
+        delivery,
+        action_id: request.identity.action_id,
+        request_fingerprint: request.identity.fingerprint,
+        source_message_event_id: source.root_opening_event_id().to_owned(),
+        target: owner.unwrap_or_else(|| Invocation {
+            session_id: target.id().to_owned(),
+            turn_id: Uuid::new_v4().to_string(),
+            run_id: Uuid::new_v4().to_string(),
+            invocation_id: Uuid::new_v4().to_string(),
+        }),
+        target_revision: target.revision(),
+        delegation_text: request.text.unwrap_or_else(|| content.text.clone()),
+    };
+    delegation
+        .message(content)
+        .map_err(|reason| failure(Code::OperationUnavailable, reason))?;
+    let action = EventWrite::plain(RuntimeEvent::new(
+        source.invocation,
+        Fact::WorkhubDelegated {
+            delegation: Box::new(delegation.clone()),
+        },
+    ))
+    .map_err(|error| failure(Code::InternalFailure, &error.to_string()))?;
+    let committed = match &target {
+        Target::Created { creation, .. } => executions
+            .log
+            .create_workhub_session(&action, &creation.configuration)
+            .await
+            .map_err(|error| match error {
+                maka_event_log::StoreError::CommitUnknown(_)
+                | maka_event_log::StoreError::OperationUnknown => {
+                    CommitError::OutcomeUnknown(error.to_string())
+                }
+                other => CommitError::Rejected(other.to_string()),
+            }),
+        Target::Existing { .. } => executions.log.append(&action).await,
+    };
+    if let Err(error) = committed {
+        return Err(match error {
+            CommitError::OutcomeUnknown(reason) => {
+                executions.begin_drain();
+                failure(Code::CommitOutcomeUnknown, &reason)
+            }
+            CommitError::Rejected(reason) => {
+                let current = executions
+                    .log
+                    .get_session::<crate::session::SessionConfiguration>(target.id())
+                    .await
+                    .map_err(|error| super::commands::stored(executions, error))?;
+                let code = if let Target::Existing {
+                    configuration_digest,
+                    ..
+                } = &target
+                    && current.is_none_or(|record| {
+                        record.archived || record.configuration_digest != *configuration_digest
+                    }) {
+                    Code::CandidateSetStale
+                } else {
+                    Code::OperationConflict
+                };
+                failure(code, &reason)
+            }
+        });
+    }
+    executions
+        .dispatch_pending(target.id(), &mut admission)
+        .await?;
+    Ok(delegation)
+}
diff --git a/crates/runtime-host/src/execution/workhub/delegation/receipt.rs b/crates/runtime-host/src/execution/workhub/delegation/receipt.rs
new file mode 100644
index 0000000000..7cb1c6311d
--- /dev/null
+++ b/crates/runtime-host/src/execution/workhub/delegation/receipt.rs
@@ -0,0 +1,78 @@
+/*
+ * 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::commands;
+use super::super::{Executions, Result, failure};
+use crate::plugins::workhub::delegation::Identity;
+use maka_protocol::OperationErrorCode as Code;
+use maka_runtime::{
+    event::Fact,
+    workhub::{COORDINATION_SESSION_ID, Delegation},
+};
+
+/// Caller holds admission; an existing canonical action takes precedence over
+/// source liveness, target changes and model availability.
+pub(super) async fn read(
+    executions: &Executions,
+    identity: &Identity,
+) -> Result<Option<Delegation>> {
+    if executions
+        .log
+        .workhub_stop(&identity.action_id)
+        .await
+        .map_err(|error| commands::stored(executions, error))?
+        .is_some()
+        || executions
+            .log
+            .workhub_correction(&identity.action_id)
+            .await
+            .map_err(|error| commands::stored(executions, error))?
+            .is_some()
+    {
+        return Err(failure(
+            Code::OperationConflict,
+            "WorkHub action already belongs to a control operation",
+        ));
+    }
+    // Receipt authority survives source termination, model removal and target changes.
+    if let Some(stored) = executions
+        .log
+        .workhub_action(&identity.action_id)
+        .await
+        .map_err(|error| commands::stored(executions, error))?
+    {
+        let Fact::WorkhubDelegated { delegation } = stored.event.fact else {
+            return Err(failure(
+                Code::OperationConflict,
+                "WorkHub action belongs to another operation",
+            ));
+        };
+        if stored.event.invocation.session_id != COORDINATION_SESSION_ID
+            || stored.event.invocation.turn_id != identity.turn_id
+            || delegation.request_fingerprint != identity.fingerprint
+        {
+            return Err(failure(
+                Code::OperationConflict,
+                "WorkHub action belongs to another request",
+            ));
+        }
+        return Ok(Some(*delegation));
+    }
+    Ok(None)
+}
diff --git a/crates/runtime-host/src/plugins/workhub.rs b/crates/runtime-host/src/plugins/workhub.rs
index cb78198779..833d6db6df 100644
--- a/crates/runtime-host/src/plugins/workhub.rs
+++ b/crates/runtime-host/src/plugins/workhub.rs
@@ -32,6 +32,7 @@ use serde_json::Value;
 use std::sync::Arc;
 pub(crate) mod candidates;
 pub(crate) mod control;
+pub(crate) mod delegation;
 pub(crate) mod resume;
 pub(crate) mod selection;
 pub(crate) mod target;
diff --git a/crates/runtime-host/src/plugins/workhub/control.rs b/crates/runtime-host/src/plugins/workhub/control.rs
index 2485ef1b16..7586ae872e 100644
--- a/crates/runtime-host/src/plugins/workhub/control.rs
+++ b/crates/runtime-host/src/plugins/workhub/control.rs
@@ -43,6 +43,16 @@ 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 delegation(
+        &self,
+        caller: Context,
+        identity: super::delegation::Identity,
+    ) -> BoxFuture<'_, Result<Option<maka_runtime::workhub::Delegation>>>;
+    fn delegate(
+        &self,
+        caller: Context,
+        request: super::delegation::Request,
+    ) -> BoxFuture<'_, Result<maka_runtime::workhub::Delegation>>;
     /// Apply the domain predicate inside one bounded read snapshot, before its limit.
     fn candidates(
         &self,
diff --git a/crates/runtime-host/src/plugins/workhub/delegation.rs b/crates/runtime-host/src/plugins/workhub/delegation.rs
new file mode 100644
index 0000000000..781c5c0a71
--- /dev/null
+++ b/crates/runtime-host/src/plugins/workhub/delegation.rs
@@ -0,0 +1,99 @@
+/*
+ * 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::{
+    control::{Result, failure, fingerprint},
+    selection::SelectedTarget,
+    target::Target,
+};
+use maka_protocol::{
+    OperationErrorCode as Code,
+    workhub::{ActInput, ActResult, Proposal},
+};
+use maka_runtime::workhub::{ActionId, Delegation, DelegationKind};
+
+#[derive(Clone)]
+pub(crate) struct Identity {
+    pub action_id: ActionId,
+    pub turn_id: String,
+    pub fingerprint: String,
+}
+pub(crate) struct Request {
+    pub identity: Identity,
+    pub target: Target,
+    pub selected: Option<SelectedTarget>,
+    pub text: Option<String>,
+}
+
+impl super::Control {
+    pub(crate) async fn delegate(
+        &self,
+        input: ActInput,
+        selected: Option<SelectedTarget>,
+    ) -> Result<ActResult> {
+        if !matches!(input.proposal, Proposal::Route(_)) {
+            return Err(failure(
+                Code::OperationConflict,
+                "Not an ordinary WorkHub delegation",
+            ));
+        }
+        let identity = Identity {
+            action_id: input.action_id.clone(),
+            turn_id: input.turn_id.clone(),
+            fingerprint: fingerprint(&input)?,
+        };
+        if let Some(delegation) = self
+            .commands
+            .delegation(self.caller.clone(), identity.clone())
+            .await?
+        {
+            return Ok(receipt(&delegation));
+        }
+        let target = self.prepare_target(&input, selected.as_ref()).await?;
+        let delegation = self
+            .commands
+            .delegate(
+                self.caller.clone(),
+                Request {
+                    identity,
+                    target,
+                    selected,
+                    text: input.delegation_text,
+                },
+            )
+            .await?;
+        Ok(receipt(&delegation))
+    }
+}
+
+fn receipt(delegation: &Delegation) -> ActResult {
+    let target_session_id = delegation.target.session_id.clone();
+    let target_turn_id = delegation.target.turn_id.clone();
+    match delegation.kind {
+        DelegationKind::Existing => ActResult::DelegateExisting {
+            target_session_id,
+            target_turn_id,
+            steered: delegation.delivery.is_steering(),
+        },
+        DelegationKind::Created => ActResult::CreateNew {
+            target_session_id,
+            target_turn_id,
+        },
+    }
+}
diff --git a/crates/runtime-host/src/plugins/workhub/selection.rs b/crates/runtime-host/src/plugins/workhub/selection.rs
index 71e0dff72a..a4c35c30f4 100644
--- a/crates/runtime-host/src/plugins/workhub/selection.rs
+++ b/crates/runtime-host/src/plugins/workhub/selection.rs
@@ -31,6 +31,7 @@ use maka_runtime::{
 };
 pub(crate) mod offer;
 
+#[derive(Clone)]
 pub(crate) struct SelectedTarget {
     pub invocation: Invocation,
     pub candidate_ref: String,
diff --git a/crates/runtime-host/src/server.rs b/crates/runtime-host/src/server.rs
index 39ac742941..ebe5a65bb5 100644
--- a/crates/runtime-host/src/server.rs
+++ b/crates/runtime-host/src/server.rs
@@ -43,6 +43,7 @@ mod outbound;
 mod plugin_remote;
 mod projects;
 mod scheduler;
+pub(crate) use projects::Usage as ProjectUsage;
 pub(crate) use projects::resolve_record as resolve_project_workspace;
 mod registration;
 mod resources;
@@ -124,13 +125,14 @@ pub struct Host {
     interactions: Arc<interactions::Interactions>,
     uploads: artifacts::Uploads,
     project_directories: projects::Directories,
+    project_usage: ProjectUsage,
     // Log connections close before root authority is released.
     log: Arc<EventLog>,
     configuration: Arc<ConfigurationStore>,
     connection_effects: connection_effects::ConnectionEffects,
     oauth: oauth::Coordinator,
     changes: broadcast::Sender<serde_json::Value>,
-    change_revision: AtomicU64,
+    change_revision: Arc<AtomicU64>,
     access_revocations: broadcast::Sender<String>,
     access_changed: tokio::sync::Notify,
     session_catalog: Arc<catalog_feed::CatalogFeed>,
@@ -214,6 +216,9 @@ impl Host {
         let epoch = Uuid::new_v4().to_string();
         log.begin_message_epoch(&epoch).await?;
         let changes = broadcast::channel(64).0;
+        let change_revision = Arc::new(AtomicU64::new(0));
+        let project_usage =
+            ProjectUsage::new(log.clone(), changes.clone(), change_revision.clone());
         let session_catalog = Arc::new(catalog_feed::CatalogFeed::new(
             *log.subscribe_commits().borrow(),
         ));
@@ -259,9 +264,10 @@ impl Host {
         crate::plugins::workhub::install(
             &mut setup,
             &executions.plugin_catalog,
-            Arc::new(crate::execution::WorkHubCommands(Arc::downgrade(
+            Arc::new(crate::execution::WorkHubCommands::new(
                 &executions,
-            ))),
+                project_usage.clone(),
+            )),
         )?;
         crate::plugins::scheduler::install(
             &mut setup,
@@ -326,12 +332,13 @@ impl Host {
             interactions,
             uploads: Default::default(),
             project_directories,
+            project_usage,
             log,
             configuration,
             connection_effects: connection_effects::ConnectionEffects::default(),
             oauth: oauth::Coordinator::default(),
             changes,
-            change_revision: AtomicU64::new(0),
+            change_revision,
             access_revocations: broadcast::channel(64).0,
             access_changed: tokio::sync::Notify::new(),
             session_catalog,
diff --git a/crates/runtime-host/src/server/projects.rs b/crates/runtime-host/src/server/projects.rs
index 656cc6a007..e28948bb98 100644
--- a/crates/runtime-host/src/server/projects.rs
+++ b/crates/runtime-host/src/server/projects.rs
@@ -19,6 +19,7 @@
 
 mod directories;
 mod projection;
+mod usage;
 use super::{Host, HostError};
 use crate::session::SessionConfiguration;
 pub(super) use directories::Directories;
@@ -32,6 +33,7 @@ use maka_protocol::session::{WorkspaceProjection, WorkspaceTarget};
 use maka_protocol::{Operation, OperationError, OperationErrorCode as Code, Outcome, project::*};
 use serde_json::{Value, json};
 use std::{path::Path, sync::atomic::Ordering};
+pub(crate) use usage::Usage;
 
 type Result<T> = std::result::Result<T, OperationError>;
 
@@ -235,18 +237,7 @@ pub(crate) async fn resolve_record(
 }
 
 pub(super) async fn record_usage(host: &Host, workspace: &WorkspaceProjection) -> Result<()> {
-    if let WorkspaceTarget::Project { project_id } = &workspace.target {
-        host.log
-            .touch_project(
-                project_id,
-                &workspace.host_cwd,
-                super::configuration::now().map_err(internal)?,
-            )
-            .await
-            .map_err(stored)?;
-        publish(host);
-    }
-    Ok(())
+    host.project_usage.record(workspace).await
 }
 
 fn publish(host: &Host) {
diff --git a/crates/runtime-host/src/server/projects/usage.rs b/crates/runtime-host/src/server/projects/usage.rs
new file mode 100644
index 0000000000..c4a4d14d1b
--- /dev/null
+++ b/crates/runtime-host/src/server/projects/usage.rs
@@ -0,0 +1,73 @@
+/*
+ * 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::{internal, stored};
+use maka_event_log::EventLog;
+use maka_protocol::{
+    OperationError,
+    session::{WorkspaceProjection, WorkspaceTarget},
+};
+use serde_json::{Value, json};
+use std::sync::{
+    Arc,
+    atomic::{AtomicU64, Ordering},
+};
+use tokio::sync::broadcast;
+
+/// Project usage and its notification share the Host's existing catalog revision.
+#[derive(Clone)]
+pub(crate) struct Usage {
+    log: Arc<EventLog>,
+    changes: broadcast::Sender<Value>,
+    revision: Arc<AtomicU64>,
+}
+impl Usage {
+    pub(crate) fn new(
+        log: Arc<EventLog>,
+        changes: broadcast::Sender<Value>,
+        revision: Arc<AtomicU64>,
+    ) -> Self {
+        Self {
+            log,
+            changes,
+            revision,
+        }
+    }
+
+    pub(crate) async fn record(
+        &self,
+        workspace: &WorkspaceProjection,
+    ) -> Result<(), OperationError> {
+        if let WorkspaceTarget::Project { project_id } = &workspace.target {
+            self.log
+                .touch_project(
+                    project_id,
+                    &workspace.host_cwd,
+                    super::super::configuration::now().map_err(internal)?,
+                )
+                .await
+                .map_err(stored)?;
+            let revision = self.revision.fetch_add(1, Ordering::SeqCst) + 1;
+            let _ = self
+                .changes
+                .send(json!({"kind":"project.catalog.changed", "revision":revision}));
+        }
+        Ok(())
+    }
+}
diff --git a/crates/runtime-host/src/server/workhub/action.rs b/crates/runtime-host/src/server/workhub/action.rs
index 1c6a779f68..65606b1786 100644
--- a/crates/runtime-host/src/server/workhub/action.rs
+++ b/crates/runtime-host/src/server/workhub/action.rs
@@ -17,16 +17,11 @@
  * under the License.
  */
 
-use super::{Host, failure, record, sessions};
+use super::{Host, failure, sessions};
 use maka_protocol::{
     OperationError, OperationErrorCode as Code,
     workhub::{ActInput, ActResult},
 };
-use maka_runtime::{
-    event::{CommitError, EventWrite, Fact, Invocation, RuntimeEvent},
-    input::InvocationInput,
-    workhub::{COORDINATION_SESSION_ID, Delegation, DelegationDelivery, DelegationKind},
-};
 use std::sync::Arc;
 use uuid::Uuid;
 
@@ -65,7 +60,9 @@ pub(super) async fn act(
                 super::control(host)?.value.stop(input).await
             }
         },
-        maka_protocol::workhub::Proposal::Route(_) => admit(host, input, None).await,
+        maka_protocol::workhub::Proposal::Route(_) => {
+            super::control(host)?.value.delegate(input, None).await
+        }
     }
 }
 
@@ -74,210 +71,10 @@ pub(super) async fn selected(
     input: ActInput,
     selected: &super::selection::SelectedTarget,
 ) -> Result<ActResult, OperationError> {
-    admit(host, input, Some(selected)).await
-}
-
-async fn admit(
-    host: &Arc<Host>,
-    input: ActInput,
-    selected: Option<&super::selection::SelectedTarget>,
-) -> Result<ActResult, OperationError> {
-    let fingerprint = fingerprint(&input)?;
-    let mut prepared = None;
-    let mut provider = None;
-    loop {
-        let mut admission = Some(host.executions.lock_admission().await);
-        if host
-            .log
-            .workhub_stop(&input.action_id)
-            .await
-            .map_err(sessions::stored)?
-            .is_some()
-            || host
-                .log
-                .workhub_correction(&input.action_id)
-                .await
-                .map_err(sessions::stored)?
-                .is_some()
-        {
-            return Err(failure(
-                Code::OperationConflict,
-                "WorkHub action already belongs to a control operation",
-            ));
-        }
-        // Receipt authority survives source termination, model removal and target changes.
-        if let Some(stored) = host
-            .log
-            .workhub_action(&input.action_id)
-            .await
-            .map_err(sessions::stored)?
-        {
-            let Fact::WorkhubDelegated { delegation } = stored.event.fact else {
-                return Err(failure(
-                    Code::OperationConflict,
-                    "WorkHub action belongs to another operation",
-                ));
-            };
-            if stored.event.invocation.session_id != COORDINATION_SESSION_ID
-                || stored.event.invocation.turn_id != input.turn_id
-                || delegation.request_fingerprint != fingerprint
-            {
-                return Err(failure(
-                    Code::OperationConflict,
-                    "WorkHub action belongs to another request",
-                ));
-            }
-            return Ok(receipt(&delegation));
-        }
-        let control = match &provider {
-            Some(control) => control,
-            None => provider.insert(super::control(host)?),
-        };
-        let _call = control
-            .admit()
-            .map_err(|error| failure(Code::OperationUnavailable, error.to_string()))?;
-        if host.draining.is_cancelled() {
-            return Err(failure(Code::HostDraining, "Host is draining"));
-        }
-        record(host)
-            .await?
-            .ok_or_else(|| failure(Code::NotFound, "WorkHub Session has not been resolved"))?;
-        let source = host.executions.workhub_source(&input.turn_id).await?;
-        if selected.is_some_and(|selected| selected.invocation != source.invocation) {
-            return Err(failure(
-                Code::OperationConflict,
-                "The selecting Run is no longer active",
-            ));
-        }
-        let InvocationInput::Message { content, .. } = source.root_input() else {
-            return Err(failure(
-                Code::OperationConflict,
-                "WorkHub action requires a user message",
-            ));
-        };
-        let Some(target) = prepared.take() else {
-            drop(admission.take());
-            prepared = Some(control.value.prepare_target(&input, selected).await?);
-            continue;
-        };
-        target::validate(host, &target).await?;
-        if let Some(selected) = selected {
-            crate::plugins::workhub::target::validate_selection(selected)?;
-        }
-        let owner = host.executions.active_session_owner(target.id());
-        let delivery = match (&target, &owner) {
-            (
-                target::Target::Existing {
-                    configuration_digest,
-                    ..
-                },
-                Some(_),
-            ) => DelegationDelivery::Steering {
-                configuration_digest: configuration_digest.clone(),
-            },
-            (_, None) => DelegationDelivery::NewTurn,
-            (target::Target::Created { .. }, Some(_)) => {
-                return Err(failure(
-                    Code::OperationConflict,
-                    "Created target already has an execution owner",
-                ));
-            }
-        };
-        let delegation = Delegation {
-            kind: target.kind(),
-            description: Some(target.description()),
-            delivery,
-            action_id: input.action_id,
-            request_fingerprint: fingerprint,
-            source_message_event_id: source.root_opening_event_id().to_owned(),
-            target: owner.unwrap_or_else(|| Invocation {
-                session_id: target.id().to_owned(),
-                turn_id: Uuid::new_v4().to_string(),
-                run_id: Uuid::new_v4().to_string(),
-                invocation_id: Uuid::new_v4().to_string(),
-            }),
-            target_revision: target.revision(),
-            delegation_text: input
-                .delegation_text
-                .unwrap_or_else(|| content.text.clone()),
-        };
-        delegation
-            .message(content)
-            .map_err(|reason| failure(Code::OperationUnavailable, reason))?;
-        let result = receipt(&delegation);
-        let action = EventWrite::plain(RuntimeEvent::new(
-            source.invocation,
-            Fact::WorkhubDelegated {
-                delegation: Box::new(delegation),
-            },
-        ))
-        .map_err(|error| failure(Code::InternalFailure, error.to_string()))?;
-        let committed = match &target {
-            target::Target::Created { creation, .. } => host
-                .log
-                .create_workhub_session(&action, &creation.configuration)
-                .await
-                .map_err(|error| match error {
-                    maka_event_log::StoreError::CommitUnknown(_)
-                    | maka_event_log::StoreError::OperationUnknown => {
-                        CommitError::OutcomeUnknown(error.to_string())
-                    }
-                    other => CommitError::Rejected(other.to_string()),
-                }),
-            target::Target::Existing { .. } => host.log.append(&action).await,
-        };
-        if let Err(error) = committed {
-            return Err(match error {
-                CommitError::OutcomeUnknown(reason) => {
-                    host.executions.begin_drain();
-                    failure(Code::CommitOutcomeUnknown, reason)
-                }
-                CommitError::Rejected(reason) => {
-                    let current = host
-                        .log
-                        .get_session::<crate::session::SessionConfiguration>(target.id())
-                        .await
-                        .map_err(sessions::stored)?;
-                    let code = if let target::Target::Existing {
-                        configuration_digest,
-                        ..
-                    } = &target
-                        && current.is_none_or(|record| {
-                            record.archived || record.configuration_digest != *configuration_digest
-                        }) {
-                        Code::CandidateSetStale
-                    } else {
-                        Code::OperationConflict
-                    };
-                    failure(code, reason)
-                }
-            });
-        }
-        host.executions
-            .dispatch_pending(target.id(), &mut admission)
-            .await?;
-        return Ok(result);
-    }
-}
-
-fn receipt(delegation: &Delegation) -> ActResult {
-    let target_session_id = delegation.target.session_id.clone();
-    let target_turn_id = delegation.target.turn_id.clone();
-    match delegation.kind {
-        DelegationKind::Existing => ActResult::DelegateExisting {
-            target_session_id,
-            target_turn_id,
-            steered: delegation.delivery.is_steering(),
-        },
-        DelegationKind::Created => ActResult::CreateNew {
-            target_session_id,
-            target_turn_id,
-        },
-    }
-}
-
-fn fingerprint(input: &ActInput) -> Result<String, OperationError> {
-    crate::plugins::workhub::control::fingerprint(input)
+    super::control(host)?
+        .value
+        .delegate(input, Some(selected.clone()))
+        .await
 }
 
 fn stored(host: &Host, error: maka_event_log::StoreError) -> OperationError {
diff --git a/docs/rust-parity.md b/docs/rust-parity.md
index f91fcf69e6..84839d57e1 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 prompt/client-tool policy, Stop/Resume request/results, candidate filtering/references, selection forms/answers and target/default choices. Host retains exact-owner cancellation, bounded snapshot reads, canonical forms and final admission. Workflow orchestration, correction, recovery and UI remain to migrate. | Move the remaining workflow, queries, associations, selection, correction/resume policy and UI into the built-in domain. Host commands retain ex [...]
+| **WorkHub:** `maka.workhub` owns prompt/client-tool policy, ordinary delegation, Stop/Resume request/results, candidate filtering/references, selection forms/answers and target/default choices. Target preparation runs outside admission; Host commands recheck the instance and canonical source before atomic delivery. | Move correction/recovery, coordinator workflows, native tools, Remote and actual Desktop UI into the built-in domain. Host retains exact-message targeting, canonical forms [...]
 | **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 e1fcf7e21a..70445b5464 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 保留精确 owner 取消、有界快照读取、规范表单及最终准入;流程编排、纠正、恢复和 UI 仍待迁移。 | 剩余流程、查询、关联、多选、纠正/resume 策略及 UI 迁入内置领域;Host 命令保留精确消息定位、原子准入/取消/回执及结算。复用领域记录与事务,不能把原子纠正拆成插件和 Host 各自写入。 |
+| **WorkHub:**`maka.workhub` 拥有提示词/客户端工具策略、普通委派、Stop/Resume 请求/结果、候选筛选/引用、选择表单/答案及目标/默认值决策。目标准备在准入锁外运行;Host 命令在原子投递前复查实例及 canonical source。 | 纠正/恢复、协调会话流程、原生工具、Remote 及实际 Desktop UI 迁入内置领域。Host 保留精确消息定位、规范表单、原子准入/取消/回执及结算;不能把纠正拆成插件和 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。 |


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org