Skip to content

Latest commit

 

History

History
927 lines (633 loc) · 35.4 KB

File metadata and controls

927 lines (633 loc) · 35.4 KB

Modular Architecture RFC

Status: Draft

This RFC describes a modular target architecture for jcode that matches the current codebase, preserves the existing product model, and gives us a safe migration path from today's mostly-monolithic root crate to a layered workspace.

It is intentionally aligned with:

Goals

  • Document the architecture that exists today, not an idealized version.
  • Define a target layered and crate architecture that improves maintainability and compile times.
  • Establish dependency rules that prevent the workspace from collapsing back into a monolith.
  • Provide a phased migration plan that fits the refactoring roadmap and compile-performance plan.
  • Preserve runtime behavior: one shared server, reconnecting clients, session-local self-dev capability, and stable tool/provider flows.

Non-Goals

  • A big-bang rewrite.
  • Renaming every module or crate immediately.
  • Forcing every subsystem into a separate crate before its boundaries are ready.
  • Changing the core product architecture from single-server, multi-client.

Executive Summary

Today, jcode is best described as a modular monolith with a growing workspace shell:

  • The root jcode crate still owns most runtime orchestration and product behavior.
  • Several heavy or relatively self-contained subsystems have already moved into workspace crates.
  • The codebase has strong module-level separation in some areas, but several broad root modules still act as architectural chokepoints.

The target architecture is a layered workspace:

  1. Foundation layer for stable shared types and runtime primitives.
  2. Domain/runtime layer for session, agent, provider, and server logic.
  3. Interface layer for CLI, TUI, self-dev, and optional heavy integrations.
  4. Composition layer where the top-level jcode package wires the product together.

The most important design rule is this:

High-churn orchestration code must depend on stable lower layers, while stable lower layers must never depend back on runtime/UI/product-specific code.

That rule serves both architecture quality and compile-speed goals.

Current Architecture

Current runtime model

At the product level, the runtime architecture is already clear:

  • jcode is a single-server, multi-client application.
  • The server owns sessions, swarm state, background tasks, provider state, and shared services.
  • Clients are primarily TUI frontends that attach to server-owned sessions.
  • Self-dev is session-local capability on the shared server, not a separate architecture.

That model should stay intact.

Current code organization

The current code organization is mixed:

  • Root crate jcode still contains most product logic.
  • Workspace crates already isolate several heavy or stable seams.
  • Subdirectories under src/ increasingly reflect domain boundaries, especially for agent, cli, server, tool, and tui.

Current workspace members from Cargo.toml are grouped roughly as follows:

  • root package: jcode
  • foundation/runtime support: jcode-agent-runtime, jcode-core, jcode-storage, jcode-terminal-launch, jcode-tool-core
  • data-contract crates: jcode-ambient-types, jcode-auth-types, jcode-background-types, jcode-batch-types, jcode-config-types, jcode-gateway-types, jcode-memory-types, jcode-message-types, jcode-selfdev-types, jcode-session-types, jcode-side-panel-types, jcode-task-types, jcode-tool-types, jcode-usage-types
  • protocol and planning: jcode-protocol, jcode-plan
  • heavy or optional integrations: jcode-embedding, jcode-pdf, jcode-notify-email
  • auth and providers: jcode-azure-auth, jcode-provider-core, jcode-provider-metadata, jcode-provider-openrouter, jcode-provider-gemini
  • TUI extraction seams: jcode-tui-core, jcode-tui-markdown, jcode-tui-mermaid, jcode-tui-render, jcode-tui-workspace
  • product surfaces outside the main TUI binary: jcode-desktop, jcode-mobile-core, jcode-mobile-sim

What the root crate still owns

The root crate still directly owns most of the following concerns:

  • CLI parsing and dispatch
  • server orchestration and socket lifecycle
  • session state and persistence
  • agent turn execution and tool orchestration
  • provider implementation composition and runtime provider wiring; the shared Provider trait now lives in jcode-provider-core
  • protocol/message/config types
  • tool registry and many tool implementations
  • TUI application state and rendering
  • auth, memory, safety, ambient mode, and product glue

