mlua_swarm/service/linker.rs
1//! Spawner Linker — a Service-internal helper that takes a compiled
2//! `CompiledAgentTable` (the base `SpawnerAdapter`), wraps it with
3//! `SpawnerLayer`s via the `LayerRegistry`, and returns the finished
4//! `Arc<dyn SpawnerAdapter>`.
5//!
6//! The old inline loop inside `task_launch.rs` scattered the linker
7//! responsibility across the Service path. This module consolidates
8//! it into one place, so the Blueprint → Compiled → Linked
9//! three-stage split is expressed at the file boundary.
10//!
11//! # Path
12//!
13//! ```text
14//! Compiler.compile(&Blueprint) → CompiledBlueprint { router: Arc<CompiledAgentTable>, ... }
15//! │
16//! │ link(router, blueprint.spawner_hints.layers, engine)
17//! ▼
18//! `Arc<dyn SpawnerAdapter>` (base + every base_factories[*] + every lookup_hint(hints)[*] wrapped)
19//! │
20//! ▼ EngineDispatcher::with_spawner
21//! engine.dispatch_attempt_with(..., &linked) → flow eval
22//! ```
23//!
24//! Unregistered hint keys are **silently skipped** — the
25//! `LayerRegistry` default is lenient, so Blueprints stay portable. A
26//! strict mode is a carry.
27
28use crate::core::engine::Engine;
29use crate::middleware::SpawnerStack;
30use crate::worker::adapter::SpawnerAdapter;
31use std::sync::Arc;
32
33/// Wrap the compiled base `SpawnerAdapter` with Layers and return the
34/// finished value.
35///
36/// - `base`: `Compiler.compile()`'s `CompiledBlueprint.router`
37/// (`Arc<CompiledAgentTable>`), upcast to `Arc<dyn SpawnerAdapter>`.
38/// - `layer_hints`: `Blueprint.spawner_hints.layers` — the capability
39/// key strings.
40/// - `engine`: needed both to look factories up on the
41/// `LayerRegistry` and to run them (each base/hint factory takes
42/// `&Engine` and returns `Arc<dyn SpawnerLayer>`).
43///
44/// Order: `base_factories` first (wrapped for every Blueprint), then
45/// `lookup_hint` (the layers this Blueprint declares).
46pub fn link(
47 base: Arc<dyn SpawnerAdapter>,
48 layer_hints: &[String],
49 engine: &Engine,
50) -> Arc<dyn SpawnerAdapter> {
51 let layer_registry = engine.layer_registry();
52 let mut stack = SpawnerStack::new(base);
53 for factory in layer_registry.base_factories() {
54 stack = stack.layer_dyn(factory(engine));
55 }
56 for hint_key in layer_hints {
57 if let Some(factory) = layer_registry.lookup_hint(hint_key) {
58 stack = stack.layer_dyn(factory(engine));
59 }
60 // Unregistered hints are silently skipped — the lenient
61 // default.
62 }
63 stack.build()
64}