AoAH Day 5: Bytesrw Eio adapters and automating opam metadata / Dec 2025
After the
Approach
The Bytesrw-eio library is exceedingly simple and exposes just two functions, so it was pretty easy for the agent to code up given the source repositories already had Unix equivalents.
val bytes_reader_of_flow :
?slice_length:int -> _ Eio.Flow.source->
Bytesrw.Bytes.Reader.t
val bytes_writer_of_flow :
?slice_length:int -> _ Eio.Flow.sink ->
Bytesrw.Bytes.Writer.t
Results
While coding this simple library, I realised my bottleneck lay in managing the growing number of opam packages that I have lying around! Can I also use agents to manage these libraries?

My opam structure
I'm maintaining all the libraries at a custom aoah-opam-repository overlay on Tangled. Each individual repository I publish also uses a Spindle CI action to build that library on every push.
For every package I publish, I need to:
- get the metadata right in the dune-project file so that the opam files are generated right
- add the right .ocamlformat version to the repo
- then translate the per-repo opam metadata into an entry that lives in the aoah-opam-repo so packages can depend on each other even though they're unreleased.
- do various other health checks like copyright headers and opam-dune-lint.
The problem with scripting these up is that there's subtle parameterisation which changes slightly across each library. Some might have an extra external dependency, another might have a custom build step, and others might just have tests that require specific actions. This seems ideal for a coding agent that can take a template of actions and then propose metadata changes automatically.
Using Claude skills to manage opam metadata
The idea behind Claude skills is incredibly simple: you just create a ~/.claude/skills/my-skill that contains a SKILL.md and the associated scripts that need to run. Nothing else; no state, no MCP server, just a simple file! The header looks as follows:
—
name: ocaml-metadata
description: Standards for OCaml project metadata files. Use when initialising a new OCaml library/module, preparing for opam release, setting up testing infrastructure, or searching the OCaml ecosystem for dependencies. Not for normal code edits.
license: ISC
—
# OCaml Project Metadata Standards
## When to Use This Skill
Invoke this skill when:
1. **Initializing a new OCaml project** - Setting up dune-project, LICENSE, README, CI, etc.
2. **Preparing for opam release** - Ensuring all metadata is correct for publication
3. **Setting up testing infrastructure** - Especially for Eio-based libraries that need mock testing
4. **Searching the OCaml ecosystem** - Finding and fetching dependency sources for reference
5. **Adding third-party source references** - Using `opam source` to study library implementations
**Do not use for:**
- Regular code edits or bug fixes
- Simple function additions
- Refactoring existing code
The Yaml frontmatter is the only thing loaded by Claude at startup (thus saving context space). Then it uses that information to load the rest when needed on demand. For example, for the

If you browse the full skill repository you will also find even more detailed (and personalised) instructions about how to structure tests and retrieve sources to my laptop. My workflow is that if Claude gets something wrong workflow-wise, I now prompt it to also fix the skill after its done. The agent then revises the workflow information in this repository and (hopefully) doesn't make the same mistake twice.

Tests
Getting back to the problem at hand, testing the bytesrw-eio adapter was a simple matter of prompting the agent to come up with some read and write tests, and then I deliberately introduced an error to sanity check they were failing when expected and then refined the tests to include those failures as well.
One frustration here I had is a long-standing problem in OCaml where we don't
have a single agreed IO representation. Eio uses
Cstruct which is backed by Bigarray
(which lives out-of-heap), and then others use bytes/string (which live
in-heap). The reason is usually because out-of-heap values can be non-moving
(allowing for zero-copy IO), but in return it results in bytes). At some point in the future, I
reckon we need to get a non-moving bytes mechanism into OCaml 5.x so that we
can all just use one IO type and avoid these dratted copies.
Reflections
The bytesrw-eio package itself was simple enough, and it added to my long-term
todo list to
Next, onto