a789495a98
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
372 lines
12 KiB
Markdown
372 lines
12 KiB
Markdown
# Provider, Session, and Shared-Contract Boundary Audit
|
|
|
|
Status: 2026-04-16 audit note
|
|
|
|
This document audits the current provider, session, and shared-contract seams in the jcode workspace and recommends the next **realistic** crate moves that improve modularity without creating high-churn dependency cycles.
|
|
|
|
It is intentionally conservative. The goal is to identify boundaries that are both:
|
|
|
|
- structurally useful
|
|
- low enough churn to be worth turning into workspace crates now
|
|
|
|
See also:
|
|
|
|
- [`COMPILE_PERFORMANCE_PLAN.md`](./COMPILE_PERFORMANCE_PLAN.md)
|
|
- [`REFACTORING.md`](./REFACTORING.md)
|
|
- [`SERVER_ARCHITECTURE.md`](./SERVER_ARCHITECTURE.md)
|
|
- [`MULTI_SESSION_CLIENT_ARCHITECTURE.md`](./MULTI_SESSION_CLIENT_ARCHITECTURE.md)
|
|
|
|
## Executive summary
|
|
|
|
The next clean workspace moves are **not** a full `Provider` trait extraction and **not** a full `session.rs` split.
|
|
|
|
The best next steps are:
|
|
|
|
1. **Add a small `jcode-shared-contracts` crate** for the serde-only protocol/session overlap types that already act like shared contracts.
|
|
2. **After that, add a narrow `jcode-session-contracts` crate** for session metadata/replay/view structs that are widely reused but do not need the full `Session` runtime.
|
|
3. **If we want one more provider-side move before a larger provider refactor, extract the pure provider identity/selection layer** into `jcode-provider-core` or a small `jcode-provider-selection` crate.
|
|
|
|
The main things to avoid for now:
|
|
|
|
- extracting `Provider` / `EventStream` into a shared crate
|
|
- extracting all of `protocol.rs`
|
|
- extracting all of `session.rs`
|
|
- moving `provider_catalog.rs` wholesale into a crate
|
|
|
|
Those look tempting, but today they would mostly convert existing high-churn coupling into workspace-crate churn.
|
|
|
|
## Current workspace boundary state
|
|
|
|
Already landed and directionally good:
|
|
|
|
- `crates/jcode-provider-metadata`
|
|
- `crates/jcode-provider-core`
|
|
- `crates/jcode-provider-openrouter`
|
|
- `crates/jcode-provider-gemini`
|
|
|
|
A useful property of the current extracted crates is that they are still **leaf-like support crates**.
|
|
|
|
Current local workspace dependency picture for those crates:
|
|
|
|
- `jcode-provider-core`: no local workspace deps
|
|
- `jcode-provider-metadata`: no local workspace deps
|
|
- `jcode-provider-openrouter`: no local workspace deps
|
|
- `jcode-provider-gemini`: no local workspace deps
|
|
|
|
That is the right pattern to preserve. The next crate moves should keep producing small, leaf-ish crates instead of creating new central hubs that everything recompiles through.
|
|
|
|
## Hotspots and coupling observed
|
|
|
|
Relevant file sizes in the main crate:
|
|
|
|
- `src/session.rs`: 2730 lines
|
|
- `src/provider/mod.rs`: 2283 lines
|
|
- `src/protocol.rs`: 1198 lines
|
|
- `src/provider/openrouter.rs`: 1132 lines
|
|
- `src/provider/gemini.rs`: 1117 lines
|
|
- `src/provider_catalog.rs`: 775 lines
|
|
- `src/plan.rs`: 17 lines
|
|
|
|
High-level coupling observed during the audit:
|
|
|
|
- `src/provider/mod.rs` directly references `auth`, `logging`, `bus`, `message`, and `usage`
|
|
- `src/session.rs` directly references `message`, `protocol`, `plan`, `storage`, and support modules
|
|
- `src/protocol.rs` directly references `bus`, `config`, `message`, `plan`, `provider`, `session`, and `side_panel`
|
|
- `src/provider_catalog.rs` is especially tied to `env`, `storage`, and `logging`
|
|
|
|
That means the biggest blockers are not the already-extracted support crates. They are the remaining mixed runtime/facade modules in the main crate.
|
|
|
|
## Dependency shape
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
P[provider/mod.rs] --> AUTH[auth]
|
|
P --> MSG[message]
|
|
P --> BUS[bus]
|
|
P --> USAGE[usage]
|
|
|
|
S[session.rs] --> MSG
|
|
S --> PROTO[protocol.rs]
|
|
S --> PLAN[plan.rs]
|
|
S --> STORE[storage]
|
|
|
|
PROTO --> BUS
|
|
PROTO --> CFG[config]
|
|
PROTO --> MSG
|
|
PROTO --> PLAN
|
|
PROTO --> PROVIDER_TYPES[provider types]
|
|
PROTO --> SESSION_TYPES[session types]
|
|
```
|
|
|
|
The key architectural smell is that some types that are effectively **shared contracts** still live inside large mixed-responsibility modules.
|
|
|
|
## Provider boundary audit
|
|
|
|
### What is already in a good state
|
|
|
|
The existing provider crate moves were well chosen:
|
|
|
|
- `jcode-provider-metadata` holds stable login/profile catalog data
|
|
- `jcode-provider-core` holds route/cost/shared HTTP client/core value types
|
|
- `jcode-provider-openrouter` holds OpenRouter-specific catalog/cache/ranking/model-spec support
|
|
- `jcode-provider-gemini` holds Gemini Code Assist schema/types/support helpers
|
|
|
|
These are all relatively pure support surfaces.
|
|
|
|
### What is not a good next move yet
|
|
|
|
### Do not extract `Provider` / `EventStream` yet
|
|
|
|
`src/provider/mod.rs` is still deeply entangled with:
|
|
|
|
- `crate::message::{Message, StreamEvent, ToolDefinition}`
|
|
- auth-driven behavior
|
|
- runtime selection/failover
|
|
- logging and bus notifications
|
|
- provider-specific compaction and transport behavior
|
|
|
|
Moving the trait now would likely create a new shared crate that still changes whenever runtime/provider behavior changes.
|
|
|
|
That would improve directory layout, but not boundary quality.
|
|
|
|
### Do not move `provider_catalog.rs` wholesale yet
|
|
|
|
`src/provider_catalog.rs` is not just metadata. It currently mixes:
|
|
|
|
- catalog/profile values
|
|
- env mutation
|
|
- auth probing helpers
|
|
- config-file lookup
|
|
- logging/warnings
|
|
|
|
That facade is still too runtime-aware to become a clean leaf crate as-is.
|
|
|
|
### Best realistic provider move
|
|
|
|
### Option A: extract provider identity + pure selection
|
|
|
|
Most realistic provider-side move after the current support crates:
|
|
|
|
- move the provider identity enum currently represented by `ActiveProvider`
|
|
- move `src/provider/selection.rs`
|
|
- optionally move pure fallback ordering helpers that do not depend on auth/runtime state
|
|
|
|
Target:
|
|
|
|
- either a new `crates/jcode-provider-selection`
|
|
- or a small `provider_identity` / `selection` module inside `jcode-provider-core`
|
|
|
|
Why this is realistic:
|
|
|
|
- `selection.rs` is already pure logic
|
|
- it does not need `Message`, `EventStream`, auth state, or storage
|
|
- it would shave some policy code out of `src/provider/mod.rs`
|
|
- it creates a stable place for provider-order and provider-name normalization rules
|
|
|
|
Why this should stay narrow:
|
|
|
|
- once the code starts touching account failover, auth checks, runtime availability, or logging, it stops being a good crate boundary
|
|
|
|
## Session boundary audit
|
|
|
|
### Why `session.rs` should not be extracted wholesale yet
|
|
|
|
`src/session.rs` is large, but it is not one thing.
|
|
It currently mixes:
|
|
|
|
- persisted session data structures
|
|
- runtime session state
|
|
- journaling / file persistence helpers
|
|
- replay-event persistence
|
|
- startup/remote snapshot helpers
|
|
- image rendering helpers
|
|
|
|
A whole-file crate extraction would drag in more coupling than it removes.
|
|
|
|
Current blockers:
|
|
|
|
- `StoredMessage` depends on `crate::message::{ContentBlock, Message, Role, ToolCall}`
|
|
- replay-event types currently depend on `crate::protocol::SwarmMemberStatus`
|
|
- replay-event plan snapshots currently depend on `crate::plan::PlanItem`
|
|
- the session module also owns persistence and storage concerns
|
|
|
|
So the next move should be a **session-contract slice**, not a full session crate.
|
|
|
|
### Best realistic session move
|
|
|
|
### Option B: narrow `jcode-session-contracts`
|
|
|
|
After shared contracts are extracted first, move the session types that are:
|
|
|
|
- serde-only
|
|
- reused outside `session.rs`
|
|
- not tied to `storage` or the full `Session` runtime
|
|
|
|
Good first candidates:
|
|
|
|
- `SessionStatus`
|
|
- `SessionImproveMode`
|
|
- `StoredDisplayRole`
|
|
- `StoredTokenUsage`
|
|
- `StoredCompactionState`
|
|
- `StoredMemoryInjection`
|
|
- `RenderedImageSource`
|
|
- `RenderedImage`
|
|
- `StoredReplayEvent` and `StoredReplayEventKind` once their swarm/plan payloads stop pointing back into `protocol.rs`
|
|
|
|
What should stay in the main crate for now:
|
|
|
|
- `Session`
|
|
- `StoredMessage`
|
|
- session journaling/file IO
|
|
- session startup/load/save orchestration
|
|
- message-to-image rendering functions
|
|
|
|
Why this is realistic:
|
|
|
|
- these contract structs already have broad fanout across agent, server, replay, and TUI code
|
|
- they are semantically session-level contracts, not session-runtime behavior
|
|
- the move becomes much cleaner once shared swarm/protocol payloads are extracted first
|
|
|
|
## Shared-contract boundary audit
|
|
|
|
This is the highest-leverage next seam.
|
|
|
|
There are several small, serde-only types that are clearly shared contracts already, but they currently live inside large modules:
|
|
|
|
- `PlanItem` in `src/plan.rs`
|
|
- `TranscriptMode` in `src/protocol.rs`
|
|
- `CommDeliveryMode` in `src/protocol.rs`
|
|
- `FeatureToggle` in `src/protocol.rs`
|
|
- `SessionActivitySnapshot` in `src/protocol.rs`
|
|
- `SwarmMemberStatus` in `src/protocol.rs`
|
|
- `AgentInfo` in `src/protocol.rs`
|
|
- `ContextEntry` in `src/protocol.rs`
|
|
- `SwarmChannelInfo` in `src/protocol.rs`
|
|
- `AwaitedMemberStatus` in `src/protocol.rs`
|
|
- `NotificationType` in `src/protocol.rs`
|
|
|
|
These are used across server, tool, TUI, replay, and session persistence flows, but they do not need the rest of `protocol.rs`.
|
|
|
|
### Best overall next move
|
|
|
|
### Option C: add `jcode-shared-contracts`
|
|
|
|
Recommended contents for the first pass:
|
|
|
|
- `PlanItem`
|
|
- `TranscriptMode`
|
|
- `CommDeliveryMode`
|
|
- `FeatureToggle`
|
|
- `SessionActivitySnapshot`
|
|
- swarm-related status/info structs:
|
|
- `SwarmMemberStatus`
|
|
- `AgentInfo`
|
|
- `ContextEntry`
|
|
- `SwarmChannelInfo`
|
|
- `AwaitedMemberStatus`
|
|
- `NotificationType`
|
|
|
|
Why this is the best next move:
|
|
|
|
- it breaks the `session.rs -> protocol.rs / plan.rs` dependency knot at the contract layer
|
|
- it gives replay/session persistence a clean dependency for swarm and plan snapshots
|
|
- it trims `protocol.rs` without trying to extract `Request` and `ServerEvent` yet
|
|
- it preserves the current successful pattern of a small, leaf-ish support crate with mostly `serde` types
|
|
|
|
Minimal dependency goal:
|
|
|
|
- `serde`
|
|
- nothing else, if possible
|
|
|
|
## Recommended sequencing
|
|
|
|
### Phase 1
|
|
|
|
Create `crates/jcode-shared-contracts`.
|
|
|
|
Expected immediate moves:
|
|
|
|
- `src/plan.rs` contents
|
|
- the small shared structs/enums listed above from `src/protocol.rs`
|
|
|
|
Keep in main crate for now:
|
|
|
|
- `Request`
|
|
- `ServerEvent`
|
|
- `encode_event` / `decode_request`
|
|
|
|
### Phase 2
|
|
|
|
Create `crates/jcode-session-contracts`.
|
|
|
|
Do this only after Phase 1, so session replay types can point at `jcode_shared_contracts::*` instead of `crate::protocol::*` or `crate::plan::*`.
|
|
|
|
### Phase 3
|
|
|
|
If a provider-side move is still desired before a larger provider refactor, extract only:
|
|
|
|
- provider identity enum
|
|
- pure selection/fallback ordering helpers
|
|
|
|
Do **not** include:
|
|
|
|
- `Provider` trait
|
|
- `EventStream`
|
|
- account failover
|
|
- auth state inspection
|
|
- runtime provider availability
|
|
- logging/bus side effects
|
|
|
|
## Moves to explicitly defer
|
|
|
|
These should be treated as later-stage refactors, not next-step crate moves.
|
|
|
|
### Defer: full `protocol.rs` crate
|
|
|
|
Reason:
|
|
|
|
- `Request` and `ServerEvent` still pull in `message`, `provider`, `session`, `side_panel`, and `bus`
|
|
- extracting the whole file now would create a broad, high-fanout crate instead of a clean contract crate
|
|
|
|
### Defer: full `session.rs` crate
|
|
|
|
Reason:
|
|
|
|
- the file mixes contracts, runtime state, rendering, journaling, and persistence
|
|
- `StoredMessage` still anchors the session layer to `message.rs`
|
|
|
|
### Defer: full provider trait / impl crate split
|
|
|
|
Reason:
|
|
|
|
- the trait seam is still mixed with runtime behavior and provider-specific execution policy
|
|
- moving it now would likely centralize churn rather than reduce it
|
|
|
|
### Defer: full `provider_catalog.rs` extraction
|
|
|
|
Reason:
|
|
|
|
- the file is still a runtime facade around env/config/auth probing, not just metadata
|
|
|
|
## Why this order avoids dependency-cycle mistakes
|
|
|
|
The sequence matters:
|
|
|
|
1. extract small shared contracts first
|
|
2. then extract session contracts that depend on those shared contracts
|
|
3. only then revisit deeper provider or protocol extraction
|
|
|
|
That order avoids creating crates that need to point back into the main crate for basic DTOs, which is exactly how high-churn dependency cycles usually start.
|
|
|
|
## Recommended concrete next actions
|
|
|
|
1. Add `crates/jcode-shared-contracts` with serde-only types from `plan.rs` and the small protocol/session overlap set.
|
|
2. Update `session.rs`, `protocol.rs`, server, tool, replay, and TUI imports to point at that crate.
|
|
3. Re-measure touched-file compile times for:
|
|
- `src/session.rs`
|
|
- `src/protocol.rs`
|
|
- `src/provider/mod.rs`
|
|
4. If the new seam stays clean, follow with a narrow `jcode-session-contracts` extraction.
|
|
5. Revisit provider trait extraction only after message/runtime/provider-execution seams are thinner.
|