Skip to content
Merged
14 changes: 14 additions & 0 deletions crates/loomweave-cli/src/serve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use loomweave_federation::config::{
select_provider_with_env,
};
use loomweave_federation::filigree::FiligreeHttpClient;
use loomweave_federation::warpline::WarplineMcpClient;
use loomweave_llm::{
ApiEmbeddingProvider, ApiEmbeddingProviderConfig, ClaudeCliProvider, ClaudeCliProviderConfig,
CodexCliProvider, CodexCliProviderConfig, EmbeddingProvider, EmbeddingProviderError,
Expand Down Expand Up @@ -91,6 +92,12 @@ pub fn run(path: &Path, config_path: Option<&Path>) -> Result<()> {
)
.context("build Filigree HTTP client")?;

// Read-only Warpline churn consumer for the high-churn / recently-changed
// surfaces. `None` when disabled (the default) — the surfaces degrade
// honestly. Enrich-only, dependency-sink: nothing flows loomweave→warpline.
let warpline_client =
WarplineMcpClient::from_config(&config.integrations.warpline, Some(&project_root));

let diagnostics = loomweave_mcp::DiagnosticsContext {
llm: llm_diagnostics,
filigree: filigree_resolution,
Expand Down Expand Up @@ -127,6 +134,7 @@ pub fn run(path: &Path, config_path: Option<&Path>) -> Result<()> {
llm_provider,
semantic_search_state(&config.semantic_search, embedding_provider),
filigree_client,
warpline_client,
diagnostics,
loomweave_mcp::McpToolPolicy {
enable_write_tools: config.serve.mcp.enable_write_tools,
Expand Down Expand Up @@ -199,6 +207,7 @@ fn spawn_mcp_stdio(
llm_provider: Option<Arc<dyn LlmProvider>>,
semantic_search: Option<SemanticSearchState>,
filigree_client: Option<FiligreeHttpClient>,
warpline_client: Option<WarplineMcpClient>,
diagnostics: loomweave_mcp::DiagnosticsContext,
tool_policy: loomweave_mcp::McpToolPolicy,
analyze_config_path: Option<PathBuf>,
Expand All @@ -215,6 +224,7 @@ fn spawn_mcp_stdio(
llm_provider,
semantic_search,
filigree_client,
warpline_client,
diagnostics,
tool_policy,
analyze_config_path,
Expand All @@ -234,6 +244,7 @@ fn run_mcp_stdio(
llm_provider: Option<Arc<dyn LlmProvider>>,
semantic_search: Option<SemanticSearchState>,
filigree_client: Option<FiligreeHttpClient>,
warpline_client: Option<WarplineMcpClient>,
diagnostics: loomweave_mcp::DiagnosticsContext,
tool_policy: loomweave_mcp::McpToolPolicy,
analyze_config_path: Option<PathBuf>,
Expand Down Expand Up @@ -271,6 +282,9 @@ fn run_mcp_stdio(
if let Some(client) = filigree_client {
state = state.with_filigree_client(Arc::new(client));
}
if let Some(client) = warpline_client {
state = state.with_warpline_client(Arc::new(client));
}
state = state.with_diagnostics(diagnostics);

let serve_result = loomweave_mcp::serve_stdio_with_state_on_runtime(
Expand Down
50 changes: 50 additions & 0 deletions crates/loomweave-federation/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,14 @@ impl ClaudePermissionMode {
#[serde(default, deny_unknown_fields)]
pub struct IntegrationsConfig {
pub filigree: FiligreeConfig,
/// Warpline (the federation's temporal/change authority) churn-count read,
/// consumed at read time by `entity_high_churn_list` /
/// `entity_recent_change_list`. Read-only, enrich-only, dependency-sink:
/// loomweave never stores a warpline fact (the seam's HARD RULE — loomweave
/// retains no cross-run history; see the 2026-06-13 warpline interface lock
/// §1, §5). Default disabled — the churn surfaces stay honest-empty with a
/// missing-signal note until an operator opts in.
pub warpline: WarplineConfig,
}

#[derive(Debug, Clone, PartialEq, Default, Deserialize)]
Expand Down Expand Up @@ -754,6 +762,48 @@ impl Default for FiligreeConfig {
}
}

/// Read-time consumption of Warpline's FROZEN churn-count read
/// (`warpline_entity_churn_count_get`, `warpline.entity_churn_count.v1`). This
/// is a *read-only* seam: loomweave asks warpline for per-entity change counts
/// to rank `entity_high_churn_list` / `entity_recent_change_list`, joins them at
/// read time, and retains NOTHING (the loomweave↔warpline HARD RULE — loomweave
/// holds no cross-run history). There is deliberately NO write/emit flag here:
/// unlike the Filigree seam, nothing flows loomweave→warpline.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct WarplineConfig {
/// Whether the churn surfaces consult warpline. Default `false`: the
/// surfaces stay honest-empty (with a missing-signal note naming warpline)
/// until an operator opts in. A missing/unreachable warpline with this
/// `true` degrades the same way — never an error, never empty-as-clean.
pub enabled: bool,
/// Operator-configured actor identity. **Reserved, not sent on the wire:**
/// `actor` is not in warpline's FROZEN `warpline_entity_churn_count_get`
/// schema (`additionalProperties: false`), so the churn read carries none.
/// The field is retained (rather than removed) so an existing `loomweave.yaml`
/// that sets `integrations.warpline.actor` still parses under
/// `deny_unknown_fields` — warpline's own dogfood config sets it.
pub actor: String,
/// Per-call timeout (seconds) for the warpline churn subprocess round-trip.
/// Warpline is an MCP-stdio member (no HTTP read API), launched as a
/// subprocess and driven over **newline-delimited** MCP JSON-RPC (the
/// transport `warpline-mcp` actually speaks). A warpline child that accepts
/// the connection and never answers would otherwise hang the read; this
/// bound makes a transport fault degrade to the honest `warpline-unreachable`
/// response instead. Default 10s; a `0` is floored to 1s by the client.
pub timeout_seconds: u64,
}

impl Default for WarplineConfig {
fn default() -> Self {
Self {
enabled: false,
actor: "loomweave-mcp".to_owned(),
timeout_seconds: 10,
}
}
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ProviderSelection {
Disabled,
Expand Down
1 change: 1 addition & 0 deletions crates/loomweave-federation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ pub mod filigree_url;
pub mod loomweave_port;
pub mod loomweave_url;
pub mod scan_results;
pub mod warpline;
Loading