Files
sanderling/docs/development/architecture.md
T
pj 8ccf95c1cf refactor: rename project uatu -> sanderling (#24)
* refactor: rename Go module path uatu -> sanderling

Module path github.com/priyanshujain/uatu -> github.com/priyanshujain/sanderling,
including all imports and the proto go_package option. Generated .pb.go files
rewritten in-place; safe to regenerate with protoc later.

* chore(proto): regenerate driverpb after module path rename

The previous sed-based module rename corrupted the embedded descriptor
byte lengths. buf generate rewrites them cleanly.

* refactor: rename CLI binary uatu -> sanderling

Updates Makefile target + UATU_BIN var, .goreleaser project/build IDs,
.gitignore comment, and all user-facing strings in the CLI help text,
error messages, and tests. Binary is now bin/sanderling.

* refactor(sdk): rename Kotlin package dev.uatu.sdk -> dev.sanderling.sdk

Moves sdk/android/src/{main,test}/kotlin/dev/uatu -> dev/sanderling and
rewrites package declarations, imports, and the Gradle namespace. Class
names (Uatu, UatuRuntime) are renamed in a follow-up commit.

* refactor(sidecar): rename Kotlin package dev.uatu.sidecar -> dev.sanderling.sidecar

Moves sidecar/src/{main,test}/kotlin/dev/uatu -> dev/sanderling and
rewrites package declarations, imports, and the application mainClass.

* refactor: rename Uatu API surface -> Sanderling

- Kotlin: Uatu -> Sanderling, UatuRuntime -> SanderlingRuntime (+ files).
- JS host binding: globalThis.__uatu__ -> __sanderling__ (Go verifier,
  spec-api, tests).
- TS interface: UatuRuntime -> SanderlingRuntime; internal tags
  __uatuFormula / __uatuActionGenerator -> __sanderling* variants.
- Go trace: UatuVersion field + uatu_version JSON tag renamed.
- Socket naming: uatu-agent / uatu-agent-reader -> sanderling-agent*.
- Sample app, docs, inline-JS test strings updated to match.

* refactor(examples): rename examples/folio/uatu -> examples/folio/sanderling

Renames the example spec directory; updates justfile paths + gitignore
entries accordingly. Package.json name/description and @uatu/spec
dependency are renamed in the npm + docs commits.

* chore(build): rename gradle property + rootProject.name uatu -> sanderling

- Renames the uatu.version gradle property and all its -P references in
  Makefile, build.gradle.kts files, and .github/workflows/release.yml.
- settings.gradle.kts rootProject.name = "sanderling".
- Renames .env.local.example header + release-cli workflow job name.

* refactor(proto): rename proto package uatu.driver.v1 -> sanderling.driver.v1

Updates the proto package and java_package, regenerates driver.pb.go +
driver_grpc.pb.go, rewrites Kotlin imports and the gRPC ServiceName
assertion in driver_test.go.

* refactor: rename npm package @uatu/spec -> @sanderling/spec

Renames package name in pkg/spec-api/package.json + lockfile, all
consumer imports (examples/folio spec, testdata, verifier tests), the
esbuild alias in cmd/sanderling/test_run.go, and related doc references.

* docs: rename uatu -> sanderling in README, docs, and URLs

- README + docs/{manual,development}/*: narrative + GitHub + Pages URLs.
- POM + npm package.json repo/homepage/bugs URLs.
- .gitignore + embed_stub + Makefile-comment references updated to
  'make sanderling'.
- Minor narrative comments in cmd/sanderling/test_run.go and
  internal/inspect/server.go.

* refactor: rename remaining internal uatu strings -> sanderling

- SANDERLING_TEST_PHONE/OTP env vars (cmd + bundler tests).
- sanderling-sidecar runtime tmp dir + extracted JAR filename.
- Inspect web UI: @sanderling/inspect-web package, title, theme
  localStorage key, RunList empty-state copy, uatu_version TS field.
- Sample app storage key sanderling.ledger.v1.
- Test data: sanderling_test AVD name + com.example.sanderling_test.
- Release docs tarball name template.
2026-04-21 11:57:49 +07:00

2.8 KiB

title
title
Architecture

Architecture

Three processes, two transports.

flowchart TB
    subgraph Go["sanderling (Go)"]
        Bundler[Bundler<br/>esbuild] --> Verifier[Verifier<br/>goja + LTL]
        Verifier <--> Runner[Runner]
        Runner --> Trace[Trace writer<br/>JSONL + PNG]
        Runner <--> Driver[Driver iface]
    end

    subgraph Sidecar["Maestro Sidecar (JVM)"]
        Maestro[maestro-client]
    end

    subgraph Device["Emulator"]
        subgraph App["Android app (debug)"]
            SDK[sanderling-sdk<br/>pause / hierarchy<br/>logs / coverage]
        end
    end

    Driver -- gRPC --> Maestro
    Maestro -- UIAutomator --> App
    Runner -- Unix socket --> SDK
    Trace --> Runs[(runs/)]

Processes

sanderling (Go). The top-level binary. Bundles the spec with esbuild, evaluates it in goja, runs the main loop, dispatches actions through the driver, writes the trace.

Maestro sidecar (JVM). A Kotlin process that wraps maestro-client and exposes a gRPC surface matching the driver.Driver interface. Handles UI input, screenshots, the system accessibility tree, and OS-level alerts.

In-app SDK. A Kotlin (or Swift for iOS) library linked into the app under test. Exposes a Unix socket to the runner. Provides pause and resume, view-hierarchy dumps, coverage reads, log capture, and user-registered state extractors.

Transports

Channel Transport Purpose
Go to Maestro sidecar gRPC (localhost TCP) UI input, screenshots, system alerts
Go to in-app SDK Unix domain socket Pause / resume, hierarchy, coverage, logs, extractors

The split exists for one reason: only real UI events need the cost of crossing process and OS-API boundaries. Introspection is cheap, frequent, and lives on a fast local socket directly to the app.

Per-step cycle

The heart of the system is:

pause  ─►  capture state  ─►  evaluate properties  ─►  pick action  ─►  resume  ─►  dispatch
  1. The runner asks the driver to wait until the UI is idle.
  2. The runner sends PAUSE to the SDK over the agent socket. The SDK freezes the main runloop at a safe point.
  3. The SDK sends back a STATE message: view hierarchy, coverage delta, logs since last step, exception list, snapshot values.
  4. The runner feeds state into goja. Extractors re-read; properties re-evaluate; the action generator returns a weighted tree.
  5. The runner writes the trace entry for this step.
  6. The runner picks an action by weight.
  7. The runner sends RESUME to the SDK, then dispatches the action through the driver (gRPC to sidecar, which talks to Maestro, which talks to UIAutomator or XCTest).
  8. Loop.

The cycle runs hundreds of times per minute. Every step produces one row in trace.jsonl and one screenshot.