Skip to main content
Nika follows real semver toward 1.0. Features ship incrementally across 1.x minors over years. The 1.0 architecture must accommodate later minors (memory subsystem, agent-v2; WASM plugins, observability), and features we haven’t imagined without breaking changes to the public API.
Status: LOCKED at v0.80. Every crate admitted to the Diamond workspace must comply before passing Gate 12. Patterns distilled from Rust (2015β†’), Tokio (0.1β†’1.x), Deno, Cargo, Serde (9+ years stable), Axum/Tower, Bevy.Canonical source: docs/architecture/forward-compat-invariants.md.

The 8 patterns

Define traits in nika-kernel at v0.90 for subsystems that ship later. Default methods return Err(Unsupported) until real implementations land.
#[trait_variant::make(MemoryStoreDyn: Send)]
pub trait MemoryStore: Send + Sync + 'static {
    async fn put(&self, frame: MemoryFrame) -> Result<MemoryFrameRef>;
    async fn query(&self, q: &MemoryQuery) -> Result<Vec<MemoryFrame>>;
    async fn forget(&self, _r: &MemoryFrameRef) -> Result<()> {
        Err(NikaError::Unsupported("forget"))
    }
}
Locked traits (v0.90): MemoryStore, EmbeddingProvider, ToolExecutor, WasmPluginHost, MetricsExporter + TracerProvider + AuditSink + EventSink + BillingSink, Sandbox. Stubs in src/plugin/, src/infra/.Impact: v0.95 lights up the memory subsystem by adding nika-memory-oxigraph that implements MemoryStore. Zero v0.90 code modification.
Every public type carries #[non_exhaustive] so adding a field or variant is always additive. new() constructors provide ergonomic construction without exposing struct literal syntax.
#[non_exhaustive]
pub struct InferRequest {
    pub provider: ProviderId,
    pub model: ModelId,
    pub messages: Vec<Message>,
    // adding fields here never breaks downstream
}

impl InferRequest {
    pub fn new(provider: ProviderId, model: ModelId) -> Self { /* ... */ }
}
Invariant #19: every #[non_exhaustive] struct ships a new() constructor.
A workflow pins its contract with one header line Β· nika: v1 (a single version marker Β· the language name as key, the contract version as value Β· supersedes the older K8s apiVersion: and schema: nika/workflow@X forms). Parsers reject any other value (v1.0 Β· v2 …) with a clear error.
illustration Β· the envelope pin
nika: v1
workflow: my-flow
tasks: [...]
v1 is the only value for the lifetime of the v1 contract β€” minor additions are additive and never change it. A nika: v2 would be a deliberate breaking generation (the v1 envelope is frozen Β· effectively never). Non-workflow artifacts (package manifests, events) carry their own schema version field.
Public schemas reserve two namespaces: nika.* for core additions (authoritative, versioned) and x-* for community overlays (no stability guarantee, no collision with core). Scope note Β· in the workflow language both are RESERVED, not current β€” unknown top-level fields are rejected at v0.1, and the tool namespace set is closed at nika: / mcp: (engine-specific tools route through mcp:). The live x-* surface today is the pck registry.
illustration Β· RESERVED future extension fields (rejected today)
nika: v1
workflow: my-flow
nika.cache: { ttl: 3600 }      # nika.* core extension Β· a future additive minor
x-my-org-metric: 42             # x-* community overlay Β· reserved Β· NOT valid v0.1
Two error surfaces, one rule each. The WORKFLOW-VISIBLE surface is the spec’s NIKA-<NAMESPACE>-<NNN> taxonomy (28 registered v0.1 codes Β· reference/error-codes) β€” namespaces own 001-099 ranges, codes are never repurposed. The ENGINE-INTERNAL registry (nika_error::codes Β· NIKA-1000+ blocks per L1 effect crate) is diagnostics machinery β€” reserved per crate, never reassigned, and never leaked into workflow-visible errors.
Core kernel traits (e.g., Provider, EventSink, Sandbox) are sealed via a private supertrait β€” only Nika-owned crates can implement them. Extension traits (e.g., MemoryStore) are open for community crates to implement.
mod sealed { pub trait Sealed {} }
pub trait Provider: sealed::Sealed + Send + Sync { /* ... */ }
Enforced by ADR-014.
New capability = new feature flag, OFF by default. After 1-2 minor cycles of stability, flips to default-ON. Never remove a default feature (breaking change) β€” deprecate and redirect.
[features]
default = ["rustls-tls"]
rustls-tls = ["dep:rustls"]
native-tls = ["dep:native-tls"]   # alternative, off by default
Three tools on every CI run:
  • cargo public-api β€” detects API surface changes per crate.
  • cargo semver-checks β€” verifies SemVer compatibility.
  • cargo deny β€” enforces license + advisories + layer bans.
Gate 12 fails on any unintentional public API drift. .public-api.json snapshots are checked into the repo per crate.

The 5 locked decisions (v0.80)

IDDecisionImpact
FCI-009EventKind = scoped sub-enums (Pattern B), ~22 categoriesEmit site is compile-time checked; sub-enums evolve independently
FCI-010InferRequest / InferResponse fields reserved: memory, budget, baggage, trust_level, trace_idv0.95 adds memory without touching kernel
FCI-011schema: field mandatory day-1Every on-disk artifact versioned from v0.90 forward
FCI-012Provider trait shape frozen (4 methods, 1 sealed supertrait)Cross-provider parity testable (shadow zone 2 + 7)
FCI-013Telemetry = 5 sibling sinks (Event, Metrics, Trace, Audit, Billing)OTel GenAI semconv bridged via typed GenAiAttrs β€” Q13 rev.3

The 10 rules

Every crate admission answers β€œyes” to each. cargo public-api + cargo semver-checks catch violations before merge.
  1. All public types carry #[non_exhaustive].
  2. All public structs with #[non_exhaustive] ship a new() constructor.
  3. All async trait methods use trait_variant::make for dynamic dispatch.
  4. All core traits seal via private supertrait.
  5. All error types expose a NIKA-XXX code via NikaError::code().
  6. All on-disk schemas carry schema: "nika/<kind>@<major>".
  7. All DTO fields reserve forward-compat extension slots (see FCI-010).
  8. All feature flags default to a stable subset β€” new caps ship OFF.
  9. All public API changes emit a cargo public-api diff in the PR.
  10. All breaking changes require ADR Accepted before merge.

See also

Layer registry

Six layers + L0.5, mechanical sort test, security axes.

L0 foundation decisions

Q1-Q13 locked 2026-04-16 β€” proc macros, kernel prelude, transform crate, memory sinks.

12-gate admission

How a crate earns a seat at the workspace β€” SPEC through ATOMIC commit.

ADR index

35 decision records (30 Accepted + 5 Proposed).