Skip to content

t3code

t3code is a web GUI for coding agents. Its backend is not CRUD-shaped. It is built from Effect layers, evented reactors, provider registries, callback bridges, orchestration services, and projection pipelines.

That makes it a good case study for the analyzer, because the hard part is not finding isolated Effects. It is recovering architecture from a large backend that is composed through layers and higher-order services.

This walkthrough is based on the current analyzer output against the real t3code repository.

Running the coverage audit on the server source gives a good top-level picture:

Terminal window
npx effect-analyze ./apps/server/src \
--coverage-audit \
--show-by-folder \
--quiet

Current output:

Discovered: 189
Analyzed: 142
Zero programs: 47
Suspicious zeros: 0
Failed: 0
Coverage: 75.1%
Analyzable coverage: 100.0%
Unknown node rate: 1.46%
Top unknown node reasons (by count):
71 Could not determine effect type
16 Could not determine loop body

The most important signals here are:

  • 142 analyzable programs in the server source
  • 0 failures
  • 0 suspicious zeros
  • 1.46% unknown-node rate

That is already enough to trust the analyzer as a repo navigation tool. The remaining misses are concentrated, not random.

Layer architecture is the strongest repo-wide view

Section titled “Layer architecture is the strongest repo-wide view”

t3code server code is heavily layer-driven, so architecture mode is more useful than a raw per-file flowchart:

Terminal window
npx effect-analyze ./apps/server/src \
--format architecture \
--no-colocate \
--quiet

Current output starts like this:

Project architecture (0 runtimes, 76 layer assemblies)
Layer assemblies:
OrchestrationEngineLive (orchestration/Layers/OrchestrationEngine.ts)
Ops: effect
References: OrchestrationEngineService, makeOrchestrationEngine
OrchestrationProjectionPipelineLive (orchestration/Layers/ProjectionPipeline.ts)
Ops: effect(...).pipe -> provideMerge -> provideMerge -> provideMerge ...
References: NodeServices.layer, ProjectionProjectRepositoryLive, ProjectionThreadRepositoryLive, ...
ProviderRegistryLive (provider/Layers/ProviderRegistry.ts)
Ops: effect(...).pipe -> provideMerge -> provideMerge
References: CodexProviderLive, ClaudeProviderLive
RuntimeLayer (index.ts)
Ops: empty.pipe -> provideMerge -> provideMerge -> provideMerge -> ...
References: CliConfig.layer, ServerLive, OpenLive, NetService.layer, NodeServices.layer, FetchHttpClient.layer

For this codebase, that is the right starting point. The analyzer shows how the server is assembled:

  • orchestration services
  • persistence repositories
  • provider adapters and registries
  • runtime receipt bus
  • terminal integration
  • top-level runtime composition

This is much closer to how a contributor actually needs to read the backend.

Small architecture files are very readable

Section titled “Small architecture files are very readable”

serverLayers.ts is a good example of where architecture mode is already concise:

Terminal window
npx effect-analyze ./apps/server/src/serverLayers.ts \
--format architecture \
--tsconfig ./apps/server/tsconfig.json \
--no-colocate \
--quiet
Project architecture (0 runtimes, 2 layer assemblies)
Layer assemblies:
runtimeServicesLayer (serverLayers.ts)
Ops: mergeAll
References: orchestrationLayer, OrchestrationProjectionSnapshotQueryLive, checkpointStoreLayer, checkpointDiffQueryLayer, RuntimeReceiptBusLive
serverLayers-layer-2 (serverLayers.ts)
Ops: mergeAll(
orchestrationReactorLayer,
GitCoreLive,
gitManagerLayer,
terminalLayer,
KeybindingsLive,
).pipe -> provideMerge
References: NodeServices.layer

This is exactly the kind of file where the analyzer helps immediately: it shows which layer groups exist and what they depend on, without making you read every import and provideMerge manually.

Callback-heavy code is one of the places where the analyzer is useful in t3code.

Take the bootstrap reader:

Terminal window
npx effect-analyze ./apps/server/src/bootstrap.ts \
--format explain \
--tsconfig ./apps/server/tsconfig.json \
--quiet

Current output:

readBootstrapEnvelope (direct):
1. Yields fdReady <- isFdReady
2. Yields stream <- makeBootstrapInputStream
3. Returns:
Pipes callback through:
Registers callback bridge: callback
Callback: 5 resume calls
Inner effects:
Calls cleanup — callback-handler
Calls handleError — callback-handler
Callback:
Calls resume -> Effect.succeedNone — callback-resume
Calls resume -> Effect.fail(...) — callback-resume
Calls isUnavailableBootstrapFdError — callback-call
Calls handleLine — callback-handler
Callback:
Calls resume -> Effect.succeedSome(parsed.success) — callback-resume
Calls resume -> Effect.fail(...) — callback-resume
Calls decodeJsonResult — callback-call
Calls Result.isSuccess — callback-call
Calls handleClose — callback-handler
Callback:
Calls resume -> Effect.succeedNone — callback-resume
Times out after timeoutMs
Transforms via map

This is much better than a flat Calls fn or generic Effect.callback entry. The analyzer now shows:

  • that this is a callback bridge
  • how many resume paths exist
  • the named handlers inside the bridge
  • the important resume payloads
  • the timeout and transform wrappers around it

For t3code, that is exactly the kind of explanation that makes async boundary code readable.

The Mermaid output is still useful when you want the condensed shape instead of the handler detail:

Terminal window
npx effect-analyze ./apps/server/src/bootstrap.ts \
--format mermaid \
--tsconfig ./apps/server/tsconfig.json \
--quiet
flowchart TB

  %% Program: readBootstrapEnvelope

  start((Start))
  end_node((End))

  n1["fn"]
  n3["fdReady <- isFdReady (side-effect)"]
  n4["stream <- makeBootstrapInputStream"]
  n5["return"]
  term_6(["return"])
  n7["Pipe (2 steps)"]
  n8["callback"]
  n10["Effect"]
  timeout_11["Timeout(timeoutMs)"]
  n12["map (transform)"]

  %% Edges
  n3 --> n4
  n8 --> n10
  n10 --> timeout_11
  timeout_11 --> n12
  n7 --> n8
  n5 --> n7
  n12 --> term_6
  n4 --> n5
  n1 --> n3
  start --> n1
  n3 --> end_node

  %% Styles
  classDef startStyle fill:#c8e6c9,stroke:#2e7d32
  classDef endStyle fill:#ffcdd2,stroke:#c62828
  classDef effectStyle fill:#90EE90,stroke:#333,stroke-width:2px
  classDef pipeStyle fill:#ADD8E6,stroke:#333,stroke-width:2px
  classDef timeoutStyle fill:#87CEEB,stroke:#333,stroke-width:2px
  classDef terminalStyle fill:#FF6B6B,stroke:#333,stroke-width:2px
  classDef transformStyle fill:#A5D6A7,stroke:#388E3C,stroke-width:2px
  class start startStyle
  class end_node endStyle
  class n1 effectStyle
  class n3 effectStyle
  class n4 effectStyle
  class n5 terminalStyle
  class term_6 terminalStyle
  class n7 pipeStyle
  class n8 effectStyle
  class n10 effectStyle
  class timeout_11 timeoutStyle
  class n12 transformStyle

Provider registry code is another place where the analyzer now recovers useful stream structure:

Terminal window
npx effect-analyze ./apps/server/src/provider/Layers/ProviderRegistry.ts \
--format explain \
--tsconfig ./apps/server/tsconfig.json \
--quiet
ProviderRegistryLive (generator):
1. Yields codexProvider <- CodexProvider
2. Yields claudeProvider <- ClaudeProvider
3. changesPubSub = Acquires resource:
pubsub.create
Then releases:
Calls PubSub.shutdown
4. Yields providersRef <- make
5. Background stream reactor (CodexProvider.streamChanges): runForEach -> runForEach
Calls CodexProvider.streamChanges — service-call
runForEach callback:
Calls syncProviders — callback-call
6. Background stream reactor (ClaudeProvider.streamChanges): runForEach -> runForEach
Calls ClaudeProvider.streamChanges — service-call
runForEach callback:
Calls syncProviders — callback-call

That is a real semantic improvement. The analyzer now understands that these are background reactors driven by provider change streams, not just anonymous stream pipelines.

The nested refresh program is also clearer now:

ProviderRegistryLive.refresh (generator):
1. Switch on provider:
Case "codex":
Calls CodexProvider.refresh — service-call
Case "claudeAgent":
Calls ClaudeProvider.refresh — service-call
Case default:
Runs 2 effects in sequential (concurrency: unbounded):
Calls CodexProvider.refresh — service-call
Calls ClaudeProvider.refresh — service-call
2. Returns:
Calls syncProviders

Earlier versions of the analyzer lost these service-property calls entirely.

For this file, Mermaid now captures the reactor and refresh structure reasonably well:

Terminal window
npx effect-analyze ./apps/server/src/provider/Layers/ProviderRegistry.ts \
--format mermaid \
--tsconfig ./apps/server/tsconfig.json \
--quiet
flowchart TB

  %% Program: ProviderRegistryLive.refresh

  start((Start))
  end_node((End))

  n2["Switch: provider"]
  switch_3{"Switch: provider"}
  n4["codexProvider.refresh (service-call)"]
  n5["claudeProvider.refresh (service-call)"]
  n6["Effect.all (2) (concurrency)"]
  parallel_fork_7{{"All (2)"}}
  parallel_join_7{{"Join"}}
  n8["codexProvider.refresh (service-call)"]
  n9["claudeProvider.refresh (service-call)"]
  n10["return"]
  term_11(["return"])
  n12["syncProviders (side-effect)"]

  %% Edges
  switch_3 -->|'codex'| n4
  switch_3 -->|'claudeAgent'| n5
  n6 --> parallel_fork_7
  parallel_fork_7 -->|codexProvider.refresh| n8
  n8 --> parallel_join_7
  parallel_fork_7 -->|claudeProvider.refresh| n9
  n9 --> parallel_join_7
  switch_3 -->|default| n6
  n10 --> n12
  n12 --> term_11
  n4 --> n10
  n5 --> n10
  parallel_join_7 --> n10
  start --> switch_3
  switch_3 --> end_node

  %% Styles
  classDef startStyle fill:#c8e6c9,stroke:#2e7d32
  classDef endStyle fill:#ffcdd2,stroke:#c62828
  classDef effectStyle fill:#90EE90,stroke:#333,stroke-width:2px
  classDef parallelStyle fill:#FFA500,stroke:#333,stroke-width:2px
  classDef switchStyle fill:#FFD700,stroke:#333,stroke-width:2px
  classDef terminalStyle fill:#FF6B6B,stroke:#333,stroke-width:2px
  class start startStyle
  class end_node endStyle
  class n2 switchStyle
  class switch_3 switchStyle
  class n4 effectStyle
  class n5 effectStyle
  class n6 parallelStyle
  class parallel_fork_7 parallelStyle
  class parallel_join_7 parallelStyle
  class n8 effectStyle
  class n9 effectStyle
  class n10 terminalStyle
  class term_11 terminalStyle
  class n12 effectStyle
  • recovering large-scale layer composition across the backend
  • identifying repository/service live layers and their references
  • explaining callback bridges like Effect.callback(...)
  • recognizing service-backed property effects such as provider.refresh
  • recognizing background stream reactors such as runForEach(...).pipe(Effect.forkScoped)
  • producing repo-level coverage metrics that are low-noise enough to guide further work

The remaining misses are visible in the coverage report:

  • Could not determine effect type is still the top unknown reason
  • some higher-order callback bodies still compress to generic loop or callback summaries
  • many zero-program files are legitimate service contracts, schema files, or test helpers rather than missed programs

That means the next wins are mostly semantic depth, not broad discovery:

  • better callback-body inference
  • richer loop-body summaries
  • better handling of service-contract and schema-only files in coverage reporting

For this repository, the best workflow is:

  1. run --coverage-audit --show-by-folder on the server source
  2. run --format architecture on the server source or on layer-heavy directories
  3. drill into individual files like bootstrap.ts or ProviderRegistry.ts with --format explain

That matches the current analyzer well. t3code is not primarily a “show me one pretty flowchart” codebase. It is a “help me understand the architecture, then zoom in” codebase.