Skip to main content

wcode_protocol/
registry.rs

1//! In-process address book + router — the A2A bus (§8 #3, §10.1).
2//!
3//! Two pieces: an **address book** (`SessionId` → `Backend`: an in-process
4//! handle or a remote `Client`) and an **ownership** map (the `report_back_to`
5//! **ownership** map (the `report_back_to` edge: worker → orchestrator). Routing
6//! a message checks the **permitted set** — an orchestrator reaches its workers,
7//! a worker its orchestrator, and nothing else (v1 star, §10.1) — then delivers
8//! through the target's handle carrying the sender, so the recipient can
9//! attribute it (`AgentEvent::MessageReceived { from }`).
10//!
11//! Addressing lives *here*, one layer above the kernel: a [`Request`] is
12//! target-side and names no peer; the router turns a `to` address into a mailbox
13//! given the sender's scope. The future **group** mode widens only the permitted
14//! set (§10.1) — nothing else here changes.
15
16use std::collections::HashMap;
17use std::sync::{Arc, Mutex};
18
19use tokio::sync::watch;
20use wcode_harness::actor::SessionHandle;
21use wcode_harness::protocol::{MemberState, Request, SessionId};
22
23use crate::backend::Backend;
24#[cfg(unix)]
25use crate::client::Client;
26
27/// Why a message could not be routed (§10.1 enforcement).
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum RouteError {
30    /// No peer is registered at that address.
31    Unknown(SessionId),
32    /// The sender may not address that peer — outside the permitted set.
33    NotPermitted { from: SessionId, to: SessionId },
34    /// The target's session has shut down (its actor ended).
35    Closed,
36}
37
38impl std::fmt::Display for RouteError {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        match self {
41            RouteError::Unknown(id) => write!(f, "no peer at {id}"),
42            RouteError::NotPermitted { from, to } => {
43                write!(f, "{from} may not address {to}")
44            }
45            RouteError::Closed => f.write_str("the target session is closed"),
46        }
47    }
48}
49
50impl std::error::Error for RouteError {}
51
52/// An in-process address book and router over session mailboxes.
53///
54/// Cloneable and shared: every clone refers to the same registry. There is no
55/// automatic deregistration — a peer that shuts down stays listed, and a delivery
56/// to it fails with [`RouteError::Closed`] (a sweep can come later; §10.1 keeps
57/// this minimal).
58#[derive(Clone, Default)]
59pub struct Registry {
60    inner: Arc<Inner>,
61}
62
63struct Inner {
64    peers: Mutex<HashMap<SessionId, Backend>>,
65    owners: Mutex<HashMap<SessionId, SessionId>>,
66    /// Each session's effective model id (see [`Registry::set_model`]), so a
67    /// socket server can name a member's model in the roster it pushes.
68    models: Mutex<HashMap<SessionId, String>>,
69    /// Server-side liveness per session, mirrored from the actor's `watch` cell
70    /// so the roster push (and the `Sessions` reply) can name it — the twin of
71    /// `models`. Cheap: one entry per served session. `Idle` for a session the
72    /// registry never watched, or an older peer (a `state`-less `SessionInfo`).
73    states: Mutex<HashMap<SessionId, MemberState>>,
74    /// Each session's effective effort (see [`Registry::set_effort`]).
75    efforts: Mutex<HashMap<SessionId, Option<String>>>,
76    /// Each session's context window in tokens (see [`Registry::set_context_window`]).
77    windows: Mutex<HashMap<SessionId, u64>>,
78    /// The provider's advertised catalog — ONE value for the whole registry
79    /// (connection), not per session (see [`Registry::set_models`]).
80    catalog: Mutex<Vec<String>>,
81    /// The live **local** roster (see [`Registry::locals`]), refreshed on every
82    /// `register`/`register_remote` so a socket server can fan a session that
83    /// appears after it started. Read via [`Registry::subscribe`].
84    roster: watch::Sender<Vec<(SessionId, SessionHandle)>>,
85}
86
87impl Default for Inner {
88    fn default() -> Self {
89        Self {
90            peers: Mutex::new(HashMap::new()),
91            owners: Mutex::new(HashMap::new()),
92            models: Mutex::new(HashMap::new()),
93            states: Mutex::new(HashMap::new()),
94            efforts: Mutex::new(HashMap::new()),
95            windows: Mutex::new(HashMap::new()),
96            catalog: Mutex::new(Vec::new()),
97            roster: watch::channel(Vec::new()).0,
98        }
99    }
100}
101
102impl Registry {
103    pub fn new() -> Self {
104        Self::default()
105    }
106
107    /// Register a peer's mailbox at `id`; re-registering replaces it.
108    pub fn register(&self, id: SessionId, handle: SessionHandle) {
109        // Mirror the session's run-state into the roster: the registry keeps
110        // NO back-reference into the kernel (layering) — it watches the
111        // handle's `state_rx()` and re-publishes on every transition, so the
112        // socket server pushes the change without polling. One task per served
113        // session (a handful); it ends when the session drops.
114        let mut state_rx = handle.state_rx();
115        self.inner
116            .peers
117            .lock()
118            .unwrap()
119            .insert(id.clone(), Backend::Local(handle));
120        let registry = self.clone();
121        tokio::spawn(async move {
122            while state_rx.changed().await.is_ok() {
123                registry.set_state(id.clone(), *state_rx.borrow_and_update());
124            }
125        });
126        self.refresh_roster();
127    }
128
129    /// Register a peer reachable **across a socket** — a served session this
130    /// process can message, and which can reach back if it knows us (§8, S4-4).
131    #[cfg(unix)]
132    pub fn register_remote(&self, id: SessionId, client: Client) {
133        self.inner
134            .peers
135            .lock()
136            .unwrap()
137            .insert(id, Backend::Remote(client.clone()));
138        // Remote liveness rides the peer's own roster push: a served peer
139        // stamps each `SessionInfo` with a `state`, so mirroring the remote
140        // client's roster into `states` makes `state_of` reflect a remote
141        // member with no back-channel of our own.
142        let registry = self.clone();
143        let mut roster = client.subscribe_roster();
144        tokio::spawn(async move {
145            while roster.changed().await.is_ok() {
146                let sessions = roster.borrow_and_update().clone();
147                for info in &sessions {
148                    registry.set_state(info.id.clone(), info.state);
149                }
150            }
151        });
152        self.refresh_roster();
153    }
154
155    /// The **local** peers — the in-process handles this registry holds, sorted
156    /// by address for a deterministic roster. A socket server serves exactly
157    /// these ([`Self::register`]ed sessions: the root and its team); a peer
158    /// reached across a socket ([`Self::register_remote`]) is not a local handle
159    /// and is omitted, so it is never re-served.
160    pub fn locals(&self) -> Vec<(SessionId, SessionHandle)> {
161        let mut locals: Vec<(SessionId, SessionHandle)> = self
162            .inner
163            .peers
164            .lock()
165            .unwrap()
166            .iter()
167            .filter_map(|(id, backend)| match backend {
168                Backend::Local(handle) => Some((id.clone(), handle.clone())),
169                #[cfg(unix)]
170                Backend::Remote(_) => None,
171            })
172            .collect();
173        locals.sort_by(|a, b| a.0.as_str().cmp(b.0.as_str()));
174        locals
175    }
176
177    /// A live view of [`Self::locals`]: a receiver **seeded** with the current
178    /// local roster and updated whenever it changes (a session is
179    /// `register`ed). A socket server serves it, so a session that appears
180    /// *after* the server started is still reachable — see [`crate::serve`].
181    pub fn subscribe(&self) -> watch::Receiver<Vec<(SessionId, SessionHandle)>> {
182        self.inner.roster.subscribe()
183    }
184
185    /// Recompute the local roster and publish it to every [`Self::subscribe`]r.
186    /// `send_replace` stores the value even when nobody is watching yet, so a
187    /// session registered *before* the first subscriber — a `[team]` member that
188    /// starts ahead of the socket server — still seeds a later [`Self::subscribe`].
189    /// (`watch::Sender::send` would drop it: it is a no-op with no receivers.)
190    fn refresh_roster(&self) {
191        let _ = self.inner.roster.send_replace(self.locals());
192    }
193
194    /// Whether a peer is already registered at `id`. A plain existence probe —
195    /// unlike [`Self::resolve`], it needs no `from` and enforces no permitted set.
196    pub fn contains(&self, id: &SessionId) -> bool {
197        self.inner.peers.lock().unwrap().contains_key(id)
198    }
199
200    /// Record the `report_back_to` edge: `worker` reports to `owner` (its
201    /// orchestrator). This is the whole ownership tree — a star in v1.
202    pub fn set_owner(&self, worker: SessionId, owner: SessionId) {
203        self.inner.owners.lock().unwrap().insert(worker, owner);
204    }
205
206    /// Record a session's effective model id, so the roster this registry feeds
207    /// (a socket server's `Sessions` push) can name it. Mirrors [`Self::set_owner`]
208    /// — a plain metadata write, no effect on routing; it RE-PUBLISHES the roster.
209    pub fn set_model(&self, id: SessionId, model: impl Into<String>) {
210        self.inner.models.lock().unwrap().insert(id, model.into());
211        self.refresh_roster();
212    }
213
214    /// Mirror a session's run-state. Called by the per-session watcher
215    /// `register` spawns — NOT by the actor, so the registry keeps NO
216    /// back-reference into the kernel (layering). Re-publishes the roster, so
217    /// the socket server pushes the change without polling.
218    pub fn set_state(&self, id: SessionId, state: MemberState) {
219        self.inner.states.lock().unwrap().insert(id, state);
220        self.refresh_roster();
221    }
222
223    /// The state last mirrored for `id`; `Idle` when unknown (a remote peer the
224    /// registry never watched, or an older server).
225    pub fn state_of(&self, id: &SessionId) -> MemberState {
226        *self
227            .inner
228            .states
229            .lock()
230            .unwrap()
231            .get(id)
232            .unwrap_or(&MemberState::Idle)
233    }
234    /// The model recorded for `id`, if any ([`Self::set_model`]). `None` for a
235    /// peer registered without one (e.g. a remote reached across a socket).
236    pub fn model_of(&self, id: &SessionId) -> Option<String> {
237        self.inner.models.lock().unwrap().get(id).cloned()
238    }
239
240    /// Record a session's effective effort. A metadata write, no routing effect —
241    /// but it RE-PUBLISHES the roster, so a socket push reflects it.
242    pub fn set_effort(&self, id: SessionId, effort: Option<String>) {
243        self.inner.efforts.lock().unwrap().insert(id, effort);
244        self.refresh_roster();
245    }
246
247    /// The effort last recorded for `id`; `None` when unset OR cleared.
248    pub fn effort_of(&self, id: &SessionId) -> Option<String> {
249        self.inner.efforts.lock().unwrap().get(id).cloned().flatten()
250    }
251
252    /// Record a session's context window in tokens. RE-PUBLISHES the roster.
253    pub fn set_context_window(&self, id: SessionId, window: u64) {
254        self.inner.windows.lock().unwrap().insert(id, window);
255        self.refresh_roster();
256    }
257
258    /// The window last recorded for `id`; `None` when unknown.
259    pub fn window_of(&self, id: &SessionId) -> Option<u64> {
260        self.inner.windows.lock().unwrap().get(id).copied()
261    }
262
263    /// Record the connection's model catalog. Does NOT re-publish — the catalog
264    /// names no session, so it is not roster data.
265    pub fn set_models(&self, models: Vec<String>) {
266        *self.inner.catalog.lock().unwrap() = models;
267    }
268
269    /// The recorded catalog (empty when unset — `ListModels` then answers `[]`).
270    pub fn models(&self) -> Vec<String> {
271        self.inner.catalog.lock().unwrap().clone()
272    }
273
274    /// Whether `from` may address `to` (§10.1): the pair must be joined by an
275    /// ownership edge in either direction — an orchestrator and one of its
276    /// workers. There are no lateral (worker → worker) edges.
277    pub fn permitted(&self, from: &SessionId, to: &SessionId) -> bool {
278        let owners = self.inner.owners.lock().unwrap();
279        owners.get(from) == Some(to) || owners.get(to) == Some(from)
280    }
281
282    /// Resolve `to` for `from`, enforcing the permitted set. Fails
283    /// [`RouteError::Unknown`] if no peer is registered, else
284    /// [`RouteError::NotPermitted`] if the sender is out of scope.
285    pub fn resolve(&self, from: &SessionId, to: &SessionId) -> Result<Backend, RouteError> {
286        let endpoint = self
287            .inner
288            .peers
289            .lock()
290            .unwrap()
291            .get(to)
292            .cloned()
293            .ok_or_else(|| RouteError::Unknown(to.clone()))?;
294        if !self.permitted(from, to) {
295            return Err(RouteError::NotPermitted {
296                from: from.clone(),
297                to: to.clone(),
298            });
299        }
300        Ok(endpoint)
301    }
302
303    /// Deliver a target-side `request` to `to`, attributed to `from`. The
304    /// recipient sees it as if `from` had sent it directly
305    /// ([`SessionHandle::send_from`]).
306    pub fn deliver(
307        &self,
308        from: &SessionId,
309        to: &SessionId,
310        request: Request,
311    ) -> Result<(), RouteError> {
312        let endpoint = self.resolve(from, to)?;
313        endpoint
314            .send_from(from.clone(), request)
315            .map_err(|_| RouteError::Closed)
316    }
317}
318
319#[cfg(test)]
320mod tests {
321    use super::*;
322    use std::time::Duration;
323
324    use wcode_harness::actor::SessionActor;
325    use wcode_harness::agent::{Agent, AgentConfig};
326    use wcode_harness::compaction::CompactionPolicy;
327    use wcode_harness::event::{AgentEvent, LlmStreamEvent};
328    use wcode_harness::hooks::HooksSet;
329    use wcode_harness::loop_::DEFAULT_MAX_TURNS;
330    use wcode_harness::streamfn::{LlmOpts, LlmStream, StreamFn};
331
332    use futures::StreamExt as _;
333
334    /// A live session whose model never streams — enough for routing tests (the
335    /// messages we send are `Notify`, which run no turn).
336    fn session() -> SessionHandle {
337        session_with(Arc::new(|_ctx, _sys, _tools, _opts| {
338            Box::pin(futures::stream::empty()) as LlmStream
339        }))
340    }
341
342    /// A session over a caller-supplied stream — for tests that drive a real
343    /// turn (a hang, a stream error), not just routing.
344    fn session_with(stream_fn: StreamFn) -> SessionHandle {
345        SessionActor::spawn(Agent::new(AgentConfig {
346            system: "sys".into(),
347            tools: Vec::new(),
348            llm: LlmOpts {
349                model: "m".into(),
350                ..LlmOpts::default()
351            },
352            stream_fn,
353            hooks: HooksSet::default(),
354            session: None,
355            context: Vec::new(),
356            working_dir: std::path::PathBuf::new(),
357            max_turns: DEFAULT_MAX_TURNS,
358            parallel_tools: true,
359            compaction: CompactionPolicy::default(),
360            plan_mode: wcode_harness::hooks::PlanModeHandle::new(),
361        }))
362    }
363
364    async fn next_message(
365        rx: &mut tokio::sync::broadcast::Receiver<AgentEvent>,
366    ) -> AgentEvent {
367        tokio::time::timeout(Duration::from_secs(2), rx.recv())
368            .await
369            .expect("an event")
370            .expect("open")
371    }
372
373    #[tokio::test]
374    async fn orchestrator_and_worker_reach_each_other() {
375        let reg = Registry::new();
376        let orch = SessionId::agent("orch");
377        let worker = SessionId::agent("w1");
378        let h_orch = session();
379        let h_worker = session();
380        reg.register(orch.clone(), h_orch.clone());
381        reg.register(worker.clone(), h_worker.clone());
382        reg.set_owner(worker.clone(), orch.clone());
383
384        // Orchestrator → worker: the worker receives it, attributed to the orch.
385        let mut rx = h_worker.subscribe();
386        reg.deliver(
387            &orch,
388            &worker,
389            Request::Notify {
390                content: "do the thing".into(),
391            },
392        )
393        .unwrap();
394        match next_message(&mut rx).await {
395            AgentEvent::MessageReceived { from, content } => {
396                assert_eq!(from, orch);
397                assert_eq!(content, "do the thing");
398            }
399            other => panic!("expected MessageReceived, got {other:?}"),
400        }
401
402        // Worker → orchestrator (report back): permitted.
403        let mut rx = h_orch.subscribe();
404        reg.deliver(
405            &worker,
406            &orch,
407            Request::Notify {
408                content: "done".into(),
409            },
410        )
411        .unwrap();
412        match next_message(&mut rx).await {
413            AgentEvent::MessageReceived { from, content } => {
414                assert_eq!(from, worker);
415                assert_eq!(content, "done");
416            }
417            other => panic!("expected MessageReceived, got {other:?}"),
418        }
419    }
420
421    #[tokio::test]
422    async fn lateral_message_between_workers_is_refused() {
423        let reg = Registry::new();
424        let orch = SessionId::agent("orch");
425        let w1 = SessionId::agent("w1");
426        let w2 = SessionId::agent("w2");
427        for id in [&orch, &w1, &w2] {
428            reg.register(id.clone(), session());
429        }
430        reg.set_owner(w1.clone(), orch.clone());
431        reg.set_owner(w2.clone(), orch.clone());
432
433        assert_eq!(
434            reg.deliver(
435                &w1,
436                &w2,
437                Request::Notify {
438                    content: "psst".into()
439                }
440            ),
441            Err(RouteError::NotPermitted {
442                from: w1.clone(),
443                to: w2.clone()
444            })
445        );
446        assert!(!reg.permitted(&w1, &w2));
447    }
448
449    #[tokio::test]
450    async fn unknown_target_is_reported() {
451        let reg = Registry::new();
452        let orch = SessionId::agent("orch");
453        reg.register(orch.clone(), session());
454
455        let ghost = SessionId::agent("ghost");
456        assert_eq!(
457            reg.deliver(&orch, &ghost, Request::Notify { content: "?".into() }),
458            Err(RouteError::Unknown(ghost))
459        );
460    }
461
462    #[tokio::test]
463    async fn locals_lists_only_local_peers_sorted_by_id() {
464        let reg = Registry::new();
465        let b = SessionId::agent("b");
466        let a = SessionId::agent("a");
467        reg.register(b.clone(), session());
468        reg.register(a.clone(), session());
469        #[cfg(unix)]
470        reg.register_remote(
471            SessionId::agent("remote"),
472            Client::lazy(std::path::Path::new("/nonexistent-t2.sock")),
473        );
474        let ids: Vec<SessionId> = reg.locals().into_iter().map(|(id, _)| id).collect();
475        assert_eq!(ids, vec![a, b]);
476    }
477
478    #[tokio::test]
479    async fn set_model_is_read_back_per_id() {
480        let reg = Registry::new();
481        let w1 = SessionId::agent("w1");
482        let ghost = SessionId::agent("ghost");
483        assert_eq!(reg.model_of(&w1), None, "unset id has no model");
484        reg.set_model(w1.clone(), "m1");
485        assert_eq!(reg.model_of(&w1).as_deref(), Some("m1"));
486        assert_eq!(reg.model_of(&ghost), None);
487    }
488
489    /// Await the next roster change, failing the test if it never comes.
490    async fn next_change(rx: &mut watch::Receiver<Vec<(SessionId, SessionHandle)>>) {
491        tokio::time::timeout(Duration::from_secs(2), rx.changed())
492            .await
493            .expect("a roster change")
494            .expect("open");
495    }
496
497    #[tokio::test]
498    async fn set_effort_window_catalog_are_read_back() {
499        let reg = Registry::new();
500        let w1 = SessionId::agent("w1");
501        assert_eq!(reg.effort_of(&w1), None);
502        reg.set_effort(w1.clone(), Some("high".into()));
503        assert_eq!(reg.effort_of(&w1).as_deref(), Some("high"));
504        reg.set_effort(w1.clone(), None); // cleared == None
505        assert_eq!(reg.effort_of(&w1), None);
506
507        assert_eq!(reg.window_of(&w1), None);
508        reg.set_context_window(w1.clone(), 200_000);
509        assert_eq!(reg.window_of(&w1), Some(200_000));
510
511        assert_eq!(reg.models(), Vec::<String>::new());
512        reg.set_models(vec!["m1".into(), "m2".into()]);
513        assert_eq!(reg.models(), vec!["m1".to_string(), "m2".to_string()]);
514    }
515
516    #[tokio::test]
517    async fn set_model_and_set_effort_republish_the_roster() {
518        let reg = Registry::new();
519        let w1 = SessionId::agent("w1");
520        let mut rx = reg.subscribe();
521        reg.set_model(w1.clone(), "m1"); // NEW: re-publishes
522        next_change(&mut rx).await;
523        reg.set_effort(w1.clone(), Some("low".into()));
524        next_change(&mut rx).await;
525        reg.set_context_window(w1.clone(), 1_000);
526        next_change(&mut rx).await;
527    }
528
529    #[tokio::test]
530    async fn set_state_is_read_back_and_republishes() {
531        let reg = Registry::new();
532        let w1 = SessionId::agent("w1");
533        assert_eq!(reg.state_of(&w1), MemberState::Idle, "unset id reads Idle");
534
535        let mut rx = reg.subscribe();
536        reg.set_state(w1.clone(), MemberState::Running);
537        assert_eq!(reg.state_of(&w1), MemberState::Running);
538        // `set_state` re-publishes, so a subscriber (the server's push) wakes.
539        tokio::time::timeout(Duration::from_secs(2), rx.changed())
540            .await
541            .expect("a roster change")
542            .expect("open");
543    }
544
545    #[tokio::test]
546    async fn register_mirrors_the_actor_state_into_the_roster() {
547        // A stream that emits one delta then hangs: Running is observable until
548        // a cancel ends the run.
549        let stream_fn: StreamFn = Arc::new(|_ctx, _sys, _tools, _opts| {
550            let head =
551                futures::stream::iter(vec![LlmStreamEvent::TextDelta("part".into())]);
552            Box::pin(head.chain(futures::stream::pending())) as LlmStream
553        });
554        let handle = session_with(stream_fn);
555        let reg = Registry::new();
556        let w1 = SessionId::agent("w1");
557        reg.register(w1.clone(), handle.clone());
558        assert_eq!(reg.state_of(&w1), MemberState::Idle, "a virgin session is idle");
559
560        handle.send(Request::Submit { text: "hi".into() }).unwrap();
561        let mut running = false;
562        for _ in 0..500 {
563            if reg.state_of(&w1) == MemberState::Running {
564                running = true;
565                break;
566            }
567            tokio::time::sleep(Duration::from_millis(10)).await;
568        }
569        assert!(running, "the registry mirrors the actor's Running");
570
571        handle.send(Request::Cancel).unwrap();
572        let mut done = false;
573        for _ in 0..500 {
574            if reg.state_of(&w1) == MemberState::Done {
575                done = true;
576                break;
577            }
578            tokio::time::sleep(Duration::from_millis(10)).await;
579        }
580        assert!(done, "the registry mirrors the actor's Done");
581    }
582
583    #[tokio::test]
584    async fn subscribe_yields_the_new_roster_after_register() {
585        let reg = Registry::new();
586        let mut rx = reg.subscribe();
587        let w1 = SessionId::agent("w1");
588        reg.register(w1.clone(), session());
589        rx.changed().await.unwrap();
590        let ids: Vec<SessionId> = rx.borrow().iter().map(|(id, _)| id.clone()).collect();
591        assert_eq!(ids, vec![w1]);
592    }
593
594    #[tokio::test]
595    async fn subscribe_seeds_a_session_registered_before_it() {
596        // A session registered with **no** subscriber in existence must still be
597        // visible to a `subscribe()` that starts later — the `[team]` startup
598        // members, registered before the socket server's first `subscribe()`.
599        // (`watch::Sender::send` drops a value with no receivers; `send_replace`
600        // keeps it, which is what `refresh_roster` relies on.)
601        let reg = Registry::new();
602        let w1 = SessionId::agent("w1");
603        reg.register(w1.clone(), session());
604        let rx = reg.subscribe();
605        let ids: Vec<SessionId> = rx.borrow().iter().map(|(id, _)| id.clone()).collect();
606        assert_eq!(ids, vec![w1]);
607    }
608
609    #[tokio::test]
610    async fn an_unowned_pair_is_not_permitted() {
611        let reg = Registry::new();
612        let a = SessionId::agent("a");
613        let b = SessionId::agent("b");
614        reg.register(a.clone(), session());
615        reg.register(b.clone(), session());
616        // No ownership edge recorded → they may not address each other.
617        assert_eq!(
618            reg.deliver(&a, &b, Request::Notify { content: "?".into() }),
619            Err(RouteError::NotPermitted {
620                from: a.clone(),
621                to: b.clone()
622            })
623        );
624    }
625}