Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Architecture

CyteArc uses concrete domain packages and a one-way module-load dependency structure. The public API is exposed through package facades, while reusable computation and storage mechanics live in focused implementation modules.

Dependency direction

Module-load dependencies point toward the earlier layers in this list:

  1. Foundation: storage, matrix, and utils

  2. Data model: metadata, assay, and graph

  3. Domain algorithms: neighbors, embeddings, clustering, trajectory, metrics, features, quality_control, and mapping

  4. Import and export: readers, writers, and merge

  5. Orchestration: datastore and datastore._operations

  6. Presentation: plotting

Root facade

The root cytearc package is a public import facade. It does not form another runtime layer and must not eagerly import the implementation graph. Only modules listed by that facade are available as lazy root attributes. Presentation and lower-level algorithm packages such as plotting, clustering, neighbors, and trajectory require explicit imports. This keeps the root surface small and avoids loading optional presentation code.

Plotting and local imports

plotting does not import datastore. Unified plotting consumes a narrow datastore adapter instead of resolving projection paths. Existing heatmap functions still read Zarr-backed values from their duck-typed store and assay inputs. Removing that remaining storage coupling is deferred.

Function-local imports may cross toward presentation when a public method explicitly requests a plot. Examples include RNA feature selection and datastore quality-control helpers. These calls do not create module-load cycles.

Package responsibilities

Foundation

Facade aliases do not change implementation ownership. cytearc.load_zarr is a facade alias for storage.stores.load_zarr.

Data model

Data-model modules may call domain algorithms from the method that needs them. They must not import those packages at module load time.

Domain algorithms

Domain algorithm packages must not import datastore, plotting, or general import/export packages at module load time. A domain that persists an artifact may use a narrow, named storage adapter.

Import and export

Readers parse, writers materialize, and merge combines. Format-specific code belongs in a module named for that format.

Orchestration

DataStore remains the primary workflow API. Its analysis namespaces bind the existing scoped operations in datastore._operations. datastore.namespaces owns these small accessors, and datastore.artifact_accessor owns saved result access and discovery. Public names do not replace persisted operation names, parameters, or input keys. The namespace boundary does not change computation or artifact reuse. Its public class chain is kept for compatibility:

BaseDataStore
  -> GraphDataStore
    -> MappingDatastore
      -> DataStore

Method implementations are grouped by responsibility under datastore._operations:

graph
embeddings
clustering
trajectory
mapping
mapping_reference
quality_control
features
integration_metrics
presentation

Shared helpers under the same package include enrichment_store, paris_persistence, and statistical_store, which owns the layout, reuse check, writer, and reader of stored statistical-test results. Operation mixins have no runtime inheritance from datastore facades, no __init__, and no runtime imports of sibling operation mixins. TYPE_CHECKING imports of siblings are allowed. Reusable algorithms must be placed in their domain package before being exposed through a datastore method.

datastore.pipeline_accessor orchestrates the fixed basic RNA recipe. Focused internal modules own recipe validation, run/stage ledger bookkeeping, filtering, frozen field assembly, and cluster decision persistence. The reusable bounded silhouette comparison lives in metrics.cluster_selection; its datastore adapter validates graph-coordinate lineage and persists the immutable decision. metrics.cluster_selection is not part of the public cytearc.metrics facade. datastore.pipeline_run exposes the narrow durable PipelineRun handle and its frozen cell and feature views. Pipeline execution creates immutable artifacts and a strict run/stage ledger under pipeline/runs; it does not write live metadata. The ledger runs stages one at a time in recipe order on the calling thread. DataStore-owned plotting, marker loading, and export consume narrow frozen-run views. Completed runs can be reopened by their immutable label or exact run ID.

agent/ owns the optional bounded single-RNA workflow. Its lazy facade exposes analyze_rna, resume_rna, their asynchronous counterparts, open_analysis, AnalysisRun, Study, AnalysisConfig, and RuntimeConfig. workflow.py defines fixed stages and calls existing DataStore.pipeline operations through execution.py. Agent-local evidence, diagnostics, pure choice validators, prompts, and structured provider requests remain separate from core numerical ownership. Prototype orchestrator and standalone-agent packages are retired.

records.py owns the external immutable request, events, measured evidence, visible decisions, validation outcomes, and recovery references. The default directory is Path.cwd() / "agent_runs" / <runId>; callers may choose another new external location. compact_result.py publishes one immutable agent-owned summary under the local store’s agent_results/<runId>, linking the verified final core pipeline/configuration to the external audit. It neither adds a core artifact type nor duplicates the stage history. result.py exposes the saved analysis, and rendering.py derives reports for complete and incomplete outcomes without provider calls or numerical recomputation.

Presentation

plotting/ owns the reusable plotting APIs. It has no import dependency on datastore. The removed cytearc.plots, cytearc.plotting._legacy, and DataStore.plot_* APIs must not be restored. New plots should return the established plotting result types, accept documented data contracts, and use narrow adapters instead of adding storage-path knowledge.

The single-RNA agent uses the core embedding accessor for the exact final UMAP. agent/plots.py adds a bounded marker panel from saved final marker statistics, with no count matrix reads. These views return existing public PlotResult and provenance types. They introduce no core plotting API or module-load dependency from plotting to datastore.

DataStore.plots is a thin, store-bound accessor over the canonical store-first functions in cytearc.plotting. The accessor imports concrete plot implementations only when a method is called, so this convenience namespace does not reverse the dependency from plotting to datastore.

Public facade policy

Lazy and eager facades

The lazy facades in cytearc, agent, features and its subpackages, readers, writers, merge, utils, neighbors, clustering, embeddings, trajectory, and plotting are architectural boundaries, not temporary deprecation shims. Their documented 1.x exports preserve stable import paths and defer optional or expensive implementations until an export is accessed. All of them use one private helper, cytearc._facade: an export wins over a same-named submodule, resolving an export never replaces a bound value, and only readers, writers, merge, and utils present the objects they own under the facade’s __module__.

The assay, mapping, matrix, metadata, metrics, and quality_control package initializers are eager domain facades. API reference pages and public contract tests define which of their exports carry compatibility guarantees.

Reloading a lazy facade clears cached exports before resolving them again. This keeps reload behavior deterministic for tests and interactive work.

Private facade exports used by repository tests are patch seams, not additions to the documented user API. New production code should import its canonical implementation directly unless it intentionally needs a public patch seam.

Invalidation policy

Operation revisions are the one explicit mechanism that stops CyteArc from reusing stored results. storage/operation_revisions.py gives each operation that has revisions an append-only tuple of them; an operation that is not listed is at revision 1. Planning records a revision in provenance only when it is 2 or more, so an operation without revisions keeps its identities. A stored artifact that differs from a request only in its revision is superseded: it is never reused, it stays listable, loadable, and traceable, and lineage reports mark it stale. Changing recorded parameters or inputs is not an invalidation mechanism. Operation revisions holds the decision ladder.

Stored data and API contracts

CyteArc validates persisted data and public arguments against the following contracts:

Compatibility exists only where a current public facade or an explicit file-schema test says it does. There are no silent migrations, implicit compatibility branches, or forwarding shims for retired internal modules. Incompatible stores and artifacts fail with an actionable error.

Placement rules

Use these rules when adding code:

  1. Put Zarr mechanics in storage, blockwise matrix behavior in matrix, and generic operational helpers in utils.

  2. Keep metadata and assay focused on table access, normalization, and persistence.

  3. Put reusable computation in a concrete domain package.

  4. Keep domain packages independent of datastore and plotting.

  5. Use a named storage adapter when a domain persists an artifact.

  6. Put parsing in readers, materialization in writers, and combination in merge.

  7. Keep plotting free of datastore imports and use narrow adapters for new storage-backed inputs.

  8. Add compatibility only at an existing public facade.

  9. Use a concrete biological or computational package name. Do not introduce catch-all packages such as core or analysis.

  10. Record a change to what an operation computes as an OperationRevision in storage/operation_revisions.py (Operation revisions), with a test of the revision; never change recorded parameters to invalidate results.

Architecture boundaries are enforced in tests/test_import_architecture.py. Public imports, result records, facade behavior, and wheel contents have separate contract tests.

Accepted and deferred decisions

Accepted

Deferred

Deferred to a later structural phase:

Rejected

Implementation references

Read Zarr internals for the on-disk implementation boundary and the public API reference for current contracts. Use Contributing for the test, documentation, and review workflow before making a change.