Context: Team Kitty and Zeitgeist
Read this before touching anything hosted. It is the one-page model every
agent needs when work mentions the SaaS, moments, presence, readiness, auth,
SPEC_KITTY_ENABLE_SAAS_SYNC, or the word "sync". Written for the
agentic-framework-core-team persona; it mirrors the shipped code at the heads
cited at the bottom, and a disagreement between this page and the code is a
doc defect to fix here.
The one-paragraph model
Team Kitty is the hosted product. Zeitgeist is a small, volatile presence-and-moments relay that Team Kitty runs as one container per team. The Spec Kitty CLI talks to the SaaS only to authenticate and to mint a capability; it then publishes each status moment straight to the team's relay in a single bounded HTTP request. The SaaS never receives moments over HTTP: it polls the relay back ("Pulse") to build its activity feed. Nothing is queued anywhere, nothing is persisted on the relay, and the only gates are server-side: team membership plus the repository being admitted to that team. There is no client-side opt-in, no consent grant, no offline queue, and no daemon.
"Sync" is dead
The transport called "sync" — a local daemon, an offline queue, a per-project
consent store, spec-kitty sync opt-in, POST /api/v1/events/batch/ — was
deleted on both sides in August 2026:
- CLI: the
specify_cli.syncpackage was removed with the sync transport surfaces (commit66038e2a5, 2026-08-25) and the convergence (PR #3881).tests/architectural/test_no_retired_subsystems.pyrefuses its return. - SaaS:
apps/syncwas deleted (spec-kitty-saas #260, 2026-08-26). Every oldapi/v1/sync/*,api/v1/events/*,api/v1/dossier/*andws-tokenpath returns a permanent, request-blind 410legacy_ingress_retired(spec_kitty_saas/retired_ingress.py).
The word survives only as residue: an environment-variable prefix, a URL prefix kept for one live endpoint, stale docs, and comments. When you read "sync" in this repository, translate it: the live thing is Zeitgeist. Do not design against, extend, or "re-enable" sync.
Canonical vocabulary
| Term | Meaning | Do not say |
|---|---|---|
| Team Kitty | The hosted product (SaaS repo spec-kitty-saas, production https://team.spec-kitty.ai). |
"the SaaS sync", "Spec Kitty Cloud" |
| Team workspace | The tenant container a person belongs to; a single-user tenant is a private team workspace. Naming ADR: saas docs/adr/2026-08-02-team-kitty-canonical-product-name.md (amended by saas PR #704). Code and the OpenAPI contract still say teamspace; treat that as a compatibility identifier, not a term (spec-kitty #3154). |
"teamspace" in new prose |
| Zeitgeist | The presence-and-gossip relay (repo EXPERIMENTAL-zeitgeist). Runs in managed profile for Team Kitty; the CLI targets only that profile. |
"the event server", "sync server" |
| Relay | One Zeitgeist container per team, provisioned by the SaaS on a team's first repository admission, idle-suspended and woken on demand. Volatile: a restart empties it. | |
| Moment | One content-agnostic, bounded status event published as op: event.publish with {kind, ref?, attrs}. The relay carries it and never interprets it. A moment is activity about a session, never liveness of one. |
"sync event", "envelope" (the envelope is the wire wrapper) |
| Presence / focus | Liveness samples (presence.publish, TTL ≤ 90 s) and focus sessions (focus.start/heartbeat/pause/end). The CLI refreshes presence after each moment when it holds a lease. |
|
| Capability credential | A signed statement of what a caller may do on one (team, deployment, repo), kinds presence (also grants event.publish), focus, operator, observer. Minted by the SaaS, never by Zeitgeist. It is a capability, not an identity. |
"API key", "token" (that is the separate bearer) |
| Admission | The SaaS-side decision that a repository belongs to a team (TeamRepository row on an enabled, healthy installation; one team per repo per provider). Membership plus admission is the whole gate. |
"consent", "opt-in" |
| Pulse | The SaaS poller: every 60 s per admitted repo it mints an observer credential, reads GET /managed/discover and GET /managed/events?since=<epoch>:<seq>, and upserts TeamMoment rows keyed (team, repo_slug, event_id). Retained for a rolling window, default 72 h. |
Charter rule on the Zeitgeist side, which the CLI mirrors: fail open on availability, fail closed on identity. An unreachable relay never blocks canonical local persistence; a missing credential means no moment is sent.
Sequence: one lane transition
sequenceDiagram
autonumber
participant A as Agent / CLI command
participant E as status.emit (canonical event log)
participant Z as zeitgeist_bridge + zeitgeist_client
participant S as Team Kitty SaaS
participant R as Team's Zeitgeist relay
participant P as Pulse (SaaS poller)
A->>E: move-task --to for_review
E->>E: append event, materialize snapshot (always succeeds locally)
E->>Z: fire_saas_fanout (daemon thread, 10 s bound)
Z->>Z: load bearer (auth login session / service token)
alt no usable credential cached
Z->>S: GET /api/v1/sync/repo-admission/?repo_slug=…
S-->>Z: admitted + team, or 403 / not admitted (negative cached 5 min)
Z->>S: POST /api/v1/live/capability/cli/ {repo_slug, kind: presence}
S-->>Z: relay_url, relay_token, capability_credential, expires_at
end
Z->>R: POST /managed/control op=event.publish (one request, 750 ms budget)
R-->>Z: 202 / 429 / 4xx (logged, never retried)
Z->>R: presence.publish (and focus.start if a focus lease exists)
P->>R: GET /managed/events?since=epoch:seq (every 60 s, observer credential)
P->>P: upsert TeamMoment, render activity feed
Alt text: the CLI persists locally first, resolves a capability from the SaaS only when it has none cached, publishes the moment directly to the team's relay once without retry, and the SaaS learns about it by polling the relay.
Where the code lives (CLI)
| Step | Module |
|---|---|
| Canonical persistence, then fan-out | src/specify_cli/status/emit.py (emit_status_transition → _saas_fan_out → fire_saas_fanout) |
| Handler registry, 10 s bounded thread, in-flight de-dup | src/specify_cli/status/adapters.py (ensure_zeitgeist_moment_handlers registers the three moment handlers at import unless SPEC_KITTY_SYNC_MINIMAL_IMPORT is set) |
| Payload → attrs codec, offer, liveness refresh | src/specify_cli/status/zeitgeist_bridge.py (codec is the pinned spec_kitty_events package: to_zeitgeist_attrs, zeitgeist_ref_for) |
| Credential resolution, admission and mint gateway | src/specify_cli/zeitgeist_client/resolution.py ("team admission is the gate") and credentials.py (TOML store under the runtime state root) |
Transport (offer, presence, focus), 750 ms budget, sanitizer |
src/specify_cli/zeitgeist_client/transport.py, budget.py, sanitizer.py |
Read side (spec-kitty zeitgeist status|watch, MCP zeitgeist_status/watch) |
zeitgeist_client/subscription.py, filtered_stream.py, mcp_stdio.py; [moments] config in moments.py |
| Bearer for the SaaS calls | src/specify_cli/saas_client/auth.py::load_auth_context → env token, .kittify/saas-auth.json, or the spec-kitty auth login session via auth/server_target.py and the token manager |
Lifecycle moments (mission created, specify/plan/tasks beats, decision points,
op invocations) travel the same path through fire_lifecycle_saas_fanout.
Where the code lives (SaaS and relay)
- Auth server:
apps/cli_auth(/oauth/authorize,/oauth/device,/oauth/tokenincl. refresh,/oauth/revoke,GET /api/v1/me).session_invalidmeans re-authenticate;access_token_expiredmeans refresh once. - Capability mint for the CLI:
apps/live_capability/views.py(POST /api/v1/live/capability/cli/), gates inissuer.py(team kill switch → membership → repository admission). Error codes:not_member,repository_not_admitted,team_disabled,deployment_unavailable, 503mint_failed/relay_wake_failed. - Admission preflight the CLI calls today:
apps/connectors/admission_views.py(GET /api/v1/sync/repo-admission/) — deliberately kept on the retired prefix becausezeitgeist_client/resolution.pynames it. - Relay tenancy, Pulse, retention, feed:
apps/live_capability/(auto_provisioning.py,pulse.py,models.py::TeamMoment,moment_presentation.py). - Relay wire contract:
EXPERIMENTAL-zeitgeist/zeitgeist/managed.py,managed_auth.py,zeitgeist/schemas/managed_*.schema.json; executable end-to-end referencebin/probe-managed-event-publish-hosted.sh. - Authoritative CLI contract: spec-kitty-saas
contracts/cli-saas-current-api.yaml.
What SPEC_KITTY_ENABLE_SAAS_SYNC still gates (and what it does not)
The moment path above is not gated by this flag. Moments go out by
default whenever a credential resolves. The flag still switches on four
leftovers. Two gate functions in src/specify_cli/core/saas_sync_config.py
read it: leftovers 1-2 check the bare is_saas_sync_enabled(), while leftover
3 checks sync_active() (is_saas_sync_enabled() and no
SPEC_KITTY_SYNC_DISABLE set) -- so SPEC_KITTY_SYNC_DISABLE lifts only
leftover 3, not the readiness nag, the tracker group, or --from-ticket:
- the startup readiness/auth nag (
readiness/coordinator.py); - the
trackercommand group andmission create --from-ticket; OWNED_SYNC_UNSUPPORTED, a refusal of owned-checkoutmove-task/mark-statuswhen the flag is on (tasks_move_task.py,tasks_mark_status.py) — a conservative guard added on 2026-09-02 in the dead vocabulary; an owned checkout publishes moments normally when the flag is off;SPEC_KITTY_SYNC_DISABLE/SPEC_KITTY_SYNC_MINIMAL_IMPORTdouble as the pre-review-gate opt-out and the moment-handler import gate.
Retiring these names is tracked by spec-kitty #3980 under the launch gate #1621 / #1091. Until then, name the sense when you touch them.
Stale surfaces you will meet
Marked deprecated, describe the deleted transport, keep for history only:
docs/architecture/team-kitty-saas.md, docs/operations/sync-drain.md,
docs/guides/project-sync-consent.md, the 2.x/3.x sync ADRs. On the SaaS
side docs/architecture/teamspace-compatibility-handshake.md still says
"Production" but its code is gone, and contracts/consumer-compatibility.json
pins versions the SaaS no longer uses. Vestigial ensure_sync_daemon /
ensure_daemon parameters are threaded through status/emit.py,
coordination/status_transition.py and friends and read by nobody.
Sources
Read at these heads on 2026-09-07: spec-kitty d6e8fe423,
EXPERIMENTAL-zeitgeist 9b6553e, EXPERIMENTAL-spec-kitty-saas 93e2ad2.
Related: ADR
convergence retirement and client-repo inversion;
system events for the canonical event envelope;
environment variables.