Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion decisions/WF-ADR-0048-shared-routing-core-apple-embedding.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
schema_version: 1
id: WF-ADR-0048
type: decision
status: accepted-for-phase-0-spikes
status: accepted
date: 2026-07-24
tags: [rust, swift, ios, ipados, ffi, routing, providers, persistence]
---
Expand Down Expand Up @@ -133,6 +133,22 @@ mobile independence or introduce a Swift routing implementation.
- Core extraction lands without provider or UI behavior changes.
- XCFramework/bridge work lands separately from the extraction.

## Implementation status

The first extraction boundary provides:

- `wayfinder-routing-core` as the renamed authoritative scorer and planner;
- `wayfinder-runtime-contracts` as secret-free, serializable host contracts;
- gateway and compatibility consumers compiled against the renamed core;
- hard eligibility filters that run before score-based tier selection;
- stable input-order selection and pre-output fallback planning within the
recommended tier;
- a manifest-level dependency test that rejects unreviewed production
dependencies.

The generated Swift bridge, XCFramework assembly, physical-device proof, and
provider-execution placement decision remain separate gated work.

## Consequences

The router remains one inspectable authority while each Apple host can use the
Expand Down
5 changes: 5 additions & 0 deletions roadmaps/WF-ROADMAP-0016-native-mobile-v0.2.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@ Exit:

## Phase 1 — extract the portable routing core

Implementation note: the pure routing crate and typed runtime-contract crate
are extracted, and current gateway/compatibility consumers use the renamed
core. Swift bindings, XCFramework assembly, and simulator/device bridge proof
remain before this phase's exit gate is complete.

- isolate pure routing and runtime-contract crates;
- remove host/server assumptions from route planning;
- add typed request, candidate, plan, explanation, and receipt contracts;
Expand Down
42 changes: 26 additions & 16 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
[workspace]
resolver = "3"
members = [
"crates/wayfinder-core",
"crates/wayfinder-routing-core",
"crates/wayfinder-runtime-contracts",
"crates/wayfinder-config",
"crates/wayfinder-providers",
"crates/wayfinder-gateway",
Expand Down
22 changes: 13 additions & 9 deletions rust/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
Rust is Wayfinder's sole production router and gateway runtime under
WF-ADR-0046. This workspace contains:

- `wayfinder-core` — deterministic feature extraction, scoring, tiers, and
explanations;
- `wayfinder-routing-core` — authoritative deterministic feature extraction,
compatibility filtering, scoring, tier selection, and route planning;
- `wayfinder-runtime-contracts` — secret-free requests, destination snapshots,
exclusion reasons, plans, explanations, and receipts;
- `wayfinder-config` — typed routing/gateway configuration and preserved
mutations;
- `wayfinder-providers` — bounded provider clients and streaming translation;
Expand Down Expand Up @@ -43,14 +45,16 @@ Wayfinder Desktop continues to embed `wayfinder-router` as a separately running
signed helper. Native iPhone and iPad v0.2 does not run that executable or an
internal HTTP server.

WF-ADR-0048 extracts a pure `wayfinder-routing-core` and typed runtime contracts
from this workspace. The gateway and generated Swift bridge will consume the
same core and golden fixtures. The pure core may not perform filesystem,
Keychain, process, provider, HTTP-server, UI, or Apple-framework work.
WF-ADR-0048 defines the extracted `wayfinder-routing-core` and
`wayfinder-runtime-contracts` as the shared routing authority. The gateway
already consumes that core, and dependency-boundary plus golden-corpus tests
protect its portability. The generated Swift bridge will consume these same
crates and fixtures in its own pull request. The pure core may not perform
filesystem, Keychain, process, provider, HTTP-server, UI, or Apple-framework
work.

The extraction, bridge/XCFramework, provider execution choice, iOS shell, auth,
Apple model, and pairing remain separate pull requests under
WF-ROADMAP-0016.
The bridge/XCFramework, provider execution choice, iOS shell, auth, Apple
model, and pairing remain separate pull requests under WF-ROADMAP-0016.

## Governing documents

Expand Down
2 changes: 1 addition & 1 deletion rust/crates/wayfinder-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ uuid.workspace = true
wayfinder-apple-foundation-xpc = { path = "../wayfinder-apple-foundation-xpc" }
wayfinder-codex-app-server = { path = "../wayfinder-codex-app-server" }
wayfinder-config = { path = "../wayfinder-config" }
wayfinder-core = { path = "../wayfinder-core" }
wayfinder-routing-core = { path = "../wayfinder-routing-core" }
wayfinder-gateway = { path = "../wayfinder-gateway" }
wayfinder-providers = { path = "../wayfinder-providers" }
wayfinder-service = { path = "../wayfinder-service" }
Expand Down
2 changes: 1 addition & 1 deletion rust/crates/wayfinder-cli/src/config_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use wayfinder_config::{
CONFIG_FILE, CONFIG_PATH_ENV, TierOrderPolicy, apply_supported_routing_fragment,
find_config_file, routing_config_from_toml,
};
use wayfinder_core::{FEATURE_ORDER, RoutingConfig, Weights};
use wayfinder_routing_core::{FEATURE_ORDER, RoutingConfig, Weights};

use crate::{EXIT_CONFIG, EXIT_OK, EXIT_USAGE, write_error, write_output};

Expand Down
8 changes: 4 additions & 4 deletions rust/crates/wayfinder-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@ use wayfinder_config::{
CONFIG_PATH_ENV, THRESHOLD_ENV, TierOrderPolicy, find_config_file, load_routing_config,
routing_config_from_toml,
};
use wayfinder_core::{
ComplexityScore, FEATURE_ORDER, Lexicon, RoutingConfig, RoutingMode, Weights, binary_tiers,
explain_score, score_complexity,
};
use wayfinder_gateway::delivery::{
AppleFoundationModelDelivery, BufferedProviderDelivery, CodexAppServerDelivery,
CredentialError, CredentialSource, OpenAiCompatibleDelivery, ProviderDelivery,
Expand All @@ -40,6 +36,10 @@ use wayfinder_gateway::{AppState, ConfiguredModel, RouteOn, build_reloadable_rou
use wayfinder_providers::openai_compat::{
DEFAULT_CONNECT_TIMEOUT, OpenAiProviderClient, ProviderClientConfig, SecretValue,
};
use wayfinder_routing_core::{
ComplexityScore, FEATURE_ORDER, Lexicon, RoutingConfig, RoutingMode, Weights, binary_tiers,
explain_score, score_complexity,
};
use wayfinder_service::credentials::{LegacyCommandLimits, resolve_legacy_command};
use wayfinder_service::pricing::{LedgerLoad, SavingsLedger};

Expand Down
2 changes: 1 addition & 1 deletion rust/crates/wayfinder-compat-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ serde_json.workspace = true
tokio.workspace = true
tower.workspace = true
wayfinder-config = { path = "../wayfinder-config" }
wayfinder-core = { path = "../wayfinder-core" }
wayfinder-routing-core = { path = "../wayfinder-routing-core" }
wayfinder-gateway = { path = "../wayfinder-gateway" }
wayfinder-service = { path = "../wayfinder-service" }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::time::Instant;

use serde::Deserialize;
use serde_json::json;
use wayfinder_core::{RoutingConfig, score_complexity};
use wayfinder_routing_core::{RoutingConfig, score_complexity};

#[derive(Deserialize)]
struct Fixture {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use http::{Request, StatusCode};
use serde::Deserialize;
use serde_json::{Value, json};
use tower::ServiceExt;
use wayfinder_core::RoutingConfig;
use wayfinder_gateway::{AppState, build_router};
use wayfinder_routing_core::RoutingConfig;

const MAX_OPERATIONS: usize = 2_000_000;
const RESPONSE_LIMIT: usize = 1024 * 1024;
Expand Down
2 changes: 1 addition & 1 deletion rust/crates/wayfinder-compat-tests/tests/config_parity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::error::Error;

use serde::Deserialize;
use wayfinder_config::{TierOrderPolicy, routing_config_from_toml};
use wayfinder_core::{FEATURE_ORDER, RoutingConfig, RoutingMode, score_complexity};
use wayfinder_routing_core::{FEATURE_ORDER, RoutingConfig, RoutingMode, score_complexity};

const ROUTING_CONFIG_VECTORS: &str = include_str!("../fixtures/routing-config.json");
const WHERE: &str = "compat-vector";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use std::error::Error;

use axum::body::{Body, to_bytes};
use http::{Request, StatusCode};
use serde_json::{Value, json};
use tower::ServiceExt;
use wayfinder_gateway::{AppState, build_router};
use wayfinder_routing_core::{RoutingConfig, score_complexity};

#[tokio::test]
async fn embedded_core_and_gateway_return_identical_decisions() -> Result<(), Box<dyn Error>> {
let routing = RoutingConfig::binary(0.5);
let state = AppState::new(routing.clone(), Vec::new(), false, "2026.7.0");
let prompts = [
"hi",
"Prove the theorem under exactly these constraints. Derive the invariant and explain why it prevents concurrency deadlock.",
];

for prompt in prompts {
let embedded = score_complexity(prompt, &routing)?;
let request = Request::builder()
.method("POST")
.uri("/v1/chat/completions")
.header("content-type", "application/json")
.body(Body::from(serde_json::to_vec(&json!({
"model": "auto",
"messages": [{"role": "user", "content": prompt}]
}))?))?;
let response = build_router(state.clone()).oneshot(request).await?;
assert_eq!(response.status(), StatusCode::OK, "{prompt:?}");
let body = to_bytes(response.into_body(), 1024 * 1024).await?;
let gateway: Value = serde_json::from_slice(&body)?;
let decision = gateway
.get("wayfinder")
.ok_or("gateway response omitted decision")?;

assert_eq!(decision["score"], json!(embedded.score), "{prompt:?} score");
assert_eq!(
decision["model"],
json!(embedded.recommendation),
"{prompt:?} recommendation"
);
assert_eq!(
decision["features"],
serde_json::to_value(embedded.features)?,
"{prompt:?} features"
);
}

Ok(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use http::{Request, StatusCode};
use serde::Deserialize;
use serde_json::{Map, Value};
use tower::ServiceExt;
use wayfinder_core::RoutingConfig;
use wayfinder_gateway::{AppState, ConfiguredModel, build_router};
use wayfinder_routing_core::RoutingConfig;

const GATEWAY_HTTP_VECTORS: &str = include_str!("../fixtures/gateway-http.json");
#[derive(Debug, Deserialize)]
Expand Down
4 changes: 2 additions & 2 deletions rust/crates/wayfinder-compat-tests/tests/routing_parity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::collections::BTreeMap;
use std::error::Error;

use serde::Deserialize;
use wayfinder_core::{
use wayfinder_routing_core::{
ClassifierModel, ComplexityScore, RoutingConfig, RoutingMode, Tier, Weights, score_complexity,
};

Expand Down Expand Up @@ -131,7 +131,7 @@ fn assert_decision_matches(
);
assert_eq!(
expected_features.len(),
wayfinder_core::FEATURE_ORDER.len(),
wayfinder_routing_core::FEATURE_ORDER.len(),
"{name} feature count"
);
for (feature, expected_value) in expected_features {
Expand Down
2 changes: 1 addition & 1 deletion rust/crates/wayfinder-config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ serde.workspace = true
thiserror.workspace = true
toml.workspace = true
toml_edit.workspace = true
wayfinder-core = { path = "../wayfinder-core" }
wayfinder-routing-core = { path = "../wayfinder-routing-core" }

[dev-dependencies]
serde_json.workspace = true
Expand Down
4 changes: 2 additions & 2 deletions rust/crates/wayfinder-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use std::path::{Path, PathBuf};

use thiserror::Error;
use toml::Value;
use wayfinder_core::{
use wayfinder_routing_core::{
ClassifierModel, CoreError, FEATURE_ORDER, Lexicon, RoutingConfig, Tier, Weights, binary_tiers,
python_round,
};
Expand Down Expand Up @@ -818,7 +818,7 @@ fn invalid(where_: &str, message: impl Into<String>) -> ConfigError {
#[cfg(test)]
mod tests {
use super::*;
use wayfinder_core::{RoutingMode, score_complexity};
use wayfinder_routing_core::{RoutingMode, score_complexity};

fn parse(text: &str, order: TierOrderPolicy) -> Result<RoutingConfig, ConfigError> {
routing_config_from_toml(text, "fixture", None, order)
Expand Down
2 changes: 1 addition & 1 deletion rust/crates/wayfinder-gateway/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ uuid.workspace = true
wayfinder-config = { path = "../wayfinder-config" }
wayfinder-apple-foundation-xpc = { path = "../wayfinder-apple-foundation-xpc" }
wayfinder-codex-app-server = { path = "../wayfinder-codex-app-server" }
wayfinder-core = { path = "../wayfinder-core" }
wayfinder-routing-core = { path = "../wayfinder-routing-core" }
wayfinder-providers = { path = "../wayfinder-providers" }
wayfinder-service = { path = "../wayfinder-service" }

Expand Down
2 changes: 1 addition & 1 deletion rust/crates/wayfinder-gateway/src/codex_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@ mod tests {
use axum::http::{Method, Request};
use serde_json::{Value, json};
use tower::ServiceExt;
use wayfinder_core::RoutingConfig;
use wayfinder_routing_core::RoutingConfig;

use super::*;
use crate::{AppState, build_router};
Expand Down
Loading
Loading