Layer 02 · Domain model

Domain model

A small set of records carries the whole product: people sign in through identities, publish traces under namespaces, and let a background pass summarize what their agents did. This page explains the concepts, who owns what, and the rules that keep the model consistent.

The concepts and their relationships

The diagram shows the domain at one altitude: account concepts on the left, content concepts on the right, and the namespace as the ownership hub between them. Everything publishable hangs off a namespace; everything readable hangs off a trace.

flowchart LR
  subgraph Accounts
    USER[User]
    IDENTITY[Identity
device · GitHub · Google] NS[Namespace
individual or org] MEMBER[Membership
admin · member] INVITE[Invite code] KEY[API key] BOT[Agent bot identity] end subgraph Content TRACE[Trace] MSG[Message] PART[Part] ANALYSIS[AI analysis] CHAT[Assistant conversation
agent session] end USER -- "signs in via" --> IDENTITY USER -- "joins with role" --> MEMBER MEMBER -- "binds user to" --> NS INVITE -- "admits user into" --> NS KEY -- "scoped to" --> NS KEY -- "attributes uploads to" --> BOT BOT -- "owned by" --> NS NS -- "owns" --> TRACE USER -- "created" --> TRACE TRACE -- "contains" --> MSG MSG -- "contains" --> PART TRACE -- "summarized by" --> ANALYSIS TRACE -- "references (subagent · resume)" --> TRACE USER -- "converses with assistant in" --> CHAT

Ownership rules that shape behavior

Every user gets a personal namespace. First login creates the user, a personal (individual) namespace, and the membership binding them, in one step. There is no state where a user exists without somewhere to publish.

Traces are never ownerless. A trace always records both its namespace and its creator, and optionally a bot identity when an API key published it. The permission rule that follows: namespace members read private traces, but only the creator or an admin may change, delete, or transfer one. These rules are enforced centrally in api/convex/lib/policies.ts.

Visibility is capped by the namespace. An org can forbid public traces by restricting the allowed visibility list; the publish path refuses any level outside it. The namespace settings live on the namespace record itself in api/convex/schema.ts.

Identity is many-to-one with user, cautiously. A device UUID, a GitHub account, or a Google account each map to at most one user. Google sign-in only attaches an existing identity automatically when every email match belongs to one user and at least one match is provider-verified — ambiguous matches are rejected and require explicit linking while signed in. See api/convex/services/oauth/google.ts and api/convex/lib/oauth_identity.ts.

Lifecycles

The trace

A trace begins on a developer's machine, parsed from agent storage by a CLI adapter. Publishing creates the server record; re-sharing the same session is an upsert against the same external ID, so the URL never changes. Unpublish is a soft delete: the record stays (preserving the external ID and URL) while message bodies are hard-deleted from the analytics store by a background job. Re-publishing a soft-deleted trace clears the marker, wipes stale leftovers, and pushes fresh content under a new publish watermark. Transfer to another namespace moves ownership without moving the stored message rows. The trace record with its lifecycle fields is defined in api/convex/schema.ts; the write paths that keep the two stores in step live in api/convex/services/traces.ts.

Sign-in sessions

Cookie sessions expire after 180 days; CLI device sessions never expire and must be revoked explicitly; access tokens live one hour and die with their parent. A scheduled job sweeps expired sessions hourly (api/convex/crons.ts). Usage timestamps are written at most once per five minutes per credential to avoid a database write on every request.

Invites

An invite code is created for an org with a role, then moves through active → accepted / revoked / expired. Acceptance binds the accepting user into the namespace as a membership; capped codes count their uses. Defined in api/convex/schema.ts.

AI analysis state

Each trace carries two scheduling fields rather than analysis output: a counter of messages ingested since the last run, and a "last analyzed" timestamp. The counter crossing a threshold (or the first-ever upload) is what requests a run; completing a run resets the counter. The analysis output itself is enrichment stored separately, so a failed later run never clobbers a good earlier one.

Assistant conversations

The assistant's conversation state is append-only: a session record plus ordered entries, where each entry ID doubles as an idempotency key so re-sent batches are harmless. The service persists through a write-behind store — memory is updated synchronously and the database catches up in the background with retries. Deliberately separate from the trace entity; see api/convex/schema.ts and agent/src/runtime/convex-session-store.ts.

Where the authoritative models live

ConceptAuthoritative definition
All account and trace recordsapi/convex/schema.ts (database schema)
Message & part shapespackages/shared/src/message.ts, packages/shared/src/part.ts
Trace metadata & relationshipspackages/shared/src/trace.ts
CLI event modelcli/src/types.ts
Permission rulesapi/convex/lib/policies.ts
Message/part body storagetinybird/datasources/ (messages, parts, traces, traces_ai_analysis)
Permission checks in the assistantagent/src/authz.ts