Skip to content

Latest commit

 

History

History
342 lines (272 loc) · 14.3 KB

File metadata and controls

342 lines (272 loc) · 14.3 KB

Driver Architecture

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.

1. Enduring vision, principles, and rationale

Decision

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.

Responsibility boundaries

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.

Review and failure principles

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.

2. Proposed and future architecture, including alternatives

Shared primitives and typed registries

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.

Profiles and selection

Long-term Driver resolution should support explicit precedence:

  1. operation override;
  2. feature-instance profile;
  3. Universe feature profile;
  4. application feature default;
  5. 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);

Durable execution and mobile operation

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.

Provenance and persistence

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.

Contrasting strategy example

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.

Roadmap

The original staged plan remains useful:

  1. Provider boundary (implemented): define typed text generation, implement an OpenAI-compatible adapter, keep credentials host-managed, and test the transport with stub HTTP.
  2. 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.
  3. 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.
  4. Contrasting strategies (future): add a multi-character sequential strategy, compare its context needs, and introduce per-Universe and per-Interface selection only where proven.
  5. 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.
  6. 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.

Explicit non-goals

  • one generic IDriver returning 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.

Why this direction fits WorldSharp

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.

3. Current implementation, status, and known gaps

The initial conversation proof is implemented and the pattern is now used by:

  • Convo: IConversationDriver and 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.