Libraries¶
Every library and pinned tool this repo uses, why, at which version, and what else was weighed.
Versions are read from build.sbt, project/, flake.nix, the Dockerfile,
docker-compose.yml and ci.yml. scala-steward opens the bump PRs for the first three tables,
dependabot for the workflows' actions; the devkit tag moves by hand
(Development). What to adopt from Scala 3 and the JDK themselves
is the Scala 3 and JDK review.
Language and build¶
| Tool | Version | Pinned in | Why |
|---|---|---|---|
| Scala | 3.9.0 | build.sbt |
The LTS line; Kyo's recommended strict flags (-Wvalue-discard, -Wnonunit-statement, -language:strictEquality) are set there |
| JDK | 25 | flake.nix (jdk25), CI's setup-java, the Dockerfile's Temurin 25 images |
Kyo 1.0.0-RC7's class files are version 69, so both sbt and the runtime need 25 or later |
| sbt | 1.10.7 | project/build.properties |
The build; flake.nix rebuilds nixpkgs' sbt on JDK 25 because its wrapper hardcodes its own JAVA_HOME |
| scalafmt | 3.8.3 | .scalafmt.conf |
Formatting, checked in CI |
Libraries¶
| Library | Version | Module | Why | Alternatives |
|---|---|---|---|---|
Kyo: kyo-core, kyo-direct, kyo-combinators |
1.0.0-RC7 | all | The effect system at the I/O boundary (Sync, Async, Abort); kyo-direct for the direct-style syntax. Pre-1.0 with no version-specific docs, so the pinned jar is the reference (.claude/agents/jar-verifier.md) |
Its own kyo-http and kyo-llm were reviewed (below) |
JDK java.net.http |
(the JDK) | core | Http, a thin client wrapper with one Transport seam that the golden spec replays fixtures through |
kyo-http (below) |
Hand-rolled JSON (marola.json) |
— | core | Every shape read or written is plain nested objects, arrays, strings and numbers, not worth a derivation-macro codec | kyo-schema (below) |
logback-classic |
1.5.13 | all | The SLF4J backend; cli's logback.xml sends every logger to stderr, because stdout is the MCP server's JSON-RPC channel. marola.log.Log wraps SLF4J directly |
scala-logging, rejected: its only Scala 3 release is 4.0.0-RC1 |
MCP Java SDK (io.modelcontextprotocol.sdk:mcp) |
2.0.0 | cli | SwimConditionsMcpServer's stdio transport and tool registry. It finds its JSON-schema validator through ServiceLoader, which is why the assembly concatenates META-INF/services |
none reviewed |
OpenTelemetry opentelemetry-sdk, opentelemetry-exporter-otlp (opentelemetry-sdk-testing in tests) |
1.65.0 | local | Traces to MLflow, which ingests them over OTLP/HTTP only (MIP-0010) | none reviewed |
| Apache PDFBox | 3.0.8 | local | The agencies publish bulletins only as PDFs; a JVM parser keeps the image free of native tools (MIP-0031 §4.3) | bundling pdftotext in the image, rejected |
| munit | 1.0.2 | all (tests) | The test framework; its tags keep E2E suites out of just test |
none reviewed |
sbt plugins¶
All in project/plugins.sbt.
| Plugin | Version | Why |
|---|---|---|
| sbt-assembly | 2.3.0 | The fat jar the image runs (cli/assembly); its merge strategy keeps META-INF/services and META-INF/native-image |
| sbt-scalafmt | 2.5.2 | just fmt, quality-scala |
| sbt-scalafix | 0.14.8 | Semantic lint (.scalafix.conf: unused code, import order, banned syntax); needs SemanticDB, enabled in build.sbt |
| sbt-native-image | 0.5.0 | sbt cli/nativeImage, run by just native-image with nixpkgs' GraalVM |
| sbt-scoverage | 2.4.4 | just coverage and the README's coverage badge |
Pinned tools and images¶
| Tool | Version | Pinned in | Why |
|---|---|---|---|
| marola-devkit | v0.4.1 | flake.nix, every devkit uses: and devkit-ref: and the docs-lint clone in .github/workflows/, the plugin marketplace ref in .claude/settings.json |
The shared recipes, git hooks, reusable workflows and lint tools, docs-lint included |
| nixpkgs | nixos-unstable, locked |
flake.lock |
What nix develop and the dev image resolve; CI's flake-lock job fails when the lock is stale |
| ruff, actionlint, hadolint | 0.16.5, 1.7.12, 2.14.0 | ci.yml inputs |
The versions CI lints with; locally they come from the devkit |
sbtscala/scala-sbt |
eclipse-temurin-25.0.4_7_1.13.0_3.8.4 |
Dockerfile (builder) |
A JDK 25 build image; sbt itself still runs at build.properties' version |
eclipse-temurin |
25-jre-alpine |
Dockerfile (jvm, corpus) |
A small JRE for the jvm image |
ghcr.io/graalvm/native-image-community |
25.0.2 | Dockerfile (native-build) |
Pinned because the floating :25 moved to a release that rejects a launcher flag in the image's Args |
gcr.io/distroless/base-debian12 |
nonroot |
Dockerfile (native) |
glibc, CA certificates and tzdata with no shell; libz comes from debian:12.12-slim |
nixos/nix |
2.35.2 | Dockerfile (dev) |
nix develop in an image |
ollama/ollama |
0.33.3 | docker-compose.yml |
The ollama profile's model server |
ghcr.io/mlflow/mlflow |
v3.16.0 | docker-compose.yml |
The mlflow profile; traces need MLflow 3.6 or later, whose OTLP endpoint takes an experiment id |
Reviewed, not adopted¶
kyo-http and kyo-schema¶
kyo-http's client (getJson, postJson, getText, postBinary, …) was confirmed by decompiling
the 1.0.0-RC5 jar, the version pinned at the time, because getkyo.io documents only the latest
release. Its codec typeclass, kyo.Schema[A], lives in kyo-schema, which kyo-http pulls in. Both
are on Maven Central at 1.0.0-RC7, the version pinned now (checked 2026-10-04); RC7's API has not
been re-read.
Migrating would replace Http and marola.json with HttpClient.getJson[A]/postJson[A, B] and
derives Schema case classes for every shape parsed by hand today (Overpass, Open-Meteo, Ollama,
the agencies' JSON). That means typed models instead of JsonValue navigation, and less code. It
was not done because every call site was verified live against the real APIs, and a library with
no version-specific docs risks a quiet difference in timeouts, redirects or pooling.
Recommendation: migrate as a dedicated task, one integration at a time (OpenMeteoClient
first, the simplest and most called), re-verifying each against live data, and remove Http and
marola.json only when no call site uses them. Http.withTransport, which the golden spec
replays through, needs an equivalent seam first.
kyo-llm¶
There is no kyo-ai on Maven Central; the nearest is kyo-llm, whose last release is 0.9.0 from
March 2024 (checked again 2026-10-04), built against Kyo before its 0.x to 1.0 redesign. It is
unlikely to resolve beside kyo-core 1.0.0-RC7, and its idioms predate the rest of this code.
Not adopted: LlmClient over plain HTTP to Ollama is small, verified live and has no
compatibility risk. Revisit if kyo-llm ships against Kyo 1.x.
workflows4s¶
workflows4s (0.6.2, checked 2026-10-04) composes
long-running, stateful processes (approval chains, sagas) with event-sourcing semantics and BPMN
rendering, and is not tied to one effect system. The pipeline here
(Recommender.bestPerBeachTomorrow, then the draft, then Reviewer.review) is a single-shot call
chain with no state to persist and no human step, so it has nothing to orchestrate. Revisit
if a stateful flow appears: sightings moderated before they are trusted, a daily digest that must
remember what it sent, or a bounded draft, review, revise loop. Its sibling
decisions4s, a decision-table engine, is worth a look
if the jellyfish and whale heuristics outgrow a handful of thresholds; it was not evaluated in
depth.
neotypes and Iron¶
neotypes is a type-safe, effect-agnostic Scala driver for Neo4j. Not a fit: marola has no graph data model.
Iron (3.x) adds refined types, constraints checked at compile
time or at construction (Double :| Interval.Closed[0, 100]). A real fit, not adopted yet:
Swimability.score's 0–100 range is a runtime clamp that nothing else enforces, and
Coordinates(lat, lon) accepts any Double. A handful of type aliases in model/Models.scala
would make both illegal states unrepresentable, with no architecture change. It pairs with the
opaque unit types in the Scala 3 and JDK review,
and is a smaller first step than the kyo-http migration.