Architecture #
Components #
1. Producer processes #
Each producer process observes one external source namespace, normalizes source objects, and inserts source events into Jazz. It owns that namespace's external cursor, monotonic source sequence, reconnect behavior, and backpressure. One active process writes a given source id. Producers do not invoke models directly. A deployed Jetstream listener therefore runs in explicit producer-only mode; it must not start a second copy of resident consumers already owned by the consumer service.
Initial adapters:
filesystem: arbitrary bounded roots, including selected Obsidian paths.rss: RSS/Atom polling with HTTP validators.jetstream: ATProto Jetstream commits with microsecond cursors.fastmail: serial read-only JMAP session discovery, Email/queryChanges, Email/changes, bounded metadata fetches, and cursor-safe resnapshot recovery.telegram: authenticated allowlisted Bot API webhook delivery or a read-only spool supplied by another runtime.x-webhook: signed X Activity webhook delivery split into explicit personal and public-watchlist source lanes. Seex-webhook.md.
2. Event registry #
The registry maps an NSID-style event type and schema version to validation, privacy classification, retention hints, and a renderer. Unknown types may be stored as quarantined raw observations but do not reach agents until registered.
3. Jazz database #
Jazz 2 is the shared persistence, query, subscription, durability, synchronization, and permission system. Historical tables hold events, document versions, and trace chunks; operational tables hold sources/cursors, consumer declarations/progress, execution state, and projections. A deployable Jazz policy must deny historical mutation rather than merely omitting mutators from a TypeScript interface. The current local-development policy remains permissive and is not deployment evidence.
Domain modules validate event meaning, derive deterministic row identity, and construct transactions, but they remain thin Jazz-facing boundaries. There is no generic backend-portability layer and no application-owned query engine.
4. Consumer processes #
Each consumer process compiles its declaration into a narrow Jazz query and live subscription. The subscription is a low-latency wakeup, not the authority for whether work exists: the process also re-queries durable backlog at a bounded configured interval because local persistent Jazz stores do not guarantee cross-process subscription callbacks. Durable per-source progress makes these reconciliation passes idempotent. A consumer recovers from progress, executes matching work itself, and writes lifecycle/output events back to Jazz under its own source namespace. One active process owns a consumer id/version. There is no central matcher, global dispatch queue, or scheduler lease between consumers and the stream.
A product-level focus declaration sits above this runtime contract. It names scope, objective, subscriptions, budgets, requested permissions, lineage, and retirement, while the exact executable consumer declaration remains separate authority. The first implementation appends inert focus proposals only; it cannot activate consumers, widen data access, train, promote, or deliver. See focuses.md.
Ordinary declaration/source pairs have independent scheduler keys. Stateful Letta SDK declarations are different: every concrete source subscription for one resolved Letta agent id shares one operation key, preserving exclusive access to that agent's main conversation while retaining separate source progress. Thus direct Telegram and derived ATProto batches serialize through one resident operation key while keeping independent progress rows.
Context enrichment is a named boundary owned by the trusted parent, not ad hoc model choreography. A source-specific compiler may dereference public objects, but it must bound and label each view, preserve the canonical source event, and durably snapshot the exact packet before the resident turn. Snapshots are runtime data, not repository artifacts.
Conversation compaction is a separate model-backed consumer, not hidden behavior inside reconstruction or a runner. On the retained Pi Telegram path, a reciprocal no-tool clone observes the same source, skips below its deterministic threshold, and emits a private recursive boundary over one retry-stable frozen prefix. The Pi selector may replace exactly the boundary's covered prefix with that typed historical summary while retaining exact newer turns. The active Letta resident instead owns continuity in its persistent main conversation, so the Pi parent and compactor are disabled together. Canonical events and reconstruction remain unchanged; forks or divergent boundary evidence fail closed. See compaction.md.
Batching is a separate deterministic consumer stage. A strict manifest declaration names its id/version/enabled state, exact input event types and source ids, output source/type, quiet window, maximum age, maximum item count, privacy rule, replay rule, and bounded poll interval. It has no hidden prompt state and invokes no model. Batch identity is derived from the declaration fingerprint plus ordered canonical member event ids; the payload contains ordered strong references and bounded source metadata, never arbitrary source bodies. Batch insertion and every participating source's filtered progress settle in one transaction. Each high-water mark may cross nonmatching cursor/lifecycle rows but may not cross an eligible matching row absent from that source's batch prefix. Quiet/max-age decisions use durable eligible-event timestamps after every restart; unrelated source events never postpone a flush, and max-items forces a bounded prefix flush. preserve requires one source and one privacy class. most-private admits several sources and sets the batch privacy to the strongest member without changing the members' source identities.
The resident consumes direct Telegram source events or explicit derived ATProto batch events. It never receives raw ATProto commits or a hidden in-memory bundle that cannot be replayed and inspected. Its trusted compiler dereferences every batch member from Jazz, verifies identity/type/source/privacy/sequence and ordered provenance, allocates one total character budget across exact source records and source-appropriate views, and snapshots the final packet once. Bluesky post/like members receive atproto.md plus bsky.md views; Semble collection-link members receive atproto.md link/card/collection views and no bsky.md call. Missing or mismatched members and fixed envelopes that exceed budget fail closed before model dispatch.
5. Agent runner #
For model-backed consumers, the runner builds a bounded context packet, invokes either an inference cell or an allowlisted container harness through a capability-scoped provider boundary, captures metadata-only events and usage, validates final structured output, then inserts derived events. The existing Pi/Bubblewrap path is an inference cell. Full agent runtimes use the generic contract in harnesses.md; Pi coding-agent is the first adapter. The runner never edits its triggering event.
A conceptualizer consumer is a model-backed consumer that extracts concepts and directional links from one bounded source event. For an ATProto batch trigger, the trusted parent resolves every exact member reference and compiles the same bounded ATProto member views used by the resident path before inference; a list of opaque member ids is not sufficient conceptual evidence. It uses the stream.thought.output.conceptualization@1 output contract, which validates one complete bounded graph of lowercase concept phrases and typed links. The runner settles that graph atomically as one stream.thought.derived.concept.graph event with exact source/run lineage. The conceptualizer has no outbound PDS authority; it produces only a private derived observation. Invalid graphs fail as a whole and emit no derived graph, so a partially parsed model answer cannot become durable state. Tinker is the inference provider; the conceptualizer does not train, sync an external graph, or publish to a PDS.
The Coil Public Knowledge consumer is a separate stateful Agent SDK declaration. It subscribes only to one concrete sensitive Coil filesystem source. Before opening an SDK session, the trusted parent resolves the exact immutable document version and applies the current default-deny Public Knowledge policy. Blocked, unmapped, unsupported, deleted, or oversized documents append a content-dark skipped-run receipt and advance that consumer's source progress without conversation creation or inference accounting. Eligible versions receive a bounded packet containing the exact source version, all catalog metadata, and a deterministic bounded set of relevant existing public entry bodies with exact hashes.
Each eligible stable documentId maps to one explicit conversation on Co's existing agent. Jazz stores that operational mapping; path is mutable metadata, so a confident rename preserves the conversation. A deterministic digest is written to the conversation summary, allowing a crash after remote conversation creation but before Jazz settlement to recover the same conversation instead of creating another. The declaration serializes all file conversations through one Co operation key while Jazz inference accounting applies the configured rolling/hour/day limits. The proposal-only local Agent SDK profile fixes strict permission mode, no skills, memory-root confinement, Luna, and exactly one controller-owned submit_public_knowledge_diff tool. The callback captures a validated proposal in memory and has no effect authority; all other tool behavior fails closed. The trusted runner ignores the final acknowledgment as semantic output and settles the captured proposal as sensitive stream.thought.agent.public-knowledge-diff.proposed evidence with publication authority fixed false. No current consumer materializes or publishes it. See public-knowledge.md.
6. Projections #
Projectors are consumers with no special delivery path. They follow Jazz subscriptions, recover from per-source progress, and maintain rebuildable Jazz views: root activity, topic index, unresolved recommendations, source health, consumer health, document identity, and trace summaries. Jazz performs filtering, ordering, and bounded pagination.
7. Egress dispatchers #
External actions are owned by separate destination-specific dispatcher processes. A dispatcher reads completed candidate activity from Jazz, filters it against channel policy, accumulates and renders batches, applies destination velocity limits, performs the action, and appends started/delivered/failed evidence. Producers and consumers never wait on dispatcher policy or destination throughput. The first implementation supports Telegram delivery only.
The resident Stream agent does not hold a Telegram tool. An activity-window turn may emit a contract-valid high-importance observation containing the exact inert notify-cameron proposal tuple. The Telegram dispatcher independently requires the resident and batch source to match its proposal route before ordinary velocity, claim, send, and receipt handling applies. The resident's direct-reply route names the same agent with the Telegram source and remains disjoint. A model request is neither delivery authority nor evidence that a message was sent.
The layered social path preserves the same split. Bluesky and X listeners each own one persistent source-local conversation and append derived observations. A deterministic social batch references only those output events. Its context compiler rejects any member that does not join to one exact completed run and matching source, trigger, output, execution key, and semantic result. The social listener can therefore synthesize receipt-backed observations without receiving either raw source lane. Its exact agent/source tuple is a second notification-proposal route; it never becomes a direct-reply route or a channel capability.
The Telegram dispatcher also owns one ephemeral presence action for direct-reply agents: while a recent durable running run is rooted in an allowlisted Telegram message for that exact chat and agent, it refreshes Bot API typing before Telegram's five-second expiry. This action contains no model or source content, is bounded by the direct-reply allowlist, stops when no matching run remains, and is best-effort: failure cannot fail the run or message-delivery loop. Unlike human-visible message delivery, typing is repeatable transient UI state and creates no append-only delivery claim.
8. Local interface and Review authority #
The interface is a loopback server with a single-column chronological feed and focused secondary views. Activity, execution, source health, lineage, and adapter inventory are read projections. Review adds one separately configured mutation: an allowlisted OAuth browser may append a fixed decision after CSRF validation and a body-bound proxy-to-inspector capability check. Basic remains read-only. The route cannot create prompts, run models, export datasets, activate adapters, publish, or perform arbitrary Jazz mutation. Review evidence affects dataset projection only; it is not deployment authority.
Data flow #
- Producer receives, scans, or is delivered a source object.
- Producer computes a source-native idempotency key and next source sequence.
- Registry validates the typed payload.
- A Jazz transaction writes the event, source sequence, and external cursor together.
- Matching consumer and projector subscriptions observe the durable row.
- Each consumer updates its own bounded backlog and records a started execution when work is needed.
- An optional deterministic batcher may convert explicit member events into one replayable derived batch event.
- Context builder resolves exact source and derived inputs by event/version id and reuses any durable enrichment snapshot.
- Pi or Letta runner emits trace chunks and a final output when the consumer is model-backed.
- Output validator accepts or rejects each declared derived event.
- A Jazz transaction writes derived/lifecycle events, terminal execution evidence, and the consumer's per-source progress together.
- Projector consumers update the root activity view through the same query/subscription mechanism.
- An independently running egress dispatcher may select eligible completed activity, render a destination batch, claim it durably, perform the external action, and append delivery evidence.
Authority layers #
| Layer | Authority |
|---|---|
| External source | Original email, post, message, feed item, or file |
| thought stream events | What was observed, when, and with which source identity |
| Document versions | Exact observed content needed for replay and diffs |
| Agent traces | What the runtime and model emitted |
| Derived events | Versioned claims or proposals by a named agent/runtime |
| Dispatcher receipts | Durable evidence that a configured external action was claimed, delivered, or failed |
| Review decisions | Append-only human evidence over one immutable prompt and exact candidate pair |
| Projections/UI | Rebuildable convenience views; the UI's only mutation is the fixed Review decision append |
Process shape #
Development may run producers, consumers, projectors, dispatchers, and the HTTP server under one local supervisor. Deployment may run them independently on different computers without changing event contracts. In both forms, each source id, consumer id/version, and destination dispatcher id has one active owning process.
thought stream is the human-facing local entrypoint. It loads one strict, versioned manifest from the project root (or an explicitly named path), opens Jazz, registers producer and consumer declarations, recovers nonterminal local consumer attempts, starts the loopback inspector, and starts only entries explicitly enabled for that process.
The supervisor is process glue, not a central coordinator. Polling loops never overlap themselves. Consumer processes receive work through Jazz queries/subscriptions rather than an in-memory producer-to-agent handoff queue. Shutdown closes watchers and subscriptions, drains in-flight work, settles required Jazz writes, records terminal runtime evidence, and closes the database context.
Manifest presence does not grant authority. A source declaration can read only through its connector contract; agent declarations remain tool-free and action-free; Telegram ingress cannot send; the inspector remains loopback-only. Review decisions require the separate browser and loopback capabilities described above. Network sources are disabled in the repository manifest by default.