Architecture — Jarela¶
C4 — Container¶
C4Container
title Containers — Jarela
Person(user, "Developer", "Browser / installed PWA")
Person_Ext(wa_user, "WhatsApp peer", "Phone number paired via Baileys")
System_Boundary(b, "Jarela (Next.js process)") {
Container(ui, "Web UI", "React 19 + Tailwind", "Chat, agents, models, memory, connections, and tools panels")
Container(guard, "Origin / CSRF Guard", "lib/auth", "Rejects cross-origin mutating requests; same-origin enforcement")
Container(routes, "API Routes", "Next.js Route Handlers", "REST + SSE endpoints under /api/v1")
Container(agents, "Agent Runtime", "LangGraph + @langchain/*", "State-machine orchestration of LLM + tools; streams completions through providers")
Container(advertisements, "Derived Agent Metadata", "lib/agents/advertisement-service + lib/stores", "Opt-in configured-model queue, encrypted fingerprints/state, bounded authorized catalog (Unreleased)")
Container(providers, "Provider Adapters", "lib/providers", "Per-vendor SDK glue (Anthropic, OpenAI, Google, Cohere, DeepSeek)")
Container(pricing, "Pricing and Model Updates", "lib/pricing + lib/stores/pricing + lib/stores/model-metadata", "Refreshes provider pricing, separate aggregator signals, and source-backed API model metadata")
Container(embed, "Embeddings", "lib/embeddings", "Vector embedding generator for semantic memory recall")
Container(docs, "Document RAG", "lib/documents (+ lib/documents/remote)", "Folder indexer + remote indexers for GitHub, Gmail, Outlook, iCloud, Jira, and Confluence; chunked + embedded into the documents tables; surfaced via documents_search (ADR-0024, ADR-0026, ADR-0029, ADR-0110)")
Container(voice, "Voice", "lib/voice", "Gemini STT (push-to-talk) + TTS for the generate_voice tool (ADR-0017)")
Container(mcp, "MCP Adapter", "@langchain/mcp-adapters", "Discovers & invokes external MCP tool servers")
Container(sched, "Scheduler", "cron-parser", "Runs scheduled tasks + polls event-driven watchers; persists in DB (ADR-0022, ADR-0025, ADR-0027)")
Container(bridges, "Bridges", "lib/bridges", "Inbound transports (WhatsApp/Baileys) routed to agents")
Container(registry, "Run Registry", "lib/agents/run-registry", "In-memory pub/sub of in-flight agent chunks; replay buffer for reattaching EventSource clients")
Container(notifications, "App Event Bus", "lib/notifications/bus", "Replayable app notifications and transient run activity updates")
Container(crypto, "Crypto Envelope", "lib/crypto", "AES-GCM-at-rest for sensitive memory + OAuth tokens; OS keychain or .secret-key fallback")
Container(proxy, "Proxy Dispatcher", "lib/proxy", "undici GlobalDispatcher; reads HTTP_PROXY env vars + encrypted proxy_config row; gates all outbound HTTP (ADR-0009)")
Container(envsync, "Env Sync", "lib/env", "Caches user shell env for exec, terminal, delegation and MCP children; checked skill runners deliberately do not inherit it")
Container(workflows, "Checked Execution and Workflows", "lib/skills + lib/workflows", "Exact-code consent, encrypted durable CAS state, typed choices and observed reconciliation (Unreleased)")
ContainerDb(db, "SQLite", "node:sqlite writer + @appthreat/sqlite3 worker readers", "Local checkpoints, memory, settings, schedules, proposals, bridges; selected heavy reads use WAL reader workers")
ContainerDb(filestore, "File Store", "lib/files + ~/.jarela/files/", "Content-addressed image/file refs plus generated binary artifacts; served by /api/v1/files/[name]")
ContainerDb(extdir, "Extension dirs", "filesystem (~/.jarela/{providers,tools}/)", "Drop-in .cjs files for external providers + tools, hot-loaded per request (ADR-0013)")
}
System_Ext(anthropic, "Anthropic", "Claude")
System_Ext(openai, "OpenAI", "GPT")
System_Ext(google, "Google GenAI", "Gemini (LLM + STT/TTS)")
System_Ext(deepseek, "DeepSeek", "Chat completions + optional standalone Search/Fetch APIs")
System_Ext(cohere, "Cohere", "Embeddings")
System_Ext(openrouter, "OpenRouter", "Model route pricing catalog")
System_Ext(litellm, "LiteLLM", "Model pricing catalog")
System_Ext(mcps, "MCP Servers", "External tool providers (stdio / SSE)")
System_Ext(mcpreg, "MCP Registry", "registry.modelcontextprotocol.io — discovery only (ADR-0014)")
System_Ext(github, "GitHub API", "Issues / PRs / Repos (native github_* tools, ADR-0015) + Copilot OAuth (model provider)")
System_Ext(atlassian, "Atlassian Cloud", "Jira REST + Confluence REST (tools + document-RAG ingest, ADR-0026)")
System_Ext(mailproviders, "Mail Providers", "Gmail, Microsoft Graph, and iCloud Mail (remote document indexing)")
System_Ext(whatsapp, "WhatsApp Web", "Baileys-paired endpoint")
System_Ext(usershell, "User shell rc / Windows User env", "Credential/env source for general subprocess tools; deliberately excluded from checked skill execution")
System_Ext(browserext, "Jarela Browser Extension", "Chrome MV3 — element picker, posts captures to localhost (ADR-0018)")
System_Ext(skillowner, "Skill Process Owner", "Transient Windows Job / Linux delegated cgroup broker; full-trust, not a sandbox (Unreleased)")
Rel(user, ui, "HTTPS")
Rel(ui, guard, "fetch + EventSource")
Rel(guard, routes, "allow same-origin")
Rel(routes, agents, "invoke")
Rel(routes, advertisements, "local operator policy / inspect / regenerate")
Rel(advertisements, providers, "explicit configured generation only")
Rel(advertisements, db, "encrypted derived state + bounded usage ledger")
Rel(agents, advertisements, "fresh authorized descriptive catalog")
Rel(routes, workflows, "unlocked local execution and workflow controls")
Rel(agents, workflows, "explicit permission-gated tools")
Rel(workflows, db, "encrypted results/state and approval metadata")
Rel(workflows, skillowner, "captured bytes, exact argv and bounded raw streams")
Rel(routes, voice, "STT/TTS + generate_voice")
Rel(agents, mcp, "tool calls")
Rel(agents, providers, "stream completion")
Rel(agents, pricing, "invoke pricing maintenance")
Rel(pricing, proxy, "fetch pricing and official model documentation")
Rel(pricing, db, "persist model/version/capability observations")
Rel(routes, pricing, "read catalog display metadata")
Rel(proxy, openrouter, "HTTPS pricing catalog")
Rel(proxy, litellm, "HTTPS model pricing catalog")
Rel(agents, registry, "broadcast chunks")
Rel(agents, notifications, "publish run activity lifecycle")
Rel(routes, registry, "subscribe (GET SSE) / submit (POST 202)")
Rel(routes, notifications, "subscribe / replay via GET SSE")
Rel(routes, filestore, "POST /attachments/{images,files} + GET /files/[name]")
Rel(routes, sched, "register / trigger")
Rel(sched, agents, "run scheduled job")
Rel(wa_user, whatsapp, "message")
Rel(whatsapp, bridges, "stream events")
Rel(bridges, agents, "deliver as user turn")
Rel(agents, db, "checkpoint / memory (via crypto)")
Rel(agents, crypto, "encrypt sensitive at rest")
Rel(agents, embed, "embed memory writes")
Rel(agents, filestore, "materialize attachment refs for provider calls")
Rel(routes, filestore, "GET /api/v1/files/[name]")
Rel(crypto, db, "store ciphertext")
Rel(providers, anthropic, "HTTPS")
Rel(providers, openai, "HTTPS")
Rel(providers, google, "HTTPS")
Rel(providers, deepseek, "HTTPS")
Rel(agents, deepseek, "HTTPS via web tools, only with separate DeepSeek Search & Fetch opt-in")
Rel(voice, google, "HTTPS (Gemini STT/TTS)")
Rel(embed, cohere, "HTTPS (embed)")
Rel(mcp, mcps, "stdio / SSE")
Rel(routes, mcpreg, "HTTPS (picker search)")
Rel(routes, github, "HTTPS")
Rel(routes, atlassian, "HTTPS (tools)")
Rel(docs, github, "HTTPS (remote indexer)")
Rel(sched, docs, "sweep ~10 min")
Rel(docs, mailproviders, "HTTPS / IMAP (remote indexers)")
Rel(docs, atlassian, "HTTPS (remote indexer)")
Rel(docs, embed, "embed new chunks")
Rel(docs, db, "document_sources / documents / document_chunks")
Rel(agents, docs, "documents_* tools")
Rel(agents, extdir, "scan per request (cache-busted require)")
Rel(envsync, usershell, "read on boot")
Rel(envsync, db, "write encrypted via crypto")
Rel(proxy, db, "read proxy_config (via crypto)")
Rel(providers, proxy, "outbound via GlobalDispatcher")
Rel(routes, proxy, "outbound via GlobalDispatcher")
Rel(browserext, routes, "POST /api/v1/page-capture (loopback)")
Module map¶
The C4 diagrams show runtime connections; this map describes source ownership and the boundaries new code should use.
| Module | Responsibility |
|---|---|
app/ |
App Router screens and HTTP adapters; guard and validate requests before calling lib/. |
components/, hooks/, contexts/ |
Domain UI and browser state; consume API contracts rather than persistence internals. |
api/ |
Shared API client, response types, and request contracts. |
lib/agents/ |
Agent construction, turn execution, prompt preparation, validation, and run lifecycle. |
lib/providers/ |
Provider adapters, model routing, pacing, and vendor-specific SDK behavior. |
lib/tools/, lib/mcp/ |
Capability-aware tool registry and domains; MCP discovery and invocation. |
lib/stores/, lib/db/, lib/crypto/ |
Persistent state, SQLite access, checkpoints, and encryption. |
lib/scheduler/, lib/bridges/, lib/streaming/, lib/notifications/ |
Background triggers, inbound transports, run updates, and alerts. |
packages/ |
Separately scoped npm workspace packages. |
Dependency direction: UI → API routes → runtime modules → registries, adapters, and stores → local persistence or configured external services.
The unreleased credential editor keeps API dispatch and form state in
hooks/useIntegrationCredential.ts and authorization sessions in
hooks/useIntegrationOAuth.ts. IntegrationCard composes form, action-bar, and
setup-guide views without calling credential APIs. Its typed default/create/edit
mode and lifecycle contracts are documented in the
UI hook specification.
This stays within the existing UI-to-API boundary; persistence and vendor
authorization remain server-owned.
The unreleased complexity cleanup also keeps component-local controllers responsible for loading, mutations, feedback, and resource lifetimes, with cohesive view sections handling rendering. Agent modules separate admission, context assembly, streaming, retries, persistence, and accounting into typed stages while retaining the existing queues and stores. Dashboard attribution uses the same accumulation operation for daily and global scopes. These are internal ownership changes, not additional containers, processes, persistence schemas, or external dependencies; the diagrams above are unchanged.
The unreleased internal skill-consent contract in
lib/skills/execution-consent.ts owns run-local, bounded one-use decision state.
It reuses the manifest JSON complexity guard; it does not modify
lib/stores/pending-actions.ts, introduce persistence, or register an execution
tool. Snapshot capture, human approval UI/API integration and process ownership
remain blocked work under ADR-0112.
The contract is not an immutable snapshot or a standalone authorization boundary.
Unreleased skill-bundle reading reuses the repository resolver in
lib/skills/index.ts for instructions and resources. lib/skills/frontmatter.ts
uses the yaml parser for metadata and opt-in compatibility diagnostics;
it never rewrites source files. lib/skills/bundle-files.ts shares portable-path
and bounded regular-file reads between resource access and execution-manifest
inspection. These are read-only operations, not a script runner or immutable
snapshot boundary; see the extension guide.
Unreleased deterministic built-in skill steps live in portable dependency-free
modules under lib/skills/builtins/{personal-briefing,memory-organization}/scripts/.
The fixed read-only tools validate_personal_briefing and plan_memory_cleanup
import those implementations in-process; no user-controlled module resolution
or subprocess is introduced. Their standalone JSON CLI adapters are for hosts
that independently authorize Node execution, not a checked-runner workaround.
The memory planner consumes supplied fact projections only and never accesses
the store or mutates it. Bundle files are included in standalone build tracing.
See script-backed built-ins.
The unreleased Memory Organization execution manifest adapts those same two
portable scripts for artifact/protocol contracts and explicit execution; bounded anyOf
schemas represent nullable expiry and result variants. Ordinary discovery and
the fixed typed tool are unchanged. Inspection remains execution-unavailable;
inspection itself never authorizes or invokes the separate execution service.
The internal lib/skills/execution-output.ts collector reuses result validation
over bounded copied pipe bytes with strict UTF-8. It creates no process and
cannot attest exit status, authorization or process-tree cleanup; a captured
schema-valid value is not a successful execution.
The unreleased app integration follows
ADR-0114. lib/stores/skill-run-staging.ts
owns explicit run directories under the data directory;
lib/skills/execution-runner.ts consumes the existing artifact, consent and
output contracts and an available platform process-owner adapter. Local APIs,
permission-gated tools and Skills controls share the live execution service.
The bounded sequential definition/coordinator under lib/workflows/ uses an encrypted
lib/stores/workflow-runs.ts SQLite CAS store, not generic memory or a second
retry loop around model workflow progress. Startup performs bounded retention
cleanup, never implicit recovery or replay. Shutdown drains workflows and
script owners before closing SQLite. Consent grants/captured executable bytes
stay in memory; restart invalidates pending executable approvals.
Unreleased W3/W4 primitives in lib/workflows/authoring.ts are pure
bounded text builders, not another executor or persistence system.
lib/tools/general/authoring.ts registers Config/read tools for inert external
tool scaffolds and exact-diff local contribution review bundles. Discovery and
repository/test/rights claims remain caller-supplied; output revisions bind the
selected content and scope, never authorization. Built-in procedures require
independent evidence and separate execution/install/commit/push/PR gates.
The shared secret-marker list under lib/security/secret-patterns.mjs supports
both the commit scanner and unsuppressible bundle checks; neither proves privacy.
flowchart LR
Skills["Authoring / contribution skills"] --> TextTools["Config/read text builders"]
TextTools --> Inert["Inert source / tests / local diff + revision"]
Inert --> Review["Independent validation + human review"]
Review --> Approval["Local exact-source single-stage approval"]
Approval --> Lifecycle["Bounded extension / contribution services"]
Lifecycle --> Existing["Existing owned runner / drop-in / permission / repository paths"]
lib/workflows/extension-service.ts owns bounded single-effect authoring
operations, not a model loop or another general workflow executor. It reuses
the checked staged Node runner, existing drop-in directory and permission
stores. Current source, canonical directory identities, agent permissions and
configuration bind each review. Pending-action approvals apply only one stage.
Exact code/diff previews are local-only; result bytes use existing encryption.
Ephemeral operation state/grants are invalidated on restart. The existing
staging ownership format adds retained for user-owned inert source, so cleanup
does not erase drafts or recover consent. See
ADR-0115.
Unreleased lib/agents/advertisement-service.ts is an in-process coalescing
metadata worker, not a second agent executor. Shared config-store post-save
hooks invalidate routing sources; the existing canonical tool authority resolver
separates permission from transient readiness. Explicit configured-model policy
bounds egress and usage. Unreleased advertisement input selects only tools whose
permission is exclusive to one saved agent; a single-agent roster contributes
no tools. Config changes refresh all agents, and source fingerprints recheck
the selected tool metadata before generation and publication without changing
execution permissions. Encrypted rows use the existing memory_store, excluded
from ordinary memory/recall; fingerprints and generation IDs reject stale
publication after asynchronous work. The existing delegate catalog consumes
only currently valid descriptive data with deterministic overflow and explicit
legacy/error provenance. Dispatch and shared task budgets remain in the existing
delegation path. See ADR-0116.
lib/workflows/contribution-service.ts independently inspects canonical clean
Git state, selected bytes/base, existing local Vitest runtime and hook baselines.
Approved tests use a user-owned isolated topic worktree under the workspace's
ignored node_modules/.cache/jarela-contributions. Its canonical local bundle
comes from observed validation, not a caller's test report. Distinct local
approvals gate commit, create-only topic push and draft PR using existing Git and
GitHub authentication. No package publication or second model executor is added;
source/diff previews stay local while model inspection and approval results return
metadata. Approved Git mutations/hooks/helpers use native-owned execution; inert
discovery does not execute configured helpers or retrieve credentials.
flowchart LR
UI["Local UI / explicit agent tools"] --> Coordinator["Bounded workflow service + trusted host"]
UI --> Review["Exact-code / plan approval"]
Coordinator --> RunStore["Encrypted SQLite CAS + retention"]
Coordinator --> Runner["Live checked skill execution service"]
Review --> Consent["Exact one-use consent and current policy"]
Runner --> Staging["Run-owned captured bytes"]
Runner --> Consent["Exact one-use consent and current policy"]
Runner --> Owner["Windows Job / delegated Linux cgroup owner"]
Owner --> Capture["Bounded raw output and schema checks"]
Unreleased skill execution consent policy is managed by the Security UI through
app/api/v1/security/skill-execution/route.ts, restricted to unlocked loopback
requests. lib/stores/app-settings.ts validates and persists it in the existing
memory_store namespace; generic memory mutation paths reject this reserved key.
Server-issued approval IDs/timestamps persist beside checksum-scoped trust;
unchanged records survive restart, while revocation removes the grant. Receipt
updates use the canonical lib/db/transaction.ts helper and ignore caller-supplied
receipt metadata. Draft scopes without a receipt cannot waive consent.
lib/skills/execution-policy-server.ts reads current settings/safety mode on each
evaluation, while the pure policy module distinguishes blocking, invocation or
workflow consent requirements, and exact scoped revision trust. No runner calls
this policy yet: it grants neither sandboxing nor execution by itself. See
ADR-0112.
Document retrieval applies the global recall policy from lib/stores/app-settings.ts
inside lib/documents/search.ts, before returning chunks to tools. Exact
source/path exclusions and semantic topic exclusions cover existing and future
indexed content. Topic-filter failures block retrieval instead of falling back
to unchecked text. Only the user-facing Documents search preview exposes an
explicit override; documents_search has no override parameter. The policy
uses the existing settings store. A nullable chunk embedding model signature
ensures topic filtering never compares vectors from different models, even when
dimensions match (ADR-0109); existing sources need reindexing before topic rules
can be evaluated.
Mail document sources share a validated scope/limit/retention contract (ADR-0110) across Gmail, Outlook, and iCloud. The existing scheduler dispatches Gmail and Graph scans through their OAuth clients and iCloud scans through the optional iCloud package's read-only IMAP adapter. Credentials remain in the existing encrypted integration store (or configured environment); document archives remain in the existing document tables. New sources retain indexed mail after upstream moves/deletion; deleting the source removes its archive. Mail and calendar tools use the same topical categories and capability rules across providers: read for inspection, write for owned-content changes including trash/event deletion, and execute for mail sending where supported.
iCloud sending reuses the package's credential resolver and Nodemailer SMTP
transport (smtp.mail.me.com:587, mandatory STARTTLS); calendar/reminders stay
on the existing tsdav/ical.js CalDAV/iCalendar stack. The optional stored
mail_address is the SMTP authentication and sender identity when it differs
from the Apple ID; no agent-controlled From or server endpoint is exposed.
The send tool follows the same explicit agent permission and execute
classification as Gmail/Outlook, with no new per-send confirmation mechanism.
- Keep API routes focused on transport, origin checks, and request validation.
- Let
lib/agents/coordinate providers and tools; connect those capabilities through their adapters and registries rather than importing private internals. - Keep persistent state in stores and database modules; route sensitive values
through
lib/crypto/. - Reuse the existing process and agent queues for background work. Do not add a second runtime or broker for a feature-specific module.
For tool folder placement and registration rules, see the development guide.
Shared utilities¶
Two cross-cutting helper layers sit underneath every container above. They are not their own boundary on the diagram — every container reaches into them — but they are load-bearing:
lib/utils/—getOrCreateGlobal(singleton pinning across HMR),parseJsonSafe,stripHtml,truncateBytes,createOAuthFlowStore. Replaces ~13 duplication patterns surfaced by the audit.lib/ui/tabs.ts— the canonical tab vocabulary shared by AppContext and the pure deep-link parser, keeping URL recognition aligned with available tabs.lib/api/—errorResponse/notFoundResponse/createdResponse/validateBodyand the per-resource*ToResponserow→JSON serializers shared between list and[id]route handlers.
Unreleased runtime-operation consolidation also shares:
- Stored message decoding between chat rendering and history assembly. This preserves legacy attachment recognition and plain text fallback; it is not request-schema validation or a text projection.
- Best-effort context deadlines between foreground recall and history. The helper bounds waiting, not execution, and returns the caller's fallback on rejection or expiry. History keeps its non-positive unlimited-wait adapter; foreground recall keeps an immediate timer at zero. Required work and authorization checks must not use this fallback contract.
- Tolerant app-settings reads for settings whose owners permit a null fallback. Strict skill-execution and document-recall policy readers remain separate and propagate corruption or schema failures. Database errors are not converted to fallback settings.
- Nullable agent enum overrides share omission/clear/allowed-choice handling in agent configuration; usage strategies reuse their canonical choice list. Omitted legacy values remain untouched, while explicit invalid values clear the override. Text, boolean and citation controls retain their distinct normalization rules.
- Stored summary topics share array decoding in JSON utilities. UI and compaction pipeline topic types remain separate; decoding does not validate topic ranges or strip pipeline fields.
- The update route reuses the lifecycle version reader. Health probes and image generation share the one-level cause formatter in error utilities; other error owners retain ordinary message-only formatting.
The pattern catalog lists focused behavior tests. Coverage must be checked for the particular helper and its callers, not inferred for an entire utility layer.
Review patterns and architecture drift¶
Before adding or reviewing a runtime operation, compare it with the curated machine-readable architecture pattern catalog. It records common implementations, their behavior tests, and important exceptions for request validation, response serialization, tolerant JSON parsing, bounded caches, and built-in tool registration. The catalog is deliberately small: it guides searches and review decisions; it does not replace this module map, define every dependency rule, or prove that a new implementation is architecturally sound.
Reviewers should compare behavior and ownership, not just syntax. Reuse an
existing helper when its contract fits; extract only when implementations
share a stable invariant; keep distinct behavior separate when validation,
failure handling, ordering, security, or lifecycle differ. Record which
decision was made and the evidence in the change review. npm run lint
validates that catalog entries point to existing source and test files.
Architecture audit snapshot¶
The 2026-08 whole-repo audit found four areas that should shape future refactors and dependency upgrades:
- State ownership: UI-only preferences still have direct
localStoragewriters. Any new persistent state should go throughlib/storesor an explicit documented browser-cache exception. - Route validation: API routes mix direct zod
safeParse()calls with the sharedvalidateBody()helper. New route handlers should use the helper so error bodies stay consistent. - Tool/plugin capability metadata: built-in tools declare read/write/execute
capability tiers, while external
.cjstools still default toexecute. Extension-contract changes here should be handled with an ADR and deprecation window. - Secrets and external process boundaries: credentials, MCP env, proxy config, and hot-loaded plugins remain the highest-risk security surfaces. Upgrades must preserve encryption/redaction and the single Next.js process invariant.
C4 — Component (Agent Runtime)¶
flowchart LR
A[API Route /api/v1/*] --> G0[Origin Guard<br/>lib/auth]
G0 --> B[Agent Factory<br/>lib/agents]
B --> C[Provider Adapter<br/>lib/providers]
C --> RL[Shared request pacing and concurrency<br/>lib/providers/rate-limit]
B --> D[Tool Registry<br/>lib/tools<br/>category × capability]
B --> E[MCP Client<br/>lib/mcp]
C --> F[(LLM Provider)]
C --> XP[External providers<br/>~/.jarela/providers/*.cjs<br/>hot-loaded]
D --> G[Built-in tools]
D --> XT[External tools<br/>~/.jarela/tools/*.cjs<br/>hot-loaded]
D -."delegate_to_agent reaches Agent Runtime internals<br/>(run-thread, model-router, stream-collector) directly<br/>— the one intentional bidirectional edge".-> B
D --> EM[Embeddings<br/>lib/embeddings]
D --> FS[File Store<br/>lib/files<br/>~/.jarela/files/]
A --> V[Voice<br/>lib/voice<br/>STT + TTS]
V --> F
EM --> F
E --> H[(External MCP servers)]
B --> I[Checkpoint Store<br/>lib/db]
I --> J[(SQLite ~/.jarela)]
B --> K[Memory Store<br/>lib/stores]
B --> HR[Harness Resolver<br/>lib/agents/harness]
HR --> K
B --> PR[Prepare<br/>lib/agents/prepare<br/>system prompt + history window]
G0 --> WC[Warm Context Coordinator<br/>lib/agents/context-boundary<br/>+ warm-summary-background]
B --> WC
WC --> C
WC --> TS[Thread Store<br/>lib/stores/threads]
TS --> J
SG[System signal outbox and delivery leases<br/>lib/stores/system-signals] --> PR
G --> SG
SC --> SG
SG --> J
B -.transcript plus acknowledgment transaction.-> SG
PR --> B
B --> OV[Output Validator<br/>lib/agents/output-validator]
OV -.post-turn check.-> B
K --> CR[Crypto Envelope<br/>lib/crypto]
CR --> J
K --> J
B --> L[Notifications<br/>lib/notifications]
BR[Bridges<br/>lib/bridges] --> B
SC[Scheduler<br/>lib/scheduler] --> B
EN[Env Sync<br/>lib/env<br/>boot probe + Sync button] --> K
EN -."full shell env<br/>(ADR-0072)".-> D
EN -."full shell env<br/>(ADR-0072)".-> E
RR[Run Registry<br/>lib/agents/run-registry] -.pub/sub + replay buffer.-> A
RR -.activity lifecycle updates.-> L
Tool proxy execution (unreleased)¶
invoke_tool dispatches through the same executeTool runtime as direct calls.
It preserves trusted run configuration, workspace overrides, progress writer and
call correlation, cancellation, credential routing, permission overlays,
delegation context, and coordinator-owned settlement.
The target owns its deadline, background receipt, and streaming controls; the
proxy does not start a second timeout or background operation. Top-level
deadline_ms, async_run, and stream are defaults for the target; values
inside args or decoded args_json take precedence. Output-size safeguards
remain in place, reserving proxy-envelope overhead for paged results.
Permissions, category toggles, credentials, completion-turn read-only rules,
and the target's argument schema are not bypassed.
Key Flow — User sends a chat turn¶
Durable lifecycle signals¶
The result cache/settlement retry state, per-thread queue, active-run registry, cold-attach waiters, and config snapshot are process-wide, not route-module locals. Separate Next bundles therefore serialize the same thread and can attach to its active run. Owner changes quarantine stale deliveries; durable deletion/expiry also revokes cached result access.
System signals use the existing SQLite database and agent queues. Operations, immutable outbox events, and delivery leases are separate records (ADR-0091). The dynamic prompt suffix receives only the owning agent/thread's eligible events, in journal sequence order, with bounded count and size. Redelivery can occur after failures; no exactly-once external tool effects are claimed.
Restart requests persist acceptance before exiting. At the end of bootstrap, after protected state is unlocked, the new runtime completes accepted restart operations from a different instance and marks unfinished background tools as outcome unknown. No interrupted tool is automatically replayed.
Successful assistant transcript insertion and matching lease acknowledgment
share one SQLite transaction. Silent turns persist an internal receipt.
Failed/aborted deliveries use delayed retries; exhausted attempts remain in
dead-letter records. The existing read_agent_config tool reports delivery
counts and oldest-event times, and dead-letter transitions appear in Logs.
Restart and authorized background-tool completion events wake the owning agent through the existing scheduler/thread queues, with a two-thread cap, backoff, and recursive-wake suppression. Config and approval/job events are consumed on the next eligible turn. Results are encrypted in SQLite and retrieved through the existing result tools with owner-thread checks; legacy process-local references are suppressed after restart. Signals describe only their named outcome, not permission to perform new actions.
sequenceDiagram
actor U as User
participant UI as Web UI
participant G as Origin Guard
participant ATT as /api/v1/attachments
participant API as /api/v1/threads/:id/run
participant THREAD as /api/v1/threads/:id
participant PIN as /api/v1/threads/:id/context-pin
participant AG as Agent Runtime
participant DB as SQLite
participant LLM as LLM Provider
U->>UI: drags context boundary line
UI->>PIN: PATCH { hot_since_seq } (ADR-0096)
PIN->>DB: validate seq belongs to thread
alt clear pin
PIN->>DB: clear hot_since_seq immediately
PIN-->>UI: cleared committed state
else move pin
PIN->>AG: enqueue background boundary compaction
PIN-->>UI: committed state + pending_hot_since_seq
AG->>AG: optionally align to a nearby topic start
AG->>DB: read chat and automation rows before boundary
AG->>LLM: build chat + automation summaries
LLM-->>AG: summaries
AG->>DB: compare-and-set transaction writes cursor + coverage
Note over AG,DB: On failure, keep committed state and do not publish the candidate
opt summary and commit succeed
DB-->>AG: committed hot_since_seq
end
end
loop while a cursor is pending
UI->>THREAD: GET /threads/:id
THREAD-->>UI: committed state + pending_hot_since_seq
end
Note over UI: Divider and Locate target the committed source seq
U->>UI: types message + adds attachments
opt image / binary file attachment
UI->>ATT: POST multipart file
ATT->>DB: persist metadata in message refs later
ATT-->>UI: image_ref / file_ref
end
UI->>G: POST /threads/:id/run (message, hot_since_seq?, channels?)
G->>G: check Origin / Sec-Fetch-Site
G->>API: forward if same-origin
API->>API: enqueue per-thread run
API-->>UI: 202 Accepted + queue position
API->>AG: startRun when the thread slot is available
opt non-null requested cursor differs from committed cursor
AG->>AG: schedule compaction, use committed cursor for this turn
end
AG->>DB: load checkpoint + thread.hot_since_seq
AG->>DB: select candidate rows where rowid >= committed hot_since_seq
Note over AG,DB: Message, hot-turn, and token budgets may narrow eligible rows
AG->>ATT: materialize refs for the newest turn only (ADR-0090)
Note over AG: Older image_ref/file_ref in the window become a<br/>placeholder, model calls view_attachment to re-read one
opt no matching chat summary for the effective boundary
AG->>LLM: build bounded ephemeral chat fallback if needed
LLM-->>AG: summary for this prompt
Note over AG: Automation summaries are included only on an exact coverage-seq match
end
UI->>API: GET /threads/:id/run (EventSource subscribe)
AG->>LLM: stream completion
LLM-->>AG: tokens
AG-->>API: broadcast chunks (run-registry)
API-->>UI: SSE: text_delta / tool_call / done
UI-->>U: render
AG->>DB: save checkpoint
UI->>THREAD: GET /threads/:id (refetch incl. warm_summary)
THREAD-->>UI: committed summary metadata
UI-->>U: warm summary card hydrates
Key Flow — Inbound bridge message (WhatsApp)¶
sequenceDiagram
actor P as WhatsApp peer
participant WA as WhatsApp Web
participant BR as Bridges (Baileys)
participant DSP as bridges/dispatcher
participant AG as Agent Runtime
participant DB as SQLite
participant N as Notifications
P->>WA: send message
WA-->>BR: stream event
BR->>DSP: normalize → InboundMessage
DSP->>DB: route JID → agent_id + thread_id
DSP->>AG: deliver as user turn
AG->>DB: append + run graph
AG-->>DSP: reply text
DSP->>BR: send outbound
BR-->>WA: deliver to peer
AG->>N: SSE push to any open UI
Key Flow — Agent-led integration setup (ADR-0010)¶
sequenceDiagram
actor U as User
participant UI as Web UI
participant AG as Agent Runtime
participant REG as Manifest Registry
participant PA as pending_actions
participant API as /api/v1
participant DB as SQLite
U->>AG: "connect my gmail"
AG->>REG: list_integrations / get_integration_setup("gmail")
REG-->>AG: manifest (steps + proposes kinds)
AG->>PA: propose_config_change(enable_integration, payload)
PA-->>UI: ApprovalsBanner shows pending row
U->>UI: Approve → secret-collection modal
UI->>API: POST /pending-actions/:id/approve { extras: { client_id, client_secret } }
API->>DB: applyEnableIntegration(payload, extras)
AG->>PA: propose_config_change(start_oauth, { integration_id })
U->>UI: Approve
UI->>API: POST /pending-actions/:id/approve
UI->>API: POST /integrations/gmail/oauth/start
API-->>UI: { authorize_url }
UI->>U: window.open(authorize_url)
Note over AG,DB: Agent never sees secrets — collected by approval UI directly
The same propose_config_change → ApprovalsBanner → applyAction loop also
carries agent-driven harness edits (ADR-0036):
the agent proposes upsert_harness to create or modify a custom preset, and
update_agent (with harness_id) to point an agent at it. Built-in harnesses
remain read-only and the global default pointer stays UI-only — both invariants
enforced inside applyAction.
Tool registry — category × capability¶
Every built-in tool registers with the tool registry on two orthogonal axes (ADR-0038):
ToolCategory— topical group (Memory,Files,Mail,Atlassian, …). Drives the Agent editor sidebar layout. Says nothing about safety.Capability— safety class (read|write|execute).read: pure observation, no mutations anywhere (memory_read,web_fetch,jira_search).write: mutates local Jarela-owned state — SQLite tables, the file store, files in user-controlled directories (memory_write,file_write,schedule_task,documents_add_local_source).execute: invokes external systems with side effects users see outside Jarela, OR runs arbitrary code (local_exec,generate_image,delegate_to_agent,claude_delegate,codex_delegate,jira_create_issue,gmail_create_draft).
Files with mixed capabilities (memory, files, schedule, atlassian, github,
gmail, outlook, calendar) declare every capability bucket in a single
registerLangChainPackage({ category, integrationId?, tools: { read?, write?, execute? } })
call. integrationId names the INTEGRATIONS key backing the tools, so the
permission layer can deny them when that integration is unconfigured
(ADR-0078). External
(JARELA_TOOLS_DIR) and MCP tools default to execute until a
manifest field overrides it. Consumers — a planned per-capability approval
gate, UI badges, the ADR-0037 validator's citation rules — switch on the
three values exhaustively. The capability-coverage.test.ts runtime check
asserts every registered tool has a capability so a new tool cannot land
uncategorised.
Output validator (anti-fabrication)¶
lib/agents/output-validator post-checks every assistant turn before the
terminal done chunk leaves stallRetryStream
(ADR-0037). It cross-references the
assistant text against the tool_call chunks issued in the same turn and
flags four fabrication shapes — claim-without-tool, citation-of-an-unregistered-tool,
citation-of-an-uncalled-tool, and summary-without-action. A flagged turn
triggers the same retry path the stall detector uses (↻ separator + a
reason-aware synthetic-user nudge); if the retry budget is exhausted,
persistAssistantMessage appends a visible *⚠️ Output validator flagged: ...*
footer to the persisted message. The validator runs entirely on regex —
no extra LLM call per turn — and is exercised by both unit tests and a
named-scenario regression set (npm run test:eval) seeded with real
observed hallucinations.
Key Flow — Scheduled background task¶
sequenceDiagram
participant S as Scheduler
participant AG as Agent Runtime
participant DB as SQLite
participant N as Notifications
S->>DB: poll due jobs (cron)
S->>AG: run(jobId)
AG->>DB: load state
AG->>AG: execute graph
AG->>DB: persist result
AG->>N: notify (if configured)
Key Flow — Delegate a coding task to Claude Code (ADR-0071, ADR-0073)¶
sequenceDiagram
participant AG as Agent Runtime
participant CD as claude_delegate tool
participant MB as claude-memory-bridge
participant DB as SQLite (memory_store, claude_delegate_sessions)
participant CLI as claude CLI (child process)
participant FS as ~/.claude/projects/.../memory
participant UI as Chat UI (ToolList card)
AG->>CD: claude_delegate({ task, cwd, ... })
CD->>CD: resolveSafetyGate() — JARELA_TOOL_SAFETY tier
CD->>MB: syncIn(cwd, claude-sync:<hash>)
MB->>DB: listMemory(namespace)
MB->>FS: write *.md + MEMORY.md index
CD->>DB: getSession(project_key) / rememberSession
CD->>CLI: spawn -p --output-format stream-json --permission-mode <gated>
loop each stream-json turn
CLI-->>CD: stream-json event (text / tool_use)
CD->>CD: armTimer() — resets the subprocess idle timer
CD->>UI: config.writer() -> "custom" stream -> tool_progress chunk (live step, also resets the outer wallclock)
end
CLI-->>CD: stream-json result event
CD->>MB: syncOut(cwd, namespace, manifest)
MB->>FS: read *.md
MB->>DB: putMemory / deleteMemory
CD->>CD: gitDiffSummary(cwd) — verify loop
CD-->>AG: { result, changes, permission_denials, sync, steps }
Key Flow — Supervise a Codex coding task (ADR-0086)¶
sequenceDiagram
participant AG as Agent Runtime
participant CD as codex_delegate tool
participant DB as SQLite (codex_delegate_sessions)
participant CLI as codex CLI (child process)
participant UI as Chat UI (ToolList card)
AG->>CD: codex_delegate({ task, cwd, feature?, background? })
CD->>DB: getSession(project_key)
CD->>CLI: spawn exec or exec resume with sandbox policy
loop each JSONL event
CLI-->>CD: command or agent-message event
CD->>UI: tool_progress chunk (live transcript)
end
CLI-->>CD: thread id and final result
CD->>DB: rememberSession(project_key, thread_id)
CD->>CD: gitDiffSummary(cwd) — verify loop
CD-->>AG: { result, changes, transcript, resumed }
opt background run
AG->>CD: codex_delegate_status(job_id, poll or cancel)
CD-->>AG: steps, status, result
end
Key Flow — Browser-extension page capture (ADR-0018)¶
sequenceDiagram
participant U as User
participant BX as Browser Extension (MV3)
participant SW as Extension Service Worker
participant CS as Content Script (picker)
participant PR as proxy.ts
participant API as /api/v1/page-capture
participant DB as SQLite
participant BUS as Notifications Bus
participant UI as Web UI (open tab)
Note over SW,API: Heartbeat — every 15s
SW->>API: GET /api/v1/health
API-->>SW: 200 OK ⇒ icon enabled
U->>BX: Click toolbar icon
BX->>SW: action.onClicked
SW->>CS: scripting.executeScript (idempotent)
CS-->>U: Overlay banner + element-tracking outline
U->>CS: Click target element (or ESC to cancel)
CS->>SW: runtime.sendMessage(jarela-capture-visible-tab)
SW->>SW: chrome.tabs.captureVisibleTab → PNG dataURL
SW-->>CS: dataURL
CS->>CS: crop to element bounding rect (OffscreenCanvas, devicePixelRatio)
CS->>SW: runtime.sendMessage(jarela-capture, {text, selector, screenshot})
SW->>PR: POST /api/v1/page-capture (Origin: chrome-extension://…)
PR->>PR: Loopback Host check ✓, carve-out skips Origin check
PR->>API: forward
API->>API: Truncate text to 100KB UTF-8, validate screenshot ≤ 4MB base64
API->>DB: addMessage(thread, "user", [text, image] when screenshot present)
API->>BUS: publish(thread_message_added)
API-->>SW: 200 {thread_id, msg_id, truncated, originalBytes}
SW-->>CS: ack
CS-->>U: Flash + "✈ Sent" pill animation + success banner
BUS-->>UI: SSE: thread_message_added
UI->>UI: dispatch jarela:thread-updated → re-fetch messages (image renders inline)
Non-Functional Requirements¶
| NFR | Target | Notes |
|---|---|---|
| Cold start (dev) | < 5 s | npm run dev |
| First token latency | < 1.5 s p95 | Network-bound on provider |
| Local-only operation | required | No telemetry, no required cloud backend |
| Persistence | survive process restart | All state in ~/.jarela/*.sqlite |
| API key handling | never leave the host | Stored in DB or env, not synced |
| Same-origin enforcement | required | CSRF / DNS-rebinding guard in lib/auth/access.ts |
| Secrets at rest | required for sensitive namespaces | AES-GCM envelope, master key in OS keychain or .secret-key fallback |
| Outbound proxy support | required on corporate networks | env vars or in-app proxy_config row, applied via undici setGlobalDispatcher (ADR-0009); env wins over DB |
| Stream resilience | reattach within seconds of network change | EventSource auto-reconnect + 4000-event replay buffer in lib/agents/run-registry.ts (ADR-0008) |
| CI on every push | required | .github/workflows/ci.yml: lint + tsc + build + live integration suite |
Release documentation boundary¶
The manually dispatched .github/workflows/release-docs.yml runs a pinned
Copilot CLI against a previous stable tag and exact main SHA. It uses a
Copilot-enabled secret in a read-permission generation job, then passes a
validated documentation-only patch to a separate PR-writing job. A maintainer,
not the model, reviews and merges the PR. Accepted decisions live in
docs/releases/<version>.json.
scripts/release-docs.mjs provides deterministic record/scope checks for the
optional documentation workflow and local review. Ordinary PR CI, nightly
preparation/resume/tagging, and app-tag publishing do not require documentation
reviews or Copilot credentials. Source changes invalidate optional reviews;
documentation and narrowly allowed generated release metadata/version-only bumps
may follow them. Missing or stale reviews never block publication.
Neither CI nor the nightly scheduler starts paid AI sessions.
This optional authoring service is outside the Jarela process and stores no app
state. See ADR-0106.
External Dependencies¶
| Dependency | Purpose | Failure mode |
|---|---|---|
| Anthropic / OpenAI / Google / Cohere | LLM inference | Surface provider error to UI; allow model switch |
| Google GenAI (Gemini) — STT + TTS endpoints | Push-to-talk voice input + generate_voice tool (ADR-0017) |
Voice surface degrades to text-only; chat continues |
| MCP servers | External tools | Tool call returns error; agent can recover or skip |
External provider/tool .cjs files (~/.jarela/{providers,tools}/) |
User-authored extensions, hot-loaded | Validation errors surfaced in GET /api/v1/extensions and the Extensions tab; loader skips invalid files (ADR-0013) |
User shell rc (~/.zshrc/~/.bashrc) on macOS/Linux, User-scope env on Windows |
Source for credential env vars (ADR-0016); probed at boot + on demand | Probe failure surfaces a warning, app falls back to whatever is already in process.env; tools surface "not configured" via the existing env-then-DB resolver |
GitHub API (api.github.com) |
Native github_* tools — issues, PRs, repos (ADR-0015); Copilot OAuth for the model provider |
Tool call returns the API error; agent can recover or skip |
| SQLite (local) | Persistence | Fatal — startup fails fast with clear error |
| Copilot CLI / GitHub Copilot (optional documentation infrastructure only) | On-demand documentation proposals with a 50-credit soft session limit | Explicit documentation workflow failure; releases remain independent; manual documentation PRs remain available |
Decisions¶
See docs/adr/. Significant choices on persistence, agent runtime, and provider strategy will be recorded as ADRs.