This is why the root crate is still the primary compile and architecture hotspot.

Existing extracted workspace seams

These splits already exist and should be treated as real architectural footholds, not temporary accidents:

Crate Current role
jcode-agent-runtime shared interrupt and lightweight runtime primitives for agent execution
jcode-ambient-types usage and rate-limit records shared by ambient/background flows
jcode-auth-types provider-neutral auth state and credential metadata
jcode-background-types background-task status and progress DTOs
jcode-batch-types batch tool progress DTOs, currently depending only on message types internally
jcode-config-types stable configuration data contracts
jcode-core low-level utilities such as IDs, env helpers, fs helpers, stdin detection, and formatting
jcode-gateway-types gateway-facing data contracts
jcode-memory-types memory subsystem data contracts
jcode-message-types message content and transport-adjacent data contracts
jcode-protocol client/server protocol surface built from stable type crates and provider-core values
jcode-plan plan/task graph data model shared across coordination flows
jcode-selfdev-types self-development request/status data contracts
jcode-session-types session DTOs, currently depending only on message types internally
jcode-side-panel-types side-panel page and update data contracts
jcode-task-types task/tool scheduling data contracts
jcode-tool-core runtime tool contracts such as the Tool trait and execution context
jcode-tool-types stable tool output/image DTOs
jcode-usage-types usage accounting data contracts
jcode-storage storage helpers layered on jcode-core
jcode-embedding ONNX/tokenizer-based embedding implementation and heavy inference deps
jcode-pdf PDF text extraction
jcode-azure-auth Azure bearer token retrieval
jcode-notify-email SMTP/IMAP/mail transport
jcode-provider-metadata provider/login catalog and profile metadata
jcode-provider-core shared provider contract (Provider/EventStream), value types, route/cost/model helpers, shared HTTP client, schema helpers
jcode-provider-openrouter OpenRouter-specific catalog/cache/support helpers
jcode-provider-gemini Gemini schema/model/support helpers
jcode-tui-core low-level terminal UI primitives that do not need full app state
jcode-tui-markdown markdown wrapping/rendering, layered on mermaid/workspace support
jcode-tui-mermaid mermaid parsing, rendering, caching, viewport, and widget support
jcode-tui-render reusable TUI layout/render helpers
jcode-tui-workspace workspace-map data/model/widget rendering
jcode-terminal-launch terminal process launch helpers
jcode-mobile-core shared headless mobile simulator state/protocol/visual model
jcode-mobile-sim mobile simulator CLI/app surface layered on jcode-mobile-core
jcode-desktop desktop app surface and session/workspace rendering experiments

These are already aligned with the compile-performance plan's strategy: isolate heavy dependencies and stable helper surfaces first.

Current chokepoints

The root crate still has several broad, high-fanout modules that make both maintenance and incremental compilation harder. Current sizes observed from the tree:

  • src/server.rs: ~1731 lines
  • src/provider/mod.rs: ~2283 lines
  • src/session.rs: ~2730 lines
  • src/protocol.rs: ~1198 lines
  • src/main.rs: ~55 lines

This supports the current plan direction:

  • CLI decomposition is already mostly underway and should continue.
  • Server, provider, session, and TUI state boundaries remain the most important structural work.
  • The top-level binary entrypoint is already close to the desired thin composition shape.

Current architecture in one picture

flowchart TD
  J[jcode root crate]

  J --> CLI[CLI and startup]
  J --> Server[Server orchestration]
  J --> Session[Session and persistence]
  J --> Agent[Agent turn loop and tools]
  J --> Provider[Provider trait and runtime impls]
  J --> TUI[TUI app and rendering]
  J --> Coreish[Protocol, message, config, ids]
  J --> Product[Auth, memory, safety, ambient, notifications]

  J --> AR[jcode-agent-runtime]
  J --> Emb[jcode-embedding]
  J --> PDF[jcode-pdf]
  J --> Azure[jcode-azure-auth]
  J --> Mail[jcode-notify-email]
  J --> PMeta[jcode-provider-metadata]
  J --> PCore[jcode-provider-core]
  J --> POR[jcode-provider-openrouter]
  J --> PGem[jcode-provider-gemini]
  J --> TW[jcode-tui-workspace]
