agent-joule/dev-docs/ARCHITECTURE.md
David Charlot 88a41bedd3 Rebrand Zed -> Joule across UI, docs, embedded assets, and legal
The fork shipped renamed binary/bundle but the product name "Zed" was still
shown everywhere in the UI. Rename the display name to "Joule" (tagline
"Energy-metered agentic coding") while preserving every load-bearing reference.

Display renames (105 .rs files): release_channel display_name, welcome +
onboarding + ai_onboarding, macOS app menu (About/Hide/Quit), window titles,
About panel, settings UI, notifications, update/collab messages, system specs.
Bundle display name (crates/zed/Cargo.toml) -> "Joule" so CFBundleName / the
menu-bar name and the .app are "Joule" (identifier science.transaction.AgentJoule
and the agent-joule binary are unchanged).

Docs (223 .md): product prose Zed -> Joule, fence-aware (code blocks untouched).
Embedded assets (12 JSONC): // comments only.
Legal (4 files): product -> Joule, entity "Zed Industries, Inc." -> "Transaction
Science, Inc.", domain zed.dev -> openie.dev. NOTE: these are structurally Zed's
inherited terms and need a real legal review, not just a rename.

Deliberately PRESERVED (renaming would break the app): font aliases ("Zed Plex
Sans"/".ZedMono" in text_system + assets), HTTP header names (X-Zed-Predict-*),
zed:: action ids, app/bundle identifiers (dev.zed.Zed*, Zed-Editor-* product ids),
zed.dev URLs, ZED_* env vars, crate/package names, "Zed Industries" in non-legal
attribution, and the {zed,Zed} config-dir glob.

Signed + notarized + stapled as Joule.app, installed to /Applications, verified
(Gatekeeper: Notarized Developer ID, FH93XC2VRR). Caveat: test-code string
literals were renamed too, so some `cargo test` assertions may need updating.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-03 02:23:35 -04:00

98 lines
8.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Architecture & Integration
> Generated 2026-06-14. How `agent_joule_panel` wires together internally, to the Joule host, and to its 16 external deps. file:line against audited HEAD.
## Component map
```
crates/zed/src/main.rs:770 agent_joule_panel::init(cx)
crates/zed/src/zed.rs:704 AgentJoulePanel::load(...) (initialize_panels)
│
init(cx) [agent_joule_panel.rs:84] AgentJoulePanel (struct :177) owns:
├─ control::init_control_server(cx) l0_cache: L0Cache (joule-code-cascade)
├─ observe_new<Workspace> → ToggleFocus l3_model: Option<Arc<L3Model>> (jouleclaw-runtime/loader)
└─ observe_new<Editor> → LocalFimDelegate cascade_gate: CascadeGate (cascade_gate.rs / joule-code-jcp)
hooks: HookRegistry (hooks.rs)
methodology: MethodologyTracker (methodology.rs)
session_mgr: SessionManager (sessions.rs)
energy_counter: EnergyCounter (jouleclaw-energy)
```
## 1. Cascade data flow
Entry **`submit_prompt`** (`agent_joule_panel.rs:1600`). Each tier guarded by `authorize_tier(tier, intent)`; first to close wins.
```
submit_prompt :1600
├─ resolve_at_mentions → pending_context :1608
├─ ensure_ledger_loaded / ensure_l2_loaded :1618 (rehydrate L0/L2 across sessions)
├─ L0 authorize_tier(L0) → l0_cache.try_resolve :1628 hit → note_close(L0,0) → RETURN :1646
├─ L1 attempt_l1_deterministic :1834 parse_substitution; buffer.set_text → record_tier_edit(L1)
└─ attempt_l2_then_l3 :1673
├─ L2 embed (GGUF as encoder) → cosine ≥ threshold → replay prior edit → record_tier_edit(L2)
├─ L3 attempt_l3_then_l4 :1883
│ energy_before; document_symbols (LSP) on fg :1927
│ background: load_l3_model → codebase_index.search → format_symbol_context →
│ harness_prompt → l3_generate :1958
│ FILE blocks → apply_multi_file_edit; else parse_edit;
│ empty → send_to_l4; else review? stage_pending_edit : buffer.set_text + record_l3_edit
└─ L4 send_to_l4 :2462
authorize_tier(L4) DENY → "L4 denied by grant" RETURN ← no-phone-home seam
thread.send(ContentBlock) over ACP :2491
(close captured later via thread subscription — see §risks)
```
**Edit review** (`stage_pending_edit:2178` → `accept_pending_edit:2231` → `apply_staged_now:2204`): staging records a `PendingEdit`; buffers untouched until Accept (`set_text` + `record_l3_edit`). Reject just drops it. Control protocol exposes the same (`accept`/`reject`).
**Energy** (`bracket_energy:2443`): `after − before` from `energy_uj_now`; `Estimator` → `measured_uj = None` (honest). Counter from `host_energy_counter:3850` — Apple IOReport (aarch64), NVML/RAPL (linux), else `None`.
**Sealing** (`record_tier_edit:2542`, the shared close path): bracket energy → `build_coding_op` → `append_to_ledger:686` (hash-chained COSE_Sign1) → `note_methodology_op` → `l0_cache.insert` → `record_review` → `note_close` → `index_for_l2` → `attest_applied_edit:3039` (sandbox). Also folds the op into the active session via `record_active_op:728`.
**L4 close is asymmetric:** sealed not in `send_to_l4` but in the **thread subscription** (`:1123-1136`) — on `AcpThreadEvent`, `captured_op:3137` walks the thread's diffs into a `CodingOp`, then `append_to_ledger` + `l0_cache.insert`. (See risk #1.)
## 2. Joule integration seams
- **Init:** `main.rs:770` `agent_joule_panel::init(cx)` (the `zed.rs:5337` init is in `mod test` only — no production double-init).
- **Panel load:** `zed.rs:704` `AgentJoulePanel::load` in `initialize_panels`, added via `add_panel_when_ready` alongside the other dock panels.
- **Joule APIs touched:** `workspace::Workspace` (panel host, actions, `impl Panel`); `project::Project` (`agent_server_store`, `document_symbols` LSP, `open_buffer`, worktrees); `agent_servers`/`acp_thread`/`agent-client-protocol` (ACP connection + L4 send + diff subscription); `editor::Editor` (prompt + FIM provider); `language::Buffer`/`multi_buffer` (edit application); `lsp` (symbol context); macOS `sandbox` (Seatbelt for the agent's `run` tool).
## 3. External dependency boundary (16 git deps, pinned `4132daa`)
| Dep | Used by | Purpose | Status |
|---|---|---|---|
| `joule-code-cargo` | panel, methodology | `CodingOp`, `Edit`, `Anchor`, `SignedOp` | Core |
| `joule-code-cascade` | panel, cascade_gate | `L0Cache`, `Request`, `Stage` | Core |
| `joule-code-receipt` | panel | `Energy`, `Provenance`, `Receipt` | Core |
| `joule-code-schema` | panel, gate, methodology, sessions | `Tier`, `ContentId`, `ALG_ED25519` | Core |
| `joule-code-jcp` | cascade_gate | `JcpGate` — tier auth + sealed receipts | Core (gate) |
| `jcp-bridge` | tool_gate | `LocalToolGate` — per-tool-call auth | Core (gate) |
| `joule-code-identity` | sessions, panel | `Identity`, `ActorKind` — did:key principals | Used |
| `joule-code-spec` | methodology | `cite`, `coverage`, `Clause`, `SignedSpec` | Used |
| `joule-code-review` | methodology, panel | two-stage review ledger | Used |
| `joule-code-release` | methodology | `certify`, `ReleaseCertificate` — ship door | Used (via `ship_summary`) |
| `joule-code-joulecontract` | methodology | `frame_check` — out-of-frame edit gate | Used |
| `joulecontract-schema` | methodology | `Contract`, `Rigor` | **Thin** (inert frame contract only) |
| `jouleclaw-energy` | panel | `EnergyCounter` + IOReport/NVML/RAPL | Core |
| `jouleclaw-runtime` | panel | `generate`, `embed`, `cosine_similarity` | Core (L3) |
| `jouleclaw-loader-gguf` | panel | GGUF reader/tokenizer + safetensors Mellum | Core (L3) |
| `joule-code-conformance` (dev) | tests | `verify_pack` over C1–C4 vectors | Test-only |
**Flags:** `joule-code-release`'s discharge gate is hardcoded `false` (`ship_summary:2985`) — ship door un-passable; `jcp-bridge`/`ToolGate` only active under `JOULE_CODE_AGENT_TOOLS`; Mellum surface only via `JOULE_CODE_L3_ENGINE=mellum`.
## 4. Subsystem wiring
- **`authorize_tier`** `:2611` is the single junction composing **hooks** (PreTool, deny-wins, `:2623`) + **cascade_gate** (JCP grant + lifetime ceiling, `:2629`). Every tier passes through it.
- **`record_tier_edit`** `:2542` is the close junction fanning into methodology, sessions, L0 cache, L2 index, reviews, sandbox attestation.
- **`control_dispatch`** `:2261` is the headless seam — the control server (`control.rs:185`) and MCP bridge (`run_mcp_bridge`) drive the same panel methods a human action would.
- **methodology** ← `note_methodology_op:943` signs+cites+frame-checks each op, exports to `.joule/*.json`. **sessions** ← `ensure_session_mgr` builds the manager with `cascade_gate.grant()`; worktrees back background sessions (`active_session_worktree` → agent cwd). **tool_gate** ← passed as the `authorize_tool` closure into `local_agent::run_agent_loop`.
## 5. Integration risks / danglers
1. **L4 close depends on the thread subscription firing** (`:1123-1136`). If the subscription is dropped on reconnect (`:1141`) or no diff event fires, the L4 turn produces no ledger op and no L0 entry — a silently lost close (unlike synchronous L0–L3). *(See FINDINGS P1.)*
2. **Release/ship door structurally un-passable** — `contract_discharged = false` hardcoded (`:2985`); the full release certificate can't be reached (only merge-via-`verified` can).
3. **Session grant ↔ live gate is descriptor-only** — `SessionGrant` is sealed into the open receipt but not bound to the live `CascadeGate` (`sessions.rs:25-27`); sessions with different declared grants are gated identically.
4. **Heavy env-var gating — accountability is opt-in.** Out of the box (no `JOULE_CODE_GRANT`/`_LIFETIME_UJ`/`_AGENT_TOOLS`/`JOULECLAW_L3_MODEL`/`_REVIEW_EDITS`/`_CONTROL_SOCK`) the cascade runs **ungated and L4-reachable** — no-phone-home is a configuration, not a default.
5. **`session_mgr` silently absent outside a git repo** — `ensure_session_mgr` returns early; `record_active_op` no-ops; closed ops hit the ledger but never fold into a session chain, with no surfaced warning.
6. **`joulecontract-schema` near-vestigial** — only builds an inert frame contract.
7. **RAPL counter fragile by design** — selected only if a read succeeds; normal Linux user → `None` → 0-J receipts that look honest but carry no measurement and no "unavailable" signal.
8. **FIM provider attaches per-editor at `observe_new` time** (set-once); editors opened before a model is configured stay no-op for the session.