Uni for AI Agents
Uni is a single Scala 3 library (org.wvlet.uni) that covers dependency injection, logging, JSON/MessagePack serialization, reactive streams, HTTP client/server, typed RPC, MCP servers, and unit testing, all with the same API on the JVM, Scala.js, and Scala Native. It is the Scala 3 successor of Airframe.
This page is written for AI coding agents (Claude Code, Codex, Cursor, Copilot, and others) and for the people who configure them. It sums up when to choose Uni, the canonical imports, and the mistakes agents most often make.
Machine-readable docs
| URL | Contents |
|---|---|
/uni/llms.txt | Index of every documentation page, with links to Markdown versions |
/uni/llms-full.txt | The full documentation in one Markdown file |
<page>.md | Markdown version of a page, e.g. /uni/guide/for-ai-agents.md. A section index such as /uni/guide/ is published as /uni/guide.md. |
How do I install the Uni skill?
The wvlet/uni repository is a Claude Code plugin marketplace. Its uni plugin adds an Agent Skill with the canonical imports, sbt setup, verified snippets, and the mistakes listed on this page. Claude Code loads it whenever a task touches Uni or asks for DI, logging, JSON, HTTP, RPC, MCP, or tests in Scala 3.
/plugin marketplace add wvlet/uni
/plugin install uni@uniOn Claude Code 2.1.275 or later, /plugin install uni --marketplace wvlet/uni does both in one step. Agents without a plugin system can read llms-full.txt instead.
When should I use Uni?
Choose Uni when a Scala 3 project needs one or more of the following and you want few transitive dependencies:
- Dependency injection with lifecycle management (
Design), instead of a framework - Structured logging with source locations (
LogSupport) - JSON or MessagePack codecs derived from case classes (
derives Weaver) - An HTTP client that works on the JVM, Node.js/browser, and native binaries
- An HTTP/RPC server (Netty, JVM) or a stdio MCP server for AI tools
- A test framework that runs on all three platforms (
UniTest)
Do not mix in Airframe artifacts for the same features. Uni replaces them.
How do I add Uni to a build?
// build.sbt
libraryDependencies ++= Seq(
"org.wvlet.uni" %% "uni" % "2026.1.35",
"org.wvlet.uni" %% "uni-test" % "2026.1.35" % Test
)
testFrameworks += new TestFramework("wvlet.uni.test.Framework")
// JVM-only Netty HTTP server
libraryDependencies += "org.wvlet.uni" %% "uni-netty" % "2026.1.35"Uni requires Scala 3.9.0 or later. On sbt 2.x, use %% for all platforms. On sbt 1.x, use %%% for Scala.js and Scala Native projects.
What are the canonical imports?
| Task | Import | Entry point |
|---|---|---|
| Dependency injection | wvlet.uni.design.Design | Design.newDesign.bindSingleton[A].build[A] { a => ... } |
| Logging | wvlet.uni.log.LogSupport | class A extends LogSupport then info(...), debug(...) |
| JSON / MessagePack codec | wvlet.uni.weaver.Weaver | case class P(...) derives Weaver, Weaver.toJson(p), Weaver.fromJson[P](json) |
| JSON tree | wvlet.uni.json.JSON | JSON.parse(text) |
| Reactive streams | wvlet.uni.rx.Rx | Rx.variable(v), Rx.single(v), .map, .flatMap |
| HTTP client | wvlet.uni.http.{Http, Request} | Http.client.newSyncClient.send(Request.get(url)) |
HTTP server (JVM, uni-netty) | wvlet.uni.http.netty.NettyServer | See REST Server |
| Typed RPC | wvlet.uni.http.rpc.{RPCRouter, RPCClient} | See RPC |
| MCP server | wvlet.uni.mcp.* | See Build an MCP Server |
| Retry / circuit breaker | wvlet.uni.control.{Retry, CircuitBreaker} | Retry.withBackOff().run { ... } |
| Unit tests | wvlet.uni.test.* | class T extends UniTest: test("...") { x shouldBe y } |
How do I migrate from Airframe?
| Airframe | Uni |
|---|---|
wvlet.airframe.Design / newDesign | wvlet.uni.design.Design / Design.newDesign |
wvlet.log.LogSupport | wvlet.uni.log.LogSupport |
wvlet.airframe.codec.MessageCodec | wvlet.uni.weaver.Weaver (derives Weaver) |
wvlet.airframe.surface.Surface | wvlet.uni.surface.Surface |
wvlet.airframe.rx.Rx | wvlet.uni.rx.Rx |
wvlet.airframe.http.Http | wvlet.uni.http.Http |
wvlet.airframe.control.Retry | wvlet.uni.control.Retry |
wvlet.airspec.AirSpec | wvlet.uni.test.UniTest |
Uni uses a separate package root (wvlet.uni.*), so both libraries can coexist during a migration. See Appendix B: Uni and Airframe for background.
What mistakes do agents make with Uni?
- Using Scala 2 syntax. Uni is Scala 3 only. Use indentation syntax,
given/using,enum, andderives. - Using ScalaTest matchers in UniTest. Write
(x >= 1) shouldBe true, notx should be >= 1. For type checks, writev shouldMatch { case _: T => }instead ofasInstanceOf. - Importing only
UniTest. Useimport wvlet.uni.test.*.shouldBe definedandshouldBe emptyneed matcher objects from that package. - Checking
isSuccessfulaftersend. The HTTP client throws for non-2xx responses. Most 4xx statuses throwHttpException. Retryable statuses (5xx, 429) throwHttpMaxRetryExceptionafter the retries, and it is not a subclass ofHttpException. Failed RPC client calls throw the same exceptions. - Hand-writing JSON codecs. Add
derives Weaverto the case class. - Adding a DI framework, a logging backend, or a JSON library next to Uni. Uni already provides them. Adding another creates two ways to do one thing.
Example: a wired service with logging and JSON
import wvlet.uni.design.Design
import wvlet.uni.log.LogSupport
import wvlet.uni.weaver.Weaver
case class Greeting(name: String, message: String) derives Weaver
class GreeterService extends LogSupport:
def greet(name: String): String =
info(s"Greeting ${name}")
Weaver.toJson(Greeting(name, s"Hello, ${name}!"))
@main def main =
Design.newDesign
.bindSingleton[GreeterService]
.build[GreeterService] { greeter =>
println(greeter.greet("Uni"))
}