Loading

Architectural Problems To Solve

1. The root crate is still the product and the platform

Today the root crate acts as all of the following at once:

  • domain model holder
  • runtime orchestrator
  • UI host
  • provider abstraction layer
  • integration shell
  • compile boundary for unrelated edits

That makes it hard to reason about ownership and easy to create accidental coupling.

2. Stable types and high-churn orchestration still live together

Broadly reused types like protocol structures, message forms, IDs, route metadata, and config types should be more stable than server, TUI, or provider orchestration logic. Today many of these still live in the same crate and sometimes in the same dependency fanout path.

3. Some boundary slices exist, but the center remains too wide

The existing workspace crates are good first splits, but they mostly isolate leaves. The center of gravity is still inside the root crate, especially around:

  • session state
  • provider runtime behavior and concrete provider composition
  • server lifecycle
  • tool registry wiring
  • TUI app state and reducers

4. Compile-speed and architecture incentives are the same problem

The compile-performance plan is correct that crate boundaries matter most. The same boundaries that reduce invalidation pressure also improve ownership and testability.

Target Architecture

Layered model

The target is a layered workspace with a thin composition root. Arrows below mean "depends on".

flowchart TD
  App[jcode top-level package]

  subgraph L2[Layer 2: interfaces and product surfaces]
    TUI[jcode-tui]
    SelfDev[jcode-selfdev]
    CLI[jcode-cli or root CLI modules]
  end

  subgraph L1[Layer 1: domain/runtime]
    Server[jcode-server]
    Agent[jcode-agent]
    Provider[jcode-provider]
    Session[jcode-session]
  end

  subgraph L0[Layer 0: foundation and support]
    Core[jcode-core]
    AR[jcode-agent-runtime]
    Emb[jcode-embedding]
    PDF[jcode-pdf]
    Azure[jcode-azure-auth]
    Mail[jcode-notify-email]
    PMeta[jcode-provider-metadata]
    PCore[jcode-provider-core]
    POR[jcode-provider-openrouter]
    PGem[jcode-provider-gemini]
    TW[jcode-tui-workspace]
  end

  App --> Server
  App --> TUI
  App --> SelfDev
  App --> CLI

  CLI --> Server
  CLI --> TUI
  CLI --> Core

  TUI --> Core
  TUI --> TW

  SelfDev --> Server
  SelfDev --> Core

  Server --> Agent
  Server --> Provider
  Server --> Session
  Server --> Core
  Server --> Mail

  Agent --> Provider
  Agent --> Session
  Agent --> Core
  Agent --> AR

  Provider --> Core
  Provider --> PCore
  Provider --> PMeta
  Provider --> POR
  Provider --> PGem
  Provider --> Azure

  Session --> Core
  Session --> Emb
  Session --> PDF
Loading

The exact crate names can evolve, but the dependency direction should not.

Optimal compile-oriented workspace shape

The optimal crate structure is not "one crate per folder". The target should optimize for three forces at the same time:

  1. Invalidation boundaries: high-churn edits should not rebuild unrelated stable subsystems.
  2. Dependency weight boundaries: heavy dependencies should sit behind leaf crates or opt-in features.
  3. Ownership boundaries: each crate should have one reason to change and a small public API.

The current root-crate size distribution makes the main opportunity clear: src/tui, src/server, src/tool, src/provider, src/cli, and src/auth dominate root-crate lines. Splitting only tiny helpers is useful as a safe staging tactic, but the long-term win is moving these high-churn domains behind stable lower-layer contracts.

Desired final crate families

1. Contract/type crates

