This document records an accepted architectural direction. It distinguishes the enduring Driver decision from the proposed common execution architecture and from the feature-specific Driver seams implemented today.
WorldSharp uses Drivers for interchangeable execution strategies behind stable features.
- A Feature owns user-visible semantics and canonical data.
- A Driver owns how one complete Feature operation is executed.
- A Provider adapts one low-level external capability.
- An Extension may supply feature Drivers and capability Providers.
- A future Profile may select and configure a Driver for a scope.
Drivers are not repositories, database adapters, UI components, or a universal autonomous-agent abstraction. This seam lets local, cloud-efficient, multi-request, and media-aware strategies vary without duplicating a feature's storage or UI.
Feature services:
- define the Driver contract, immutable context, typed result, and settings boundary;
- gather authoritative Core state and enforce Universe ownership;
- select a Driver and Provider configuration;
- validate output and commit canonical state transactionally;
- present review and useful failures;
- retain provenance appropriate to the feature.
Drivers:
- orchestrate only allowed Providers;
- choose request/prompt sequencing;
- honor cancellation and declared budgets;
- report structured progress where supported;
- return typed proposals or a meaningful failure;
- never mutate canonical stores directly.
Conversational Drivers should prefer plain continuation text for roleplay and conversation prose. The surrounding transcript should establish the speaker format, while the Driver keeps machine-control parsing separate from authored text. JSON or other structured output is appropriate for orchestration metadata, not for the Character's natural response unless a specific Driver truly requires it.
Roleplay message-local state follows the text-first decision in
TEXT-FIRST-ROLEPLAY-MESSAGES.md: one complete Driver-owned text document is the
editable authority for anything about that message that can affect later model
context. Typed speaker attribution may be retained as a derived index, while
generic services persist complete documents and generic rewrites without knowing
one Driver's POV or cache protocol.
Providers:
- implement a capability such as text, image, vision, or embeddings;
- normalize vendor transport into a typed WorldSharp contract;
- expose failure and usage information;
- keep credentials in host-managed configuration.
Feature Driver contracts belong with the feature, not Infrastructure and not one object-valued universal Driver package:
Core and feature contracts
^
|
Driver implementations ----> Provider abstractions
^ ^
| |
Extension composition Infrastructure/host adapters
HTTP endpoints and Razor components call feature application services, never
Drivers directly. Drivers receive immutable context and capability-scoped
dependencies, not SqliteConnection, arbitrary repositories, or
IServiceProvider.
Driver output is a proposal. Features validate it before persistence; World Tracker proposals additionally require explicit branch commit or discard. Failures must be surfaced instead of silently changing implementation.
Fallback, if introduced, is valid only for an explicit Unsupported result
reported before work starts. Exceptions, malformed output, invalid profiles,
unavailable Providers, cancellation, and budget exhaustion are failures. Driver
load order is never a fallback policy, and partial output from unrelated Drivers
is not merged.
Only concepts proven across features should move into
WorldSharp.Extensions.Abstractions. Possible shapes include a descriptor,
budget, progress sink, and execution identity:
public sealed record DriverDescriptor(
string Id,
string DisplayName,
int ContractVersion,
string Description);
public sealed record DriverBudget(
int? MaximumRequests,
int? MaximumInputTokens,
int? MaximumOutputTokens,
TimeSpan? Timeout);
public interface IDriverProgress
{
ValueTask ReportAsync(
DriverProgressUpdate update,
CancellationToken cancellationToken);
}These names are proposals, not current APIs. Prefer one typed registry per
feature rather than IDriver returning arbitrary JSON:
public interface ISocialFeedDriverRegistry
{
void Add(ISocialFeedDriver driver);
ISocialFeedDriver GetRequired(string id);
}Registry rules should be:
- qualified, stable IDs such as
worldsharp.noodle.batch; - duplicate IDs fail startup;
- built-in and extension Drivers use the same seam;
- saved configuration refers to IDs, not CLR names;
- incompatible contract versions fail registration or profile validation.
The existing synchronous IWorldSharpExtension.Register lifecycle is the
initial composition seam. A future extension-facing Driver registry should be
exposed only after in-tree features establish its contract; runtime assembly
loading remains a separate lifecycle/security decision.
Long-term Driver resolution should support explicit precedence:
- operation override;
- feature-instance profile;
- Universe feature profile;
- application feature default;
- built-in fallback.
The selected ID and settings version should remain visible. Selection changes future runs only. Driver-specific settings may use versioned JSON at the persistence boundary because the owning extension can be absent, but each Driver must deserialize to a typed settings record and validate it. Secrets stay in host-managed Provider connections. Unknown profiles should remain preserved and inspectable when an extension is unavailable.
A possible persistence record is:
public sealed record DriverProfile(
Guid Id,
string FeatureId,
string DriverId,
int SettingsVersion,
JsonElement Settings);Long work should survive a backgrounded UI. The browser can start an execution over HTTP, receive an ID, and poll durable status:
POST /api/.../runs -> 202 Accepted + run ID
GET /api/driver-runs/{id} -> status/progress/result
POST /api/driver-runs/{id}/cancel
Optional SignalR/WebSocket updates may be added later but should not be authoritative. Short Drivers can execute inline while their contracts accept cancellation and avoid relying on the browser remaining connected.
A common execution model should distinguish invalid profile, missing Driver, unavailable Provider, budget exhaustion, cancellation, malformed model output, Driver failure, and feature validation rejection. Retries must be bounded, declared, and included in usage/provenance.
An execution should contain a stable execution ID, selected Driver/profile, cancellation, budget, progress sink, capability-scoped Provider access, and an immutable feature context. This is preferable to passing broad service access because the allowed effects and test surface remain visible.
The result model should preserve these failures separately:
- invalid or incompatible profile;
- missing Driver;
- unavailable Provider;
- cancellation or budget exhaustion;
- malformed Provider/model output;
- Driver failure;
- feature validation rejection.
Accepted output should be traceable to Driver and contract versions, profile and settings versions, Provider/model identity, source records, timestamps, duration, available usage, result records, and final status. Credentials and hidden reasoning are never provenance. Full prompts, if retained at all, require an explicit diagnostic opt-in.
Possible future tables include driver_profiles, driver_runs,
driver_run_events, and driver_run_outputs. They explain execution; canonical
feature data remains in feature-owned tables.
A social-feed feature can prove why Drivers are feature-specific. A batch Driver might propose a full turn in one request. An individual-agent Driver might prompt each Character, generate media, inspect the actual image through vision, and ask Characters to react. Both must return the feature's typed result, which the Social Feed validates and persists.
The original staged plan remains useful:
- Provider boundary (implemented): define typed text generation, implement an OpenAI-compatible adapter, keep credentials host-managed, and test the transport with stub HTTP.
- Conversation proof (implemented): define immutable context and typed Character-message output, add a single-request Driver and application service, validate participants, persist accepted output, and expose Generate in the conversation UI.
- Durable common execution (future): add profiles/run migrations, run independently of the browser request, expose polling/cancellation, show progress/errors, and record common usage/provenance.
- Contrasting strategies (future): add a multi-character sequential strategy, compare its context needs, and introduce per-Universe and per-Interface selection only where proven.
- Media and richer simulation (future): add a complete social feature, batch and individual-agent strategies, image/vision Providers, and generate-image -> inspect-image -> react flows.
- Extension SDK (future): expose established typed registries, validate compatibility/settings schemas, preserve unavailable profiles, and consider dynamic loading only after static external projects prove the lifecycle.
Tests for a mature Driver-backed feature should cover:
- duplicate registration and profile/settings validation;
- deterministic context construction;
- cancellation and budget propagation;
- malformed and cross-Universe output rejection;
- transactional canonical persistence;
- unavailable Driver behavior;
- unsupported-operation fallback and failed-operation non-fallback;
- provenance linkage.
Provider adapters should use stub HTTP handlers rather than paid live APIs.
- one generic
IDriverreturning arbitrary JSON; - raw database or dependency-injection access for Drivers;
- treating repository implementations as Drivers;
- building a general agent framework before feature needs exist;
- requiring sockets for progress;
- allowing generated output to bypass feature validation;
- freezing a speculative feature contract before that feature exists.
Drivers keep Core authoritative and features understandable while allowing local and cloud models to use different orchestration. Built-in and extension implementations share one seam; users can trade speed/cost for richer simulation; mobile operation is not tied to a fragile live connection; and common infrastructure grows from real feature contracts rather than speculation.
The initial conversation proof is implemented and the pattern is now used by:
- Convo:
IConversationDriverand the single-response Driver; - Roleplay generation, including bounded Scene sessions:
IRoleplayDriver; - Summaries:
IRangeSummaryDriver; - World Tracker:
IWorldTrackerDriver; - Social Feed: separate planner, drafter, and reaction Driver contracts.
Each contract has feature-owned immutable context and typed output. Feature services resolve implementations from dependency injection, use the selected or default OpenAI-compatible Provider connection, validate results, and persist feature state. Tracker output is staged on a history branch and requires review. Summary prose remains evidence rather than a world event.
The built-in Perspective Roleplay Driver is intentionally audience-isolated rather than omniscient. It asks a context-neutral selector which Character should act next, chronologically fills missing cached transcript POVs for every current participant using history ending before each source turn, and only then simulates the reply using the selected Character's POV transcript, private card, memories, and known world context. It reprojects the selected Character's newly authored first-person turn for every other audience using each target's prior POV history; those renderers must not author reactions or responses. Generated historical POVs are returned through the Driver contract and persisted on their source messages, so later turns reuse them rather than exposing canonical or another audience's transcript. A request never receives another audience's cached projection or private Character card. Other Drivers remain free to choose a different orchestration strategy through the same typed contract.
The current implementation still exposes audience projections in the common Roleplay result contract, persists them in separate records, assembles Perspective edits in the common UI, and branches on projection support while building generation context. Those are acknowledged extension-boundary failures, not the target architecture. The text-first message migration will move that protocol into complete Perspective-owned message documents and replace projection-specific persistence with generic document rewrites.
The complete sequencing and explicit no-perception behavior are specified in
PERSPECTIVE-ROLEPLAY-DRIVER.md.
Driver IDs can be supplied per operation where a feature exposes that input. Provenance varies by feature: summaries and tracker runs retain Driver/Provider IDs, while there is no common execution record covering every Driver.
There is currently no shared DriverDescriptor, DriverBudget, DriverProfile,
typed extension registry, persisted fallback chain, generalized selection
precedence, common budget/progress protocol, or durable cross-feature run API.
Social Feed has a feature-local active heartbeat Driver selection persisted per
Universe through its settings endpoint. Universes without an explicit setting
fall back to WorldSharp:SocialFeed:ActiveHeartbeatDriver, then the default
scoped batch Driver. This remains a feature-local setting rather than the
general-purpose DriverProfile system proposed above.
The proposed Unsupported fallback behavior is not implemented. Do not present
those proposal names as current APIs.