Skip to main content
Second is built around a workspace-first data model and a streaming agent architecture. The browser talks to a Next.js API layer, which delegates agent work to a separate worker process. Access checks, review state, and source/data scoping live in the web layer before any worker output becomes trusted runtime state.

System overview

App agent and data flow

In addition to the builder agent chat flow, apps can trigger agents and persist data:
See App Governance, Source Control, App Agents, App Data, and Integrations for details. Draft and published runtime state are intentionally separate. Published app views read and write app data under the published app ID. Draft previews and draft app-agent runs use a draft data scope derived from the same app ID, so a builder can test data changes without mutating the currently published app data.

Services

Collections

Every workspace-owned entity carries a workspaceId field and is always queried with it. Nested resources are also loaded with their parent IDs, for example { workspaceId, appId, runId }, so a run from one app cannot be read through another app in the same workspace.

Request flow (standard routes)

  1. Middleware proxy — redirects unonboarded users, rejects unauthorized API calls.
  2. Route handler — calls requireWorkspaceContext to validate actor + workspace membership.
  3. Permission check — sensitive routes check a named workspace permission such as integrations:manage or members:invite.
  4. Repository — runs the database query, scoped by workspaceId.
Cross-workspace access returns 404 — not 403 — so a caller learns nothing about resources in other workspaces. App routes add one more check after loading the workspace: owners and admins can see every app, app creators (createdByUserId) and app collaborators (collaboratorUserIds) can see their private drafts/review requests, and published apps are visible only to members of the selected teams. Nested routes repeat the same rule at each parent boundary. App files, chat runs, app-agent runs, data, agents config, and settings all first prove workspace membership, then load the app by { workspaceId, appId }, then load child resources by the full parent scope.

Workspace realtime and settings reads

Workspace chrome uses explicit Redis-backed domain events rather than request-scoped MongoDB change streams. Mutations publish small events such as app.created, app.updated, review.updated, integration.changed, member.changed, run.stream_ready, and run.completed. Events contain ids, status, timestamps, and invalidation scopes; they never carry prompts, source files, secrets, headers, cookies, or full database documents. WorkspaceRealtimeProvider owns one workspace event subscription around the workspace shell. Sidebar, app chrome, integration callouts, settings pages, and run-status indicators subscribe to that provider instead of each opening their own workspace event stream. Run chat streaming remains separate because it has stricter ordering and replay requirements. Settings pages render a cheap route shell first. Members, teams, invitations, and integrations then load through projected API read models. These API routes still authorize every request with requireWorkspaceContext; a short in-process dedupe window only shares duplicate reads for the same workspace, user, role, and membership version. Realtime invalidations are hints, not authorization decisions.

App publishing and review

New apps start as draft. A draft is visible only to its app creator, explicit app collaborators, and workspace admins and owners. Creator and collaborator are app-level access categories, not workspace roles. When the builder is ready to publish, the requester selects one or more workspace teams:
  1. In local none auth mode, publishing has no approval step and only marks the app as published.
  2. In external/on-prem mode, members create a pending app review request.
  3. Admins and owners see pending requests in the review inbox, inspect the target teams, and review integration requirements.
  4. Approval promotes the reviewed draft snapshot to the published snapshot and shares that published version with the selected teams. If any requested integration still needs configuration, approval is blocked until an admin or owner configures it.
Owners and admins can self-publish after reviewing the app, but that path still records an approved review request for auditability. If the requester changes the app after creating a pending review — for example by sending another builder message or editing the approved agent configuration — the pending review is automatically closed as superseded and the app moves back to draft. Stale review approvals are rejected, and the requester must send the updated app for review again. Published apps keep a separate published source snapshot. Builders and agent-configuration edits mutate the draft source snapshot only. Current snapshots are stored in app_source_snapshots; the apps document keeps metadata pointers, hashes, file counts, and sizes so navigation and access checks do not load large file maps. Legacy embedded sourceFiles and publishedSourceFiles are still read as a fallback for old apps until they are saved or migrated. When an app creator or collaborator edits an already published app, the app keeps serving the last published snapshot to team viewers while the builder works on a draft. Publishing locally or approving a review promotes the current draft snapshot into the published snapshot. See App Governance for the full role and review flow. Source control is the repository-backed app source storage layer. Connecting a provider such as GitHub, GitLab, or Bitbucket at the workspace level only stores credentials and owner metadata; it does not upload apps by itself. Local CLI/desktop installs use app-level Publish to source control. On-prem or managed deployments can enable a workspace-level Store app source in source control policy, which makes successful builds sync sanitized source and built artifacts to the configured provider and create auto-bumped second-app-v<N> tags. Normal app page loads still render a cached built artifact; source restore happens only when a source-control-backed app needs files after the live worker/container session is gone. Available Apps is a separate discovery layer, not the definition of source-control storage. See Source Control. agents.json is a protected draft artifact. The builder and file tools may edit it, but live agent runtime permissions are trusted only after the platform records an approval for the versioned canonical JSON hash. The canonicalizer is schema-versioned so harmless representation changes, such as missing optional arrays versus empty optional arrays, do not invalidate approval. Any later effective agents.json policy change clears that draft approval. Draft app-agent runs can start from the draft file so builders can test the in-progress app, but custom HTTP tools and agent data tools require the current draft hash to match the stored approval before they can touch live integrations or app data. Publishing and review approval promote both the source snapshot and the approved agents.json payload into the published snapshot. Integration domains and OAuth metadata are approved at runtime, not trusted from model output. A custom tool must exist in the approved agents.json payload, resolve an app-scoped integration grant by workspaceId, appId, domain, and keySlug, and pass the tool-execute domain/protocol/IP guards before any credential is injected. OAuth tools add one more trusted lookup: the web route loads app_agent_runs by workspaceId + appId + runId and resolves the triggering user from that server-created row before reading a connected account.

Request flow (agent chat)

New runs start as pending. The first chat POST that claims the run starts the worker request. If a route remount, back/forward navigation, or second tab posts the same pending run while the first stream is initializing, the duplicate POST returns an empty successful stream and does not start another worker session. Completed runs can only be claimed again when the posted message list is longer than the persisted list, so stale browser history requests cannot replace a full conversation with the original first prompt. See Agent System, Worker, and Streaming for details.

Indexes

Created automatically on startup: