Arkitektur
Oversikt
Seksjon med tittelen «Oversikt»Control Center følger en funksjonsorientert Clean Architecture med et port- og adaptermønster. Kodebasen er organisert rundt forretningsdomener (funksjoner), med strenge avhengighetsregler håndhevet av en automatisert arkitekturtest.
En tynnklient over en headless server
Seksjon med tittelen «En tynnklient over en headless server»Control Center er en tynnklientarkitektur. Ingen klient åpner databasen — en headless cc_server-prosess eier dataene (en liten global.db pluss én SQLite-fil per arbeidsområde, åpnet ved behov) og betjener dem over WebSocket-RPC. Skrivebordsappen, nettbyggingen og telefonappen er alle renderere over den samme RPC-tilkoblingen.
Skrivebordsappen velger hvordan den når serveren sin ved første oppstart: lokal (den starter en cc_server på denne maskinen og kommuniserer over loopback — standarden) eller ekstern (den ringer opp en cc_server et annet sted over en sikker WebSocket). Nettklienten er alltid ekstern. Oppstartsresolveren (bootstrap/server_backend.dart) velger banen før Riverpod eksisterer, og overstyrer deretter rpcClientProvider med den tilkoblede klienten — slik at hver funksjon leser og skriver gjennom serveren i stedet for en lokal database.
Selve RPC-laget er tilstandsløst: en økt autentiseres, men den er ikke bundet til et arbeidsområde. Hvert arbeidsområdespesifikke kall bærer sitt eget workspace_id i argumentene, og dispatcheren avviser et slikt kall uten det. Det er dette som lar to klienter på én server ha ulike aktive arbeidsområder uten et serverholdt «gjeldende arbeidsområde» å være uenige om — og det betyr at isolasjon håndheves per kall, ikke per tilkobling.
Dette er et Dart-arbeidsområde med én oppløst lockfil og tjueen medlemmer — femten pakker pluss seks apper — med skrivebords- og nettappen (control_center) som arbeidsområderot. De seks appene er den headless serverbinæren (cc_server), den headless flåteutføreren (cc_worker), telefon-PWA-en (cc_remote), WebSocket-relémegleren (cc_signaling_server), Widgetbook-galleriet (cc_gallery) og den offentlige demoverten (cc_demo_server). De femten pakkene er cc_ui (designsystem), cc_domain (ren-Dart delt kjerne: entiteter, verdiobjekter, porter, hendelser og hver funksjons domenelag), cc_harness (den innebygde agentkjernen: en ren-Dart agentløkke — meldinger, leverandørport, verktøy, komprimering, styring, hooks, underagenter — uten dart:io og uten andre cc_*-avhengigheter), cc_harness_runtime (den innebygde kjøretidsadapteren for harness: kun-VM-strømmende leverandører, OAuth-/PKCE-legitimasjonsmegling, legitimasjonslagre og det generelle verktøysettet — kjører agenter uten noen ekstern CLI installert), cc_rpc (klienttransport), cc_host (serverkjerne), cc_data (fjernrepositoryer), cc_persistence (serverbasert database), cc_server_core (app-server-komposisjon + kjøretid for identitet/tilstedeværelse/flåte/evals), cc_infra (kun-VM-adaptere), cc_mcp (MCP-verktøy), cc_mcp_client (klient for eksterne MCP-servere, som broer verktøyene deres inn i det lokale registeret), cc_markdown (markdown-motor i repoet), cc_natives (FFI-løvnode) og system_audio_capture. Se Drift og klienter for hele klientmatrisen og hva som kjører hvor.
Avhengighetsregel
Seksjon med tittelen «Avhengighetsregel»Presentation → Application/Providers → Domain ← Infrastructure- Domenelaget: rene Dart-entiteter, verdiobjekter, repositorygrensesnitt, porter og domenetjenester. Null infrastrukturimporter (ingen dio, drift eller nettverksmodeller).
- Presentasjonslaget: skjermer, widgeter, notifikatorer. Ingen direkte tilgang til drift/DAO/datalaget; alt går gjennom Riverpod-providere → repositoryer.
- Infrastrukturlaget: konkrete implementasjoner av domeneportene og repositorygrensesnittene. Avhenger av domenet, aldri omvendt.
Funksjonsstruktur
Seksjon med tittelen «Funksjonsstruktur»Den fulle formen på en funksjon er:
feature_name/├── data/ # Repository implementations, data sources, services, DTOs, mappers├── domain/ # Entities, repository interfaces, ports, use cases├── presentation/ # Screens, widgets, notifiers└── providers/ # Riverpod providersSvært få funksjoner under lib/features/ har faktisk alle fire, og hullene er informative snarere enn tilfeldige. Den delte kjernen absorberte hver funksjons domain/ inn i cc_domain, og tynnklienten inneholder ingen repositoryimplementasjoner, så de fleste funksjoner under lib/ er presentation/ pluss providers/ og ingenting annet. En data/- eller domain/-katalog som overlever i klienten, markerer noe som virkelig er klientside.
To navngitte unntak er verdt å kjenne:
mcpbestår kun av providere. Innstillings- og statusgrensesnittet ligger undersettings/, og selve verktøyflaten er en pakke (cc_mcp), ikke en funksjonsmappe.orchestrationogplan_studiohar barepresentation/ogproviders/.
guardrails-funksjonen legger domenelaget sitt i cc_domain/features/guardrails/ i stedet for i rotappen, fordi serveren håndhever det: en regel kobler en ActionClass (eller et shell-commandPrefix) til en beslutning — allow, prompt eller deny — på et omfang, regler lagres per arbeidsområde, og enhver styrt agenthandling løses gjennom regelkjeden før den kjører. Oppslag går gjennom space > agent > workspace > mode preset > built-in default, og det første omfanget med en samsvarende regel bestemmer; mest restriktiv er bare et skillekriterium innenfor ett omfang og for å kombinere de ulike klassene én handling oppgir. Operatørflaten er Innstillinger → Arbeidsområde → Agenttillatelser, på /workspaces/<id>/settings/workspace/permissions. Se Tillatelsesregler for modellen.
Delt kjerne
Seksjon med tittelen «Delt kjerne»packages/cc_domain/lib/core/domain/ inneholder entiteter og repositoryer som deles på tvers av 3+ funksjoner. Det er ren Dart uten infrastrukturavhengigheter, og det er dette som gjør at både Flutter-klienten og den Flutter-frie serverbinæren kan importere de samme definisjonene:
- Kjerneentiteter:
Agent,AgentRunLog,Workspace,Repo,ReviewSpaceAssociation - Identitet og flerbruker:
User,Principal(sealedUserPrincipal|AgentPrincipal),WorkspaceMember,WorkspaceRole - Minneentiteter:
MemoryFact,MemoryPolicy,AgentWorkingMemory,MemoryAccessGrant - Delte verdiobjekter:
AgentSkills,AgentRole,Mode,SandboxBackend,SandboxSpec,RunCost - Delte porter:
SandboxPort,CredentialBrokerPort,WorkspaceFilesystemPort,GitRepoInspectorPort,EmbeddingPort - Domenetjenester:
MemoryAccessPolicy,ActivityLogger,MentionResolver,RunLivenessClassifier DomainEventBus+ hendelsestyper (arbeidsområde/agent, PR/gjennomgang, meldinger, saker, pipeline, orkestrering, minne, kalender/møter, identitet og medlemskap, observabilitet)
Identitet og flerbruker
Seksjon med tittelen «Identitet og flerbruker»Control Center er flerbruker: mennesker og agenter er likeverdige aktører forent av en Principal. Et arbeidsområdemedlem er en User bundet til et Workspace med en WorkspaceRole (owner/admin/member/viewer/guest). Medlemskap er tilgangsgrensen — ikke en parkoblingsnøkkel — og per-repo-tildelinger hindrer at arbeidsområdemedlemskap gir flere rettigheter enn kodeverten. Den første brukeren som opprettes på en fersk server, eier installasjonen; andre blir med via invitasjon eller OIDC. Når et arbeidsområde opprettes, stemples den brukeren som owner.
Samtidig samarbeid i sanntid er autoritativ server + feltvis last-writer-wins (LWW), ikke en CRDT. Tilstedeværelse (hvem som er her, hvor de er, markører, skriveindikatorer, en agents status i sanntid og løpende kostnad) er en egen kortlivet kanal som aldri lagres; varig tilstand går på en ryggrad av optimistisk mutasjon + server-rebase + LWW. Mennesker og agenter deler én deltakerliste. Med én enkelt menneskelig operatør står tilstedeværelseskanalen i ro, og ingen deltakerlistegrafikk vises — flerbruker koster ikke enkeltbrukeren noe.
Porter og adaptere
Seksjon med tittelen «Porter og adaptere»Porter er abstrakte grensesnitt i domenelaget. Adaptere er konkrete implementasjoner i infrastrukturen. Fordi den headless serveren eier dataene og må bygges uten Flutter, er nesten alle adaptere kun-VM og ligger i packages/cc_infra/lib/src/, organisert etter ansvarsområde:
| Port | Plassering av adapter |
|---|---|
SandboxPort |
cc_infra/lib/src/sandboxing/ (native_sandbox_adapter.dart) |
CredentialBrokerPort |
cc_infra/lib/src/sandboxing/ (env_credential_broker.dart, github_fine_grained_broker.dart) |
GitRepoInspectorPort |
cc_infra/lib/src/git/ (git_repo_inspector.dart) |
EmbeddingPort |
cc_infra/lib/src/embedding/ (embedding_service.dart) |
NotificationPort |
lib/core/notifications/ (klientside: gjengir serverhendelser lokalt) |
AgentBackend |
cc_infra/lib/src/dispatch/backends/ (acp_backend.dart, harness_backend.dart, cli_backends.dart) |
TicketProviderPort |
cc_infra/lib/src/tickets/linear/ (LinearTicketAdapter). Jira, ClickUp og GitHub Issues er sakssynkroniseringsadaptere i cc_infra/lib/src/tickets/sync/; lokale saker ligger i arbeidsområdedatabasen uten noen TicketProviderPort. |
PipelineEnginePort |
cc_domain features/pipelines/domain/services/ (en ren-Dart-domenetjeneste — den trenger ingen infrastruktur) |
Appens komposisjonsrot (di/providers.dart) binder klientsideporter til implementasjoner via Riverpod-providere; serveren komponerer adapterne sine i cc_server_core.
Tilstandshåndtering
Seksjon med tittelen «Tilstandshåndtering»Riverpod for all klienttilstand:
Notifier<T>ogAsyncNotifier<T>for mutbar tilstandFutureProvider<T>for asynkrone data- Lister og levende rader kommer inn over RPC-abonnementer som
AsyncValue<List<T>>— serveren bruker Drift.watch()internt; ingen klient åpner Drift - MCP-verktøy mottar avhengigheter som typede konstruktørparametere, aldri
Ref
Database
Seksjon med tittelen «Database»Drift (SQLite), delt i to databaser, begge i cc_persistence:
GlobalDatabase(<dataDir>/global.db) — en kort liste over virkelig serveromfattende tabeller: arbeidsområderegisteret, brukere/innstillinger/sammenkoplede enheter, SSO-tilkoblinger, installasjonsomfattende serverinnstillinger, administrerte handlingspolicyer (en installasjonsomfattende innstramming som bare kan stramme inn arbeidsområdenes tillatelsesregler), nyhetsstrømmen per bruker, flåkøen (workere/jobber/plasseringslogg), indeksenworkspace_routesfør autentisering og installasjonsmetadata. Oppstart åpner bare denne filen, så oppstartskostnaden holder seg flat uansett hvor mye historikk arbeidsområdene samler opp. Å legge til en tabell her er en isolasjonsbeslutning som en ratchet-test tvinger deg til å begrunne.WorkspaceDatabase(<dataDir>/<workspaceId>/workspace.db) — én katalog per arbeidsområde, som inneholder én SQLite-fil pluss alt annet som bare hører til det arbeidsområdet, slik at arbeidstrærne og agentfilene til en samtale ligger ved siden av databasen og slettes sammen med den. Nesten alt ligger her: agenter, områder, saker, minne, pipelines, møter, kodegrafen, gjennomganger, repoer. Filer åpnes ved behov ved første berøring gjennomWorkspaceDatabaseManager.of(workspaceId)og betaler sin egenquick_check, FTS-/trigger-installasjon ogvector_initda.
Begge halvdelene startet fra en sammenslått v1-baseline — onCreate bygger gjeldende skjema — med migrasjonstrinn lagt til bare når en utplassert database må føres videre.
Fordi ett arbeidsområde er én fil, er eksport av et arbeidsområde én enkelt VACUUM INTO-setning (workspace.export), ikke en tabell-for-tabell-dump; workspace.import tar i bruk en slik fil og bruker den innebygde workspace_meta til å skille en gjenopptatt egen fil fra en som kom fra en annen installasjon.
Begge databasene eies av cc_persistence og åpnes bare av cc_server; ingen klient åpner en database — hver klient leser/skriver over RPC. Tabeller defineres i packages/cc_persistence/lib/database/tables/, DAO-er i .../daos/; domeneentiteter er ren Dart, atskilt fra Drift-tabellklassene, med mapping i datalagene. FTS5 håndterer fulltekstsøk og sqlite_vector-innbygging, begge kun på serveren; begge databasene kjører PRAGMA foreign_keys=ON i WAL-journalmodus.
go_router, med ett faktum som styrer resten: enhver destinasjon i appen har arbeidsområdeprefiks i form av /workspaces/:workspaceId/…. Arbeidsområde-ID-en i URL-en er den eneste kilden til sannhet for det aktive arbeidsområdet — activeWorkspaceIdProvider drives fra ruten, ikke omvendt — og derfor har lesing av arbeidsområdespesifikke data utenfor en rute med prefiks ingen steder å hente ID-en fra.
- Rute-bygger-funksjoner i
router/routes.dart, som hver tar arbeidsområde-ID-en som første argument, ikke strengkonstanter - Fire ruter utenfor kontekst har ikke prefiks:
/splash,/onboarding,/signed-outog/workspaces(velgeren) ShellRouteomslutter appskallet; de fire flatene gjengis i fullskjerm utenfor det- Autentiseringsvakt: ufullstendig oppsett går til
/onboarding; et fullført oppsett der kodevertlegitimasjonen er borte, går til/signed-out. Fullført betyr at minst én kodevert er tilkoblet for den innloggede brukeren og at minst ett arbeidsområde finnes — ikke GitHub spesifikt, oggh-CLI-en er ikke en autentiseringsmetode - Onboarding-port i
features/auth/providers/
Nettverk (serverside)
Seksjon med tittelen «Nettverk (serverside)»All ekstern HTTP ligger i serveren. Klienter ringer aldri GitHub, Linear eller Google direkte — de kaller serverens RPC-operasjoner, og til og med eksterne medier hentes gjennom serverens /proxy/media-endepunkt i stedet for fra kildeopphavet.
- dio-klienter i
packages/cc_infra/lib/src/:GitHubApiClient,GitHubPrClient,GitHubContentClient,GitHubGraphQLClient,GitLabApiClient,BitbucketApiClient,LinearGraphQlClientogGoogleCalendarApiClient - Injeksjon av autentiseringstokener via interceptorer, inkludert en konto-spesifikk Google OAuth-interceptor i
cc_server_coresom fornyer tokener ved 401 - Alle feil mappes gjennom
packages/cc_infra/lib/src/network/error_mapper.dart→ typedeAppException-underklasser definert icc_domain
Sikkerhet
Seksjon med tittelen «Sikkerhet»Serveren holder legitimasjonen som betyr noe: GitHub- og sakstokener for agentutsendinger, nøkler til LLM-leverandører og OAuth-tokener ligger alle under serverens datakatalog eller i miljøet dens. Det klientens nøkkelring fortsatt holder, er dens egen enhetslegitimasjon og parkoblingsnøkkelen for en ekstern server.
- Hemmeligheter i
flutter_secure_storage(macOS-nøkkelring, Windows-legitimasjonsbehandler, Linux libsecret) — klientens enhetslegitimasjon og eksterne parkoblingsnøkler, ikke leverandørtokener shared_preferenceskun for ikke-sensitive innstillinger (tema, skrift, oppsett)- Gjenværende leverandørhemmeligheter fra eldre installasjoner overleveres serveren én gang av
features/auth/providers/credential_migration.dartog slettes deretter fra klientens nøkkelring
Håndheving av arkitekturen
Seksjon med tittelen «Håndheving av arkitekturen»Arkitekturrestriksjoner valideres av test/core/architecture_constraints_test.dart, som feiler hvis avhengighetsreglene brytes.
Beslektede konsepter
Seksjon med tittelen «Beslektede konsepter»- Arbeidsområder og isolasjon: hvordan isolasjon håndheves
- Livssyklus for agentutsending: utsendingsflyten
- Domenehendelser: kommunikasjon på tvers av funksjoner
- Drift og klienter: tynnklient-/servermodellen og klientmatrisen