These crates should be small, low-dependency, and slow-changing. They are allowed to be depended on broadly.

Existing examples:

  • jcode-message-types
  • jcode-tool-types
  • jcode-session-types
  • jcode-config-types
  • jcode-protocol
  • jcode-provider-core
  • jcode-plan
  • jcode-*-types

Target direction:

  • Keep these crates boring and DTO-heavy.
  • Prefer serde, chrono, and small utility dependencies only.
  • Avoid tokio, reqwest, ratatui, provider SDKs, storage paths, and product orchestration.
  • If a type requires a service handle, task runtime, channel sender, or filesystem layout, it is probably not a pure contract type.

Compile-time reason:

  • These crates will be rebuilt whenever public contracts change, so they must change rarely.
  • They allow server, tui, agent, and provider crates to talk without depending on the root crate.

2. Domain/runtime crates

These own product behavior but should depend only downward on contracts/support crates.

Target crates:

  • jcode-provider: provider composition, provider routing, streaming contract adapters, and concrete runtime implementations layered on the jcode-provider-core trait.
  • jcode-agent: turn loop, compaction orchestration, provider/tool interaction, recovery logic.
  • jcode-session: session model, state transitions, persistence-facing session operations.
  • jcode-server: daemon lifecycle, client attachment, swarm/background coordination, service registries.
  • jcode-tools or narrower jcode-tool-core plus jcode-tool-impl: tool registry contracts and tool implementations.
  • jcode-auth: root auth orchestration after provider-neutral data lives in jcode-auth-types and heavy leaf SDKs stay separate.
  • jcode-memory: memory graph/log/search orchestration once its contracts are stable enough.

Compile-time reason:

  • These are the main root invalidation hotspots.
  • They should become independent enough that an edit in TUI rendering does not rebuild provider implementations, and an edit in provider routing does not rebuild server socket lifecycle.

3. Interface/product crates

These are high-churn application surfaces and should sit above runtime/domain crates.

Target crates:

  • jcode-cli: parsing and command dispatch if CLI keeps growing.
  • jcode-tui: app state, reducers, key handling, command/input handling, UI orchestration.
  • jcode-desktop: already a separate surface.
  • jcode-mobile-*: already split.
  • jcode-selfdev: self-dev build/reload/customization workflows if they remain a substantial product surface.

Compile-time reason:

  • UI and CLI are edited frequently. Their churn should not force recompilation of stable server/provider/session internals.
  • TUI should depend on protocol/service contracts, not on concrete server internals.

4. Heavy leaf adapter crates

These should remain isolated and often feature-gated.

Existing examples:

  • jcode-embedding
  • jcode-pdf
  • jcode-azure-auth
  • jcode-notify-email
  • jcode-tui-mermaid
  • provider support crates such as jcode-provider-openrouter and jcode-provider-gemini

Target direction:

  • Keep heavy dependencies out of the root crate and out of broadly shared contracts.
  • Prefer opt-in features when the product can degrade gracefully.
  • Keep a thin root/domain facade when runtime integration still belongs at a higher layer.

Compile-time reason:

  • Heavy crates are fine when cached, but terrible when dragged into unrelated rebuilds.
  • Feature-gated leaves make local inner loops cheaper without removing full-product builds.

5. Composition package

The top-level jcode package should eventually become mostly:

  • binary entrypoints
  • feature defaults
  • runtime graph assembly
  • compatibility re-exports/facades during migration
  • product configuration and packaging defaults

It should not be the long-term home of large implementation modules.

Recommended dependency direction

A healthy final graph should look like this:

jcode binary/composition
  -> jcode-cli, jcode-tui, jcode-server, jcode-selfdev

jcode-cli / jcode-tui
  -> jcode-protocol, jcode-*-types, jcode-server-client contracts

jcode-server
  -> jcode-agent, jcode-session, jcode-provider, jcode-tools, jcode-storage

jcode-agent
  -> jcode-provider, jcode-tools, jcode-session, jcode-agent-runtime

jcode-provider
  -> jcode-provider-core, jcode-provider-* leaves, jcode-auth-types

jcode-session
  -> jcode-session-types, jcode-message-types, jcode-storage, optional leaf adapters

contract/type crates
  -> serde and small support crates only

The forbidden direction is just as important:

  • contract crates must not depend on runtime/domain crates
  • provider crates must not depend on TUI or server crates
  • TUI crates must not depend on concrete server internals when protocol/client contracts are sufficient
  • leaf adapter crates must not become backdoors into the root crate
  • the root crate should not be required by workspace peers except temporarily during migration

Split readiness checklist

A root module is ready to become a crate when most of these are true:

  • Its public API can be described in less than a page.
  • It does not need to call back into arbitrary root modules.
  • Its dependencies are either lower-layer contracts or intentionally owned leaf adapters.
  • Tests can run at the crate level without booting the full product.
  • A touched-file benchmark shows it is on a meaningful invalidation path.
  • It has a stable facade in the root crate for compatibility during migration.

If these are not true yet, keep decomposing internally first.

What not to do

Avoid these tempting but harmful structures:

  • One mega jcode-common crate. It becomes the new root crate and invalidates everything.
  • One crate per source directory. This creates noisy APIs and dependency cycles without compile wins.
  • Moving high-churn traits too early. A poorly stabilized trait crate can become worse than the monolith.
  • Moving UI-adjacent state into core. This contaminates lower layers with ratatui/terminal concepts.
  • Provider leaf crates depending on root. That prevents the root from ever becoming a composition shell.
  • Splitting by dependency weight only. Heavy leaf isolation is good, but ownership and API stability matter too.

Highest-ROI next crate seams from the current tree

Based on the current root size and existing footholds, the best next work is probably:

  1. Provider contracts: keep shrinking src/provider/mod.rs until a jcode-provider trait/runtime crate can depend only on jcode-message-types, jcode-provider-core, and small runtime primitives.
  2. Server core: extract protocol-independent pieces of src/server/ such as client lifecycle state machines, swarm/background coordination DTOs, and reload/update policies behind server-local contracts.
  3. TUI reducer/state core: extract non-rendering app state transitions from src/tui/app/* before moving the whole TUI crate.
  4. Tool contracts and registry shape: separate tool definitions, schemas, execution context, and registry metadata from individual tool implementations.
  5. Session domain: isolate session state transitions and persistence-facing operations from server/TUI/provider orchestration.
  6. Auth facade: keep provider-neutral auth data in jcode-auth-types, heavy SDKs in leaf crates, and move root auth orchestration only after provider contracts stabilize.

A useful near-term policy: every time a large root file is touched, ask whether some pure table, DTO, parser, reducer, classifier, or state transition can move downward into an existing support crate without pulling runtime dependencies with it.

Compile-time success metrics

Each structural phase should record at least:

  • touched-file cargo check for the edited hotspot
  • touched-file selfdev build for the edited hotspot
  • cargo tree -p jcode --edges normal --depth 1 before/after for dependency surprises
  • crate-level test coverage for newly extracted crates

A split is successful if it either:

  • lowers warm touched-file times for common edits, or
  • prevents unrelated heavy crates from rebuilding when the root changes, or
  • makes the next larger extraction materially safer.

A split should be reconsidered if it adds public API churn, creates cycles, or requires broad root re-exports that hide the actual dependency direction.

Target crate responsibilities

jcode-core

Purpose: stable shared types and utilities with minimal dependencies.

Should contain:

  • IDs and naming primitives
  • protocol DTOs that are not server-implementation-specific
  • message/content/tool-definition types shared across runtime layers
  • config primitives and enums that do not require runtime services
  • small shared utility types with high reuse

Should not contain:

  • TUI code
  • server lifecycle code
  • provider network code
  • tokio task orchestration unless truly unavoidable
  • product-specific wiring

Notes:

  • This is the most important future extraction because it enables the rest.
  • src/protocol.rs, src/id.rs, and carefully selected parts of config.rs and message.rs are the likely first feeders.

jcode-session

Purpose: session domain model, persistence, and state transitions.

Should contain:

  • session model and persisted metadata
  • session storage/loading/snapshot logic
  • reducer-like state transitions for session-owned data
  • memory extraction hooks that are session-domain concerns

Should not contain:

  • socket handling
  • TUI state
  • provider HTTP details
  • direct server daemon lifecycle logic

Notes:

  • This crate is not explicitly named in the current compile-performance plan, but the current size and fanout of src/session.rs make session extraction a natural stabilizing move.
  • If introducing jcode-session feels too early, the same boundary should still be established internally first and extracted later.

jcode-provider

Purpose: provider contracts and runtime-facing provider orchestration.

Should contain:

  • the Provider trait once it depends only on lower-layer types
  • provider routing abstractions
  • runtime-facing provider composition
  • shared streaming abstractions for provider results

Should not contain:

  • provider-specific heavy catalogs and schema helpers that already live well in leaf crates
  • server or TUI logic

Notes:

  • Existing crates jcode-provider-core, jcode-provider-metadata, jcode-provider-openrouter, and jcode-provider-gemini remain useful under this layer.
  • The key migration step is shrinking the Provider trait's dependency surface so it no longer depends on root-crate-only message/runtime types.

jcode-agent

Purpose: agent turn engine and tool orchestration.

Should contain:

  • turn-loop engine
  • stream handling and response recovery
  • tool execution orchestration
  • compaction integration
  • prompt assembly inputs that are agent-domain concerns

Should not contain:

  • server socket lifecycle
  • TUI state
  • provider-specific leaf implementations

Notes:

  • This aligns directly with the refactoring roadmap's "Agent Turn-Loop Unification" phase.
  • jcode-agent-runtime remains the low-level runtime primitive crate below it.

jcode-server

Purpose: daemon lifecycle and multi-client coordination.

Should contain:

  • socket listeners and debug socket handling
  • client attach/detach lifecycle
  • swarm coordination
  • reload/update server behaviors
  • server-owned registries and shared service wiring

Should not contain:

  • TUI rendering
  • provider implementation details beyond service interfaces
  • session persistence internals that belong in jcode-session

Notes:

  • The current src/server/ submodule tree is already the right shape for this extraction.
  • src/server.rs should continue shrinking into a facade/composition module.

jcode-tui

Purpose: client UI state, reducers, and rendering.

Should contain:

  • app state and reducers
  • remote client behavior and reconnect logic
  • renderer/widget orchestration
  • TUI-specific command/input handling

Should not contain:

  • server daemon code
  • session persistence internals
  • provider network logic

Notes:

  • This aligns directly with the refactoring roadmap's "TUI State/Reducer Split" phase.
  • jcode-tui-workspace can remain a leaf crate or become a child dependency of jcode-tui.

jcode-selfdev

Purpose: self-dev workflows, customization records, reload/build productization.

Should contain:

  • self-dev state and tooling policy
  • build/reload orchestration specific to self-dev workflows
  • customization record and migration logic as it lands

Should not contain:

  • generic server lifecycle not specific to self-dev
  • general TUI rendering

Notes:

  • This aligns with the compile-performance plan's issue-#32 direction and with the already-unified shared-server model.

jcode top-level package

Purpose: composition root and shipping product package.

Should eventually be responsible for:

  • binary entrypoints
  • feature/default selection
  • wiring the runtime graph together
  • packaging and product defaults

It should not remain the long-term home of most implementation logic.

Dependency Rules

These rules are the core of the RFC.

Rule 1: Dependencies flow downward only

A higher layer may depend on a lower layer. A lower layer may not depend on a higher layer.

  • foundation cannot depend on domain/runtime, interfaces, or product crates
  • domain/runtime cannot depend on TUI or self-dev UI/product layers
  • leaf adapters must not pull UI or server concerns downward

Rule 2: No TUI types below the interface layer

  • ratatui, crossterm, renderer state, viewport state, widget models, and clipboard/image/UI helper types must stay out of server, agent, provider, and core crates
  • server-to-client data crosses the boundary via protocol/event types, not TUI structs

Rule 3: No server daemon types in core or provider-support crates

  • socket/session attachment state, fanout senders, debug socket helpers, and daemon lifecycle code must not appear in jcode-core, jcode-provider-core, or provider leaf crates

Rule 4: Provider implementation crates depend on contracts, not on the server or TUI

  • provider leaf crates may depend on jcode-core, jcode-provider, and jcode-provider-core
  • they must not depend on jcode-server or jcode-tui

Rule 5: Async/network-heavy dependencies do not belong in jcode-core

jcode-core should stay cheap to compile and highly reusable.

Avoid putting these there unless absolutely necessary:

  • reqwest
  • provider SDKs
  • UI crates
  • ONNX/tokenizer stacks
  • mail/PDF dependencies

Rule 6: Stable contracts should change more slowly than orchestration

Before extracting a crate, first shrink and stabilize its public surface.

Examples:

  • move pure data types before moving stateful runtime code
  • move pure helper functions before moving integration shells
  • keep facades in the root crate during transitions if they reduce churn

Rule 7: Avoid cross-cutting "utils" crates

Do not create a dumping-ground crate.

If code has a clear owner, it belongs with that owner:

  • protocol/data types -> jcode-core
  • session persistence -> jcode-session
  • provider route/schema helpers -> provider crates
  • rendering helpers -> jcode-tui

Rule 8: The root package may compose many crates, but peer crates should stay narrow

The top-level jcode package can wire multiple domains together. Peer crates should not casually depend on each other sideways when a lower-level contract would do.

Rule 9: New crate boundaries should follow both ownership and invalidation logic

A crate split is worth doing when it improves at least one of these substantially, and ideally both:

  • clearer ownership and testability
  • lower compile invalidation for common edits

Rule 10: Preserve behavior with facades during migration

During migration, it is acceptable for the root crate to keep temporary facade modules that re-export or forward into extracted crates. That is preferable to risky behavior changes.

Recommended Target Mapping From Today's Code

This is the recommended direction from the current tree, not a one-shot move list.

Current area Likely target
src/id.rs, protocol/message/config primitives jcode-core
src/session.rs, parts of storage, restart snapshot concerns jcode-session
src/agent/*, parts of compaction, tool orchestration seams jcode-agent
src/server/ + shrinking src/server.rs facade jcode-server
src/provider/mod.rs trait/contracts plus provider composition seams jcode-provider
existing provider helper crates remain leaf/provider support crates
src/tui/* + jcode-tui-workspace jcode-tui + leaf workspace widget crate
src/cli/* stay in root initially or become jcode-cli later if justified
src/tool/selfdev/*, self-dev workflow/productization jcode-selfdev

Phased Migration Plan

This migration is intentionally incremental and aligned with existing docs.

Phase 0: Codify the architecture now

Deliverables:

  • this RFC
  • cross-links from refactoring and compile-performance docs
  • dependency rules documented before more splits land

Why now:

  • the repo already has enough workspace structure that undocumented drift is becoming more expensive

Phase 1: Finish internal module decomposition in the root crate

Aligns with REFACTORING.md phases 2 through 6.

Focus areas:

  • continue CLI decomposition until main() stays parse + runtime bootstrap only
  • continue shrinking src/server.rs into a thin facade over src/server/*
  • unify agent turn-loop variants behind one engine
  • continue TUI state/reducer separation
  • continue provider state isolation and pure helper extraction

Exit criteria:

  • root modules are organized by ownership, not by convenience
  • candidate extraction seams are obvious and lower-risk

Phase 2: Extract jcode-core

This is the highest-leverage shared boundary.

First moves should be narrow and stable:

  • IDs
  • small protocol DTOs
  • tool definition and message content forms that are broadly shared
  • config enums/primitives that do not need runtime services

Avoid moving unstable orchestration APIs too early.

Exit criteria:

  • server, agent, provider, and TUI code can all depend on the same lower-level shared types without depending on the root crate

Phase 3: Extract runtime/domain crates

Primary targets:

  1. jcode-provider
  2. jcode-agent
  3. jcode-server
  4. jcode-session

Recommended order:

  • start with whichever boundary is already most internally modular after Phase 1
  • in practice, provider and server look like the strongest current candidates because they already have meaningful submodule trees and leaf support crates
  • session may remain internal slightly longer if its public surface is still too entangled

Exit criteria:

  • the root crate no longer defines the main provider, server, and agent contracts directly

Phase 4: Extract jcode-tui

Focus:

  • move client app/reducer/rendering code out of the root crate once protocol and runtime service boundaries are stable
  • keep server events and client view-state concerns separated by protocol types

This phase should happen after enough shared contract extraction exists to avoid TUI depending back on root implementation details.

Exit criteria:

  • TUI can evolve rapidly without dragging broad server/provider recompilation

Phase 5: Extract jcode-selfdev

Focus:

  • isolate self-dev workflow code and future customization/productization work
  • keep shared-server runtime behavior intact
  • move issue-#32 style no-rebuild customization logic here when it becomes concrete

Exit criteria:

  • self-dev product behavior is explicit and no longer scattered across server/CLI/tool glue

Phase 6: Shrink the root package into a composition shell

Desired end state:

  • src/main.rs remains thin
  • jcode::run() is mostly wiring
  • the top-level package primarily assembles runtime services and default product configuration

Continuous work across all phases

These should continue throughout the migration:

  • keep carving heavy leaf dependencies into workspace crates where boundaries are safe
  • measure touched-file compile timings after structural changes
  • protect behavior with facades, tests, and refactor verification scripts
  • prefer data-driven customization over source edits where issue #32 applies

Migration Priorities

If we must prioritize, use this order:

  1. stabilize and extract shared lower-level types
  2. keep shrinking server/provider/session/agent hotspots internally
  3. extract runtime contracts and orchestration crates
  4. extract TUI
  5. extract self-dev productization

This ordering gives the best overlap between architecture safety and compile-speed payoff.

Acceptance Criteria

We should consider this RFC materially implemented when most of the following are true:

  • the root package is primarily a composition shell
  • shared cross-cutting types live in a lower-level crate rather than the root crate
  • server, agent, provider, and TUI have clear ownership boundaries
  • provider support crates no longer need root-crate-only types
  • TUI depends on protocol/service contracts rather than runtime internals
  • common self-dev edits avoid recompiling unrelated heavy subsystems whenever possible
  • architecture docs match the actual crate graph

Practical Guidance For Future Changes

When deciding where new code should go:

  1. Ask who owns the behavior.
  2. Ask which layers should be allowed to know about it.
  3. Ask whether putting it in the root crate will increase invalidation for unrelated edits.
  4. Prefer the narrowest stable owner that does not create an artificial abstraction.

Short version:

  • if it is shared data, push downward
  • if it is orchestration, keep it above stable contracts
  • if it is UI, keep it out of runtime crates
  • if it is heavy and isolated, make it a leaf crate

Open Questions

These do not block the RFC, but they should be revisited as migration proceeds:

  • Should jcode-session become an explicit crate, or remain an internal boundary until later?
  • Should CLI remain in the top-level package permanently, or eventually become jcode-cli?
  • Should message and protocol remain together in jcode-core, or split into separate contract crates if they evolve at different rates?
  • Should jcode-tui-workspace remain a separate leaf crate long-term, or fold into jcode-tui once the larger TUI extraction lands?

Recommendation

Adopt this RFC as the architectural north star for refactors and crate splits.

In practice that means:

  • keep following the current refactoring roadmap
  • keep using the compile-performance plan's measured, crate-boundary-first strategy
  • treat every new extraction as part of one layered architecture, not as an isolated cleanup