Code map
This page helps you find where your change goes, and follow the conventions already there.
This is the contributor-facing counterpart to the maintainer documentation: where the code lives and how it’s wired, rather than what the harness does at runtime.
Core Principles
Jazz is a Bun workspace under packages/*. The dependency rule below is not just documented
convention: it’s structurally enforced by TypeScript project references, so tsc -b rejects a
package importing from one it doesn’t declare a reference to.
packages/core/contains the domain, contracts (interfaces and types), and business logic.- No imports from
packages/adapters/orpackages/cli/allowed in core, except in tests. - Contracts are expressed as interfaces + Context tags (e.g.,
AgentConfigServiceTag). - Publishable standalone as
@jazz/core: no workspace dependencies of its own.
- No imports from
packages/plugin-sdk/is the zero-runtime-dependency public TypeScript contract for plugin authors. Core structurally implements that plain-JavaScript ABI without exposing Effect values.packages/adapters/implements adapters for model providers, configuration, storage, files, MCP, telemetry, notifications, peers, and webhooks, plus the goal and loop cycle runners and run-ownership primitives that the CLI and the daemon both use.- Adapters provide Layers that satisfy the tags declared in
core/interfaces. - Depends on
coreonly.
- Adapters provide Layers that satisfy the tags declared in
packages/daemon/is the daemon application: the authenticated HTTP server, the trigger/goal/loop/job ticks, attention gating, and OS service install.- Depends on
coreandadapters; nothing below it imports it (an eslint rule andscripts/package-layering.test.tsenforce that).
- Depends on
packages/cli/contains user-facing command implementations, Ink/OpenTUI presentation, and the terminal-renderingTerminalServiceimplementation.- Depends on
core,adapters,daemon,bot-shared, and the bridge packages it launches.
- Depends on
packages/runtime/is the composition root: wires core, adapters, and cli into the Effect Layer graph that becomes thejazzbinary.- Depends on
core,adapters, andcli.
- Depends on
packages/bot-shared/,packages/telegram-bot/,packages/discord-bot/,packages/whatsapp-bot/,packages/imessage-bot/, andpackages/photon-bot/are the chat-bridge integrations.bot-sharedholds the turn, approval, and sandbox helpers the bridges share.packages/website/is the Astro site that serves the public docs, the marketplace library, and the plugin catalog.
The dependency rule
flowchart TB
CLI["<b>packages/cli/</b><br/>commands · Ink TUI · presentation · TerminalService"]
CORE["<b>packages/core/</b><br/>agent loop · tools · context · types<br/><b>interfaces = ports</b><br/><i>imports nothing outward</i>"]
ADP["<b>packages/adapters/</b><br/>llm · storage · mcp · history<br/>logger · telemetry · notification"]
DMN["<b>packages/daemon/</b><br/>HTTP server · ticks · attention · service install"]
RT["<b>packages/runtime/</b><br/>composition root · jazz binary"]
CLI -->|"calls"| CORE
ADP -->|"implements ports"| CORE
DMN -->|"runs on"| ADP
CLI -->|"jazz daemon"| DMN
RT -.->|"merges Layers at startup"| CLI
RT -.->|"merges Layers at startup"| ADP
RT -.->|"merges Layers at startup"| CORE
NO["core/ → adapters/ or cli/<br/><b>never</b> (except in tests)<br/>adapters/ → daemon/ <b>never</b>"]
classDef core fill:#4f9d9d,stroke:#2f6d6d,color:#ffffff
classDef forbidden fill:#c1443c,stroke:#7d2b26,color:#ffffff
class CORE core
class NO forbidden
Arrows point inward, always. That’s what lets you swap a storage backend or add an LLM provider by touching one package, and test the agent loop with plain mocks.
Adding a capability follows the arrows in reverse:
flowchart LR
A["1 · Define the port<br/>packages/core/src/interfaces/foo.ts<br/>interface + Context.GenericTag"]
B["2 · Implement the adapter<br/>packages/adapters/src/foo.ts<br/>+ a Layer"]
C["3 · Register the Layer<br/>packages/runtime/src/app-layer.ts"]
D["4 · Test with a mock Layer<br/>Layer.succeed(FooTag, fake)"]
A --> B --> C --> D
classDef step fill:#4f9d9d,stroke:#2f6d6d,color:#ffffff
class A,B,C,D step
Directory Structure
packages/
├── cli/src/ # @jazz/cli: user-facing CLI
│ ├── commands/ # Command entry points and orchestration
│ ├── setup/ # Shared interactive provider/search configuration flows
│ ├── helpers/ # Narrow detection, parsing and formatting operations
│ ├── presentation/ # Output formatting (markdown, CLI renderer)
│ ├── chat-service.ts # Chat orchestrator (UI-touching; lives here, not adapters)
│ ├── chat/ # Chat service modules
│ │ ├── commands/ # Slash command handling
│ │ │ ├── parser.ts # Parse /help, /new, etc.
│ │ │ ├── handler.ts # Route commands and apply shared command behavior
│ │ │ └── conversation.ts # Start, fork, resume and rename conversations
│ │ └── session/ # Session management
│ │ ├── manager.ts # ID generation, logging
│ │ └── agent-setup.ts # MCP connection setup
│ ├── terminal.ts # TerminalService implementation (Ink/OpenTUI rendering)
│ └── ui/ # Ink React components
│ ├── App.tsx # Main app with store pattern
│ ├── ErrorBoundary.tsx # Error boundary for graceful failures
│ ├── LineInput.tsx # Readline-style input component
│ └── text-utils.ts # Word boundary utilities
│
├── core/src/ # @jazz/core: domain and contracts
│ ├── agent/ # Agent execution engine
│ │ ├── agent-runner.ts # Orchestrator (delegates to executors)
│ │ ├── types.ts # Shared types (AgentRunnerOptions, etc.)
│ │ ├── context/ # Context management
│ │ │ └── summarizer.ts # Auto-summarization for context window
│ │ ├── execution/ # LLM execution strategies
│ │ │ ├── streaming-executor.ts # Real-time streaming
│ │ │ └── batch-executor.ts # Non-streaming execution
│ │ ├── prompts/ # System prompts by agent type
│ │ └── tools/ # Tool implementations
│ │ ├── fs/ # Filesystem tools (read, write, grep, etc.)
│ │ ├── command-risk.ts # execute_command risk classifier (LLM)
│ │ ├── tool-categories.ts # Builtin category ids + mappings
│ │ ├── register-tools.ts # Builtin tool registration
│ │ └── register-mcp-tools.ts # Per-agent MCP connect + register
│ ├── interfaces/ # Service contracts (Tag + Interface)
│ ├── types/ # Domain types
│ └── utils/ # Shared utilities
│
├── adapters/src/ # @jazz/adapters: adapter implementations
│ ├── llm/ # LLM provider adapters
│ ├── mcp/ # MCP client + OAuth
│ ├── peers/ # ask_peer ledger/token adapters
│ ├── goals/, loops/ # Goal/loop actions and cycle runners
│ ├── runs/ # Run ownership: in-flight set, resume-owned-run
│ └── storage/ # Persistence (JSON file storage)
│
├── daemon/src/ # @jazz/daemon: HTTP server, ticks, attention, service install
│
├── runtime/src/ # @jazz/runtime: composition root
│ ├── entry.ts # Binary entrypoint
│ ├── cli-app.ts # Commander.js program, command registration
│ └── app-layer.ts # Effect Layer composition
│
├── bot-shared/src/ # @jazz/bot-shared: shared bridge helpers
├── telegram-bot/src/ # Telegram bridge
├── discord-bot/src/ # Discord bridge
├── whatsapp-bot/src/ # WhatsApp bridge (linked device, Baileys)
├── imessage-bot/src/ # iMessage bridge (local Mac, imsg CLI)
├── photon-bot/src/ # iMessage bridge (hosted Photon line)
└── website/ # Astro site: docs, marketplace library, plugin catalog
Key modules
Agent Runner (packages/core/src/agent/)
The agent runner is split into focused modules; runtime behavior is documented in Agent loop and Context management.
| Module | Purpose |
|---|---|
agent-runner.ts | Orchestrator - delegates to executors |
types.ts | Shared types: AgentRunnerOptions, AgentResponse, AgentRunContext |
context/summarizer.ts | Auto-compaction when context approaches token limit |
execution/streaming-executor.ts | Real-time LLM streaming with tool calls |
execution/batch-executor.ts | Non-streaming execution with retry logic |
Dependency Injection Pattern: To avoid circular dependencies, the summarizer.ts accepts a RecursiveRunner function parameter instead of importing AgentRunner directly.
Prompt assembly
AgentPromptBuilder assembles system prompts through three semantic layers:
- Core contains the persona and fixed Jazz harness guidance.
- Scope contains instructions belonging to the current project or task.
- Live contains runtime facts that may change while the agent runs, such as its environment.
prompts/layers.ts owns their Core → Scope → Live rendering order. Keep new prompt content in
the narrowest-lived layer that accurately describes it. Tool-specific operating details belong in
the tool description rather than any shared layer.
Chat Service (packages/cli/src/chat/)
The chat service is split into focused modules:
| Module | Purpose |
|---|---|
chat-service.ts | Session orchestrator |
commands/parser.ts | Parse slash commands from user input |
commands/handler.ts | Route commands and shared command behavior |
commands/conversation.ts | Start, fork, resume and rename conversations |
commands/types.ts | SpecialCommand, CommandResult types |
session/manager.ts | Session ID generation, logging |
session/agent-setup.ts | MCP server connections before chat |
Common Conventions
Service Contracts
A service contract is an interface + a Context tag, defined under packages/core/src/interfaces/.
// packages/core/src/interfaces/agent-config.ts
export interface AgentConfigService {
getConfig(): Effect.Effect<AgentConfig, Error>;
}
export const AgentConfigServiceTag = Context.GenericTag<AgentConfigService>("AgentConfigService");
Using Services in Effect
const config = yield * AgentConfigServiceTag;
const value = yield * config.getConfig();
Providing Layers
Layer.effect(AgentConfigServiceTag, Effect.succeed(new ConfigServiceImpl(...)))
How to Add a New Adapter/Service
- Add the contract to
packages/core/src/interfaces/(interface + Tag). - Implement the adapter in
packages/adapters/src/and create a Layer. - Add registration in
packages/runtime/src/app-layer.tsby merging the new Layer. - Add tests with a mock Layer.
Testing Patterns
Pure Function Tests
For utilities like parseSpecialCommand or generateSessionId:
import { describe, expect, it } from "bun:test";
import { parseSpecialCommand } from "./parser";
describe("parseSpecialCommand", () => {
it("should parse /help command", () => {
const result = parseSpecialCommand("/help");
expect(result.type).toBe("help");
});
});
Effect Tests with Mocked Layers
const mockLogger: LoggerService = {
debug: () => Effect.void,
info: () => Effect.void,
// ...
};
const testLayer = Layer.succeed(LoggerServiceTag, mockLogger);
const result = await Effect.runPromise(myEffect.pipe(Effect.provide(testLayer)));
UI Architecture
@jazz/cli projects one semantic presentation document into OpenTUI’s fullscreen
interface or the classic Ink interface. Effect services mutate the presentation
store; React consumes committed snapshots. Stable source IDs survive streaming,
settlement and resume. Geometry, scroll position, prompt continuations and secret
drafts remain outside persisted entries.
See Conversation presentation for ownership, source links, and the version 3 history migration. Use the terminal rendering tests to compare the emitted terminal screen with the renderer’s intended buffer.
Why This Structure
- Separates policy (core) from mechanics (adapters): makes it easy to:
- Swap LLM providers
- Substitute storage backends
- Test core logic with deterministic mocks
- Good for open-source: Contributors can implement providers/adapters without changing core logic.
Troubleshooting
- Missing tag at runtime: Ensure the Layer providing that tag is included in
createAppLayer. - Circular dependency: Use dependency injection (pass functions as parameters) instead of direct imports.
- Context overflow: The
Summarizerautomatically compacts context when tokens approach 80% of the limit.