A local-first event pipeline for independent agents, built on Jazz.
25 kB

Activity and Review interface #

Private Co chat release #

The OAuth-only /chat/ application is the private chat-first entry, with the inspector retained as a secondary evidence surface. Its server binds only to the existing agent configured by the operator through THOUGHTSTREAM_CO_AGENT_ID; it never creates or migrates an agent. New conversations isolate web transcripts, not agent-level memory. Existing memory and action/privacy instructions remain shared. The operator explicitly authorizes unrestricted harness tool execution without an approval UI; this does not authorize new connectors, publication, or wider source access. Real agent and computer identifiers belong in private deployment configuration, not public source.

A server-owned encrypted registry contains only conversations created by this web app, user-assigned titles, and send receipts. Every history/send/rename/stop operation resolves an opaque web id against it. No agent-wide conversation list, default/latest fallback, Signal import, raw SDK proxy, memory reader, model setter, or tool-config endpoint exists. Canonical transcripts remain in Letta. Browser projections omit reasoning, raw tool arguments/results, and protocol errors. Text and collapsed named tool outcomes preserve SDK accumulator order, with code copying and no model-authored HTML or remote images.

The responsive React shell follows the Letta React chat demo's ordered-row, sidebar, composer, and follow-output patterns (upstream revision ee47d2034eb667ad2eecb1cb9b292030764f01e7). It uses a separately pinned SDK 0.8.3 adapter, leaving existing consumer SDK 0.5.7 unchanged. Enter sends, Shift+Enter inserts a line break, and IME composition never submits. History paginates, selection uses fragment routes, reading older output does not force scrolling, and mobile controls have 44px targets. No private browser transcript/draft cache or offline service worker is added.

Selected context is optional, explicit, and bounded: the owner may paste a selected Stream excerpt (at most 4,000 characters) with a source label and observation time. The app fetches no feed or private history automatically. The server wraps the excerpt as untrusted, possibly stale evidence, separate from the question. No new data scope is introduced. Automatic source retrieval and file/image uploads are deferred.

Purpose #

The first interface proves that the stream and agents are alive. It uses one narrow chronological column modeled on Cameron's public site rather than a dashboard grid. Its visible name is Stream.

Root view #

Each activity card shows:

  • Observed time.
  • Source icon/name.
  • Event type and concise renderer.
  • Privacy class.
  • Root/parent lineage affordance.
  • Consumer executions and lifecycle state.
  • Derived output count.
  • Failure or blocked evidence.

The feed has one card per originating stream.thought.source.* observation, newest first. The observation title, content, source, time, and privacy are primary. Consumer summaries sit in one collapsed processing section. Selecting a card opens a focused view in the same column with complete lineage and technical evidence; it does not create a second dashboard pane. Self-rooted connector, runtime, dispatcher, action, and other operational receipts remain available through source health and detail views but never occupy the feed. Cards summarize semantic results in ordinary language; lifecycle NSIDs and record identifiers are execution details.

X post observations use their stable normalized record rather than the generic event renderer. They show the operator-configured author handle, post time, reply target when the normalized mention matches the reply user id, body text without the redundant leading reply mention, media-presence note, and direct post/parent links. This presentation does not dynamically fetch mutable X profiles or metrics and does not imply that the post has been interpreted by an agent.

Deterministic transforms are labeled rules, not agents. The interface states explicitly whether LLM inference occurred. It must not imply model reasoning when a path-based or other hard-coded transform ran.

Filters stay collapsed above the feed until requested. The current controls cover source, activity kind, processing state, and text.

On phone screens, the private interface behaves as an app shell rather than a compressed desktop document. A compact top header scrolls with the document so the feed can reclaim the viewport after the reader moves down. It places the uppercase STREAM wordmark above five low-profile destinations: Feed, Learn, Review, Artifacts, and System. Review contains local Suggestions and Comparisons views; System contains local Runs and Sources views. These local choices appear as one segmented control only after their destination is selected, rather than expanding the global navigation. The selected destination uses a high-contrast pill; inactive destinations use quiet outlined pills. Mobile pills preserve their horizontal width while reducing vertical padding. The navigation may scroll horizontally on compact phones without making the document overflow. Medium and desktop widths retain the site's quieter text-navigation grammar rather than scaling the phone pills into oversized chrome. Selecting any detail hides global and local navigation until the reader returns to the list, so the object itself owns the viewport.

Every list and detail view has a fragment route under the authenticated inspector URL. Selecting a destination or object pushes its route into browser history; browser Back restores the previous inspector view instead of leaving the app for the OAuth flow. The in-app Back control follows that same history when an internal parent exists and otherwise replaces a directly loaded detail route with its owning list. Reloading a fragment route restores the selected tab and object. Private object identifiers remain in the browser-only fragment and do not enter proxy requests or server access logs.

Feed and Sources expose filtering as an app control, not an expanded form or a separate title/toolbar row embedded in the document. The active navigation already names the view, so the Filter control lives at the far right of the global navigation line on medium/desktop widths and becomes the next horizontally scrollable phone control. It opens a modal sheet with full-width, touch-sized fields. On phones the sheet is anchored to the bottom safe area; wider screens use a centered modal. Active filters return to the main view as individually removable chips and a numeric count; zero active filters render no count. The feed runs edge to edge inside safe-area padding. Detail selection replaces the feed in the same viewport and preserves the existing back affordance. The page publishes standalone-app metadata and an exact private manifest/icon route; it does not cache private HTML, JSON, media, or Jazz-derived content offline.

The visual system uses spacing, contained cards, and local control borders instead of full-width horizontal separator rules. Feed entries, the page header, footers, course sections, forms, and modal sections do not draw decorative top or bottom rules. The selected desktop navigation underline and borders that define an actual card, input, button, or status edge remain meaningful controls rather than document separators.

An intentionally hidden browser-local font preview lets Cameron compare Google Fonts against the complete interface. Triple-clicking the STREAM wordmark or pressing Alt+Shift+F reveals the panel. It accepts an exact Google Fonts family name, applies it only to the body-font variable, keeps Around on display headings, and stores the selected family in same-origin local storage until reset. The feature creates no server record, cookie, event, or remote mutation. Loading a selected family makes an on-demand browser request to Google Fonts; the default page makes no Google request. The gesture is an obscured debug affordance, not an authorization boundary.

Bluesky avatars and embed thumbnails are rendered through one authenticated same-origin media route because the private web boundary intentionally denies arbitrary third-party image loads. The route accepts only HTTPS cdn.bsky.app/img/* URLs, follows no redirects, applies strict byte, timeout, content-type, and image-magic bounds, and returns no upstream detail on failure. Full-size post links remain ordinary external navigation. A failed image is removed from the card rather than leaving a large broken placeholder.

The inspector exposes a read-only adapter inventory with public-safe release metadata, canonical lifecycle status/generation, a separately rendered active deployment binding when one exists, selecting consumers, bound runs, and output event ids. It must distinguish the execution adapter from the learned model adapter, and release status from deployment binding, and must never render checkpoint paths or resolved environment values.

The inspector exposes a read-only metadata-only artifact catalog and per-artifact detail view. The catalog lists artifact id, version, kind, title, summary, media type, byte count, SHA-256 prefix, visibility, provenance label, and supersession status, never bytes. Detail resolves and re-verifies the private blob on demand: text is escaped and images use a separate private content route. Artifact bytes are never exposed through public routes. See artifacts.md.

The Learn destination renders the reviewed post-training course as eight compact lessons with one interactive exercise per lesson. Lesson routes live in the browser fragment, so reload and Back restore the current lesson without placing private object ids in access logs. A floating composer appears only inside Learn. It appends one OAuth-authorized question against the current course revision and polls one exact source receipt for the tutor result. The composer never accepts Basic authentication, chooses lesson context on the server, and does not send client-authored transcript history to the model. See courses.md.

The Sources view is a control-plane inventory, not a list inferred from whichever producers have already emitted data. It shows every configured source and keeps these states separate:

  • configured and enabled in operator-owned source configuration;
  • receiver/runtime readiness and the age of its latest readiness receipt;
  • upstream registration validity and desired-versus-live subscription convergence when the source has an upstream control plane;
  • durable data-plane evidence: cursor, last success/failure, current error, receipt counts, recovery count, and an operation started without terminal evidence.

No activity yet is a valid state, not source absence. Database evidence cannot prove process liveness or upstream registration, and an upstream registration cannot prove event delivery. The interface labels each receipt boundary instead of compressing them into one generic health badge.

Inspector bootstrap requests that scan shared Jazz state run serially rather than contending in the browser. Root observations have a dedicated bounded endpoint and render before their optional processing evidence is hydrated. The heavier System projection is a separate endpoint and loads after the first activity paint unless System is the selected destination. A live event refreshes the observation cards first, then refreshes their processing evidence; it refreshes System evidence only while a System list is selected. While processing evidence is pending, the Feed does not claim either that an observation was processed or that it was not. Every tab and detail request has an explicit loading, empty, and failed state, and the client verifies HTTP status and JSON content type before parsing. A reverse-proxy HTML error page must never appear as a raw response.json() exception or leave another pane permanently labeled Loading.

The inspector prewarms and retains both bounded Feed projections in process. An ordinary page load reads those retained projections without rescanning Jazz. A live-event refresh explicitly requests fresh observations and processing evidence, then replaces the retained values. HTTP responses remain private and no-store; this is a process-local projection cache, not browser persistence or a second source of truth.

The bounded Feed projection scans one deterministic window of at most 400 recent source records to select up to 100 root observations, so ordinary child records such as reactions and corrections do not displace unrelated roots. If that hard scan cap cannot recover a complete root window, the response and UI say so instead of falling back to a global scan. It then hydrates at most 500 events in those root lineages, 250 recent runs, and 250 runs directly triggered by the hydrated events. Same-timestamp query limits use the record key as a deterministic second ordering term. When either the recent-run or root-lineage hydration cap is reached, each observation marks processing history incomplete; the UI may show found processors but cannot classify an empty bounded result as “not processed.” The Feed path never enumerates complete run history; complete run evidence remains a System concern.

Documents #

The Documents destination lists operator-owned working documents and opens one in the same column: a title field, a body editor, a Sources section showing the exact selected evidence with event ids, payload hashes, version ids, and content hashes, a Proposals section, and a Versions list. From an observation detail, a compact Create working document control appears only when the session has write access; otherwise a one-line note says why it is absent. Save submits the head version the editor loaded and, on a stale-base conflict, keeps the draft in the browser, explains that the document changed, and offers a reload. Select context opens a bounded checkbox list of recent observations plus the document's own versions and records only the checked items. Request proposal names the runner and whether it performs inference. Each proposal shows its status, reason, base version, and a unified diff rendered as escaped text lines with +/- markers, never as HTML; a proposal whose base is no longer the head shows a stale notice with Accept disabled. Errors render at the action that failed. Drafts persist in browser local storage until saved. Every write carries a client-generated request or submission id held in page state; after a network failure the outcome is reported as unknown, the document is reloaded before any retry, and the same id is reused rather than resending under a new one. See documents.md.

Detail view #

  • Consumer lifecycle and derived-output details lead with a plain-language what processed this block: the declaration display name, actual trigger source, whether it was a rule or model, whether LLM inference occurred, the exact produced summary, any recommendation or proposal that actually exists, derived-record count, and whether external actions were enabled. Technical agent ids and context strategies belong in execution details. Runtime metadata must not be appended to model-authored prose. A reader must not have to traverse raw lifecycle payloads to discover either the semantic result or its provenance.
  • Canonical envelope and payload.
  • Source strong reference.
  • Document version/diff when applicable.
  • Causal tree from root to derived outputs.
  • Exact context manifest with truncation markers.
  • Model, provider, observed checkpoint revision, execution-adapter revision, learned model-adapter identity, prompt revision, usage, duration, and attempts.
  • Trace event summary and raw redacted trace download.
  • Projection contributions.

Local context inspection #

The operator CLI may resolve one exact run's durable contextSnapshot identity and inspect the model-facing packet. It must verify the snapshot document id/source/hash/size, parse all internal packet hashes, and require the snapshot manifest to match the run manifest before returning anything. For Pi runs, it also attempts exact model-input reconstruction from the matching stored declaration and shared runtime composition function, then requires the reconstructed system/current prompt lengths and hashes to match the run's durable content-dark prompt traces. Exact reconstruction fails closed when the declaration is absent or mismatched, read-only prefetch bodies or resolved image bytes were not retained, or trace evidence disagrees. The default result is content-dark metadata: packet sizes, role sequence, hashes, image-reference count, exactness reasons, and the exact manifest. Printing system text, current text, native message bodies, tool-call arguments, or image artifact references requires both --show-content and --acknowledge-sensitive-private; reconstructed model-input content is returned only when exactness is proved. This is a local read only; it creates no event, run, trace, projection, action, or external request. Hidden provider reasoning is not part of the packet and remains unavailable.

Constraints #

  • Localhost by default.
  • No client-side secrets.
  • No send, publish, arbitrary edit, adapter activation, or generic Jazz controls. The Review decision form, agent-proposal decisions, and the bounded working-document writes in documents.md are the only data mutations, and all of them are append-only through the trusted server path.
  • Streaming UI may use server-sent events; persistence never depends on the browser being open.

Authenticated external access #

The inspector itself remains loopback-only and has no authentication or public network authority. External access is an operator deployment made from three separate layers:

  1. the loopback inspector reads the private Jazz store;
  2. a separate loopback proxy authenticates every request before forwarding only GET and HEAD to the inspector;
  3. an operator-owned HTTPS reverse proxy terminates TLS and forwards to the authenticated loopback proxy.

The authenticated proxy may load a high-entropy HTTP Basic credential from a separate owner-only environment file outside the runtime data root. Basic is disabled by default and requires an explicit true flag plus that credential. When enabled, the proxy compares a fixed-length digest in constant time, strips authorization and cookie headers before forwarding, does not log requests or credentials, returns generic upstream failures, and applies no-store, anti-framing, no-referrer, and content-type security headers to every response. When disabled, the process does not require or decode the credential. The proxy may bind only to loopback. Basic authentication is not safe over plaintext HTTP, so an enabled public route must redirect to HTTPS before it is considered deployed.

Authentication gates every /inspector route and every inspector /api/ route. There is no public health, event-count, source-name, runtime metadata, manifest, trace, or error endpoint. A failed login must not touch the inspector or disclose whether a requested private object exists.

Public site and route separation #

The same loopback web process may serve an allowlisted public surface, but public and private routing are separate capabilities rather than a shared fallback:

  • / is a minimal Cameron.stream-shaped landing page: the Around-set Stream wordmark, one Log in form posting directly to the OAuth flow, and one code footer link. It contains no descriptive sentence, docs navigation, private-data counts, or architecture summary. /docs and named /docs/* pages remain directly addressable from an explicit repository-owned public-content allowlist, but the landing page does not promote them. Route input can never select a filesystem path.
  • /oauth/client-metadata.json, /oauth/jwks.json, /oauth/login, /oauth/callback, and /oauth/logout are the only public authentication routes.
  • /inspector and /inspector/* are the only routes that may forward to the loopback inspector. The prefix is removed before forwarding.
  • Unknown routes return a local content-dark 404; they never fall through to the inspector.

Public rendering has no Jazz handle, runtime root, manifest reader, event query, trace reader, environment dump, directory listing, or generic file-serving primitive. Public pages are built from reviewed Markdown files under public/ with a fixed route-to-file map and conservative escaping. They contain architecture and operating concepts only, never source names, counts, event payloads, host paths, credentials, live service metadata, or private project state.

ATProto OAuth inspector authentication #

ATProto OAuth is a browser-to-web-service authentication flow distinct from authorization to read the inspector. The official Node OAuth client owns protocol requirements including PKCE S256, PAR, DPoP, nonce handling, token refresh, identity resolution, and authorization-server discovery. The web boundary adds these constraints:

  • Public client metadata is served at its exact HTTPS client_id URL with no redirect. JWKS is public; the matching private ES256 key remains outside Git in owner-only service configuration.
  • OAuth state, DPoP keys, access tokens, refresh tokens, and browser sessions remain server-side in an encrypted owner-only store outside the thought stream runtime root. Browser cookies contain only random opaque session ids.
  • Random application state is short-lived, one-time, stored separately, and bound to an HttpOnly, Secure, SameSite=Lax callback cookie. It is passed into authorize; the SDK generates and validates a distinct protocol-state query value. After SDK callback succeeds, the returned application state must match and consume the browser-bound record.
  • Successful callbacks are accepted only for the configured allowlisted DID. A different DID is deleted from local staging without promotion or an application-initiated remote revocation and receives no inspector session; independent SDK revocation remains a provider-side residual.
  • Inspector sessions are short-lived, carry a per-DID promotion generation, restore only against that exact stored generation, and carry a separate CSRF token for logout. Restore get/set/delete are scoped to the initiating generation and rechecked afterward. Expiry, restore failure, and CSRF-checked logout delete only the matching local generation and expire the cookie; application code sends no provider revocation that could invalidate newer authority.
  • SDK state, SDK session, application-flow, and browser-session stores have hard entry and serialized-byte bounds and one process owner. The store path is fixed to the one owner-only directory permitted by systemd; custom paths fail closed. Login and callback have independent nginx and in-process rate limits.
  • SDK callback has no abort API, so callback credentials settle first in an attempt-scoped staging store. One watchdog covers callback, application-state consumption, promotion, browser persistence, and cleanup. Timeout marks the attempt non-promotable before advancing the serializer; every authority-bearing persistent write rechecks that status immediately before rename. Late expired staging is deleted locally without application-initiated remote revocation, protecting local newer generations. The unmodified SDK may still revoke on other failure paths. Eight retained attempts trigger a fail-closed operator-recycle 503 until settlement or restart.
  • OAuth failures are content-dark. Tokens, DIDs other than the configured allowlist, handles, state values, cookies, provider bodies, and exception detail are not logged or returned.
  • HTTP Basic is an independently configured, default-off emergency fallback. Enabled Basic authorizes inspector reads only and bypasses OAuth restore; disabled Basic is not loaded or advertised. An operator may retain it through OAuth activation only until one real successful OAuth login and bounded rollback read are recorded. Code presence or fixture tests do not count as that receipt.

OAuth supplies identity and, only when the separate Review capability is configured, access to the one fixed append-only Review-decision route. It grants no generic thought stream write, publish, model, connector, dispatcher, Jazz, filesystem, adapter-activation, or public-post authority.

Review view #

The private Review tab is a dense judgment workbench, not a trainer dashboard. It shows complete evidence before controls, uses blinded A/B presentation, asks judgeability before preference, supports tie and correction, and reveals provenance only after a decision. Mobile stacks evidence and candidates; desktop may use two columns. Campaign progress is descriptive and carries no gamified streak or speed target.

See review.md for event, authority, supersession, privacy, and export semantics.