cd /news/ai-agents/module-of-the-week-rcmap · home › topics › ai-agents › article
[ARTICLE · art-137553] src=effect.website ↗ pub= topic=ai-agents verified=true sentiment=· neutral

Module of the Week - RcMap

Effect's Module of the Week post details RcMap, a data structure in the Effect TypeScript library that combines a keyed map with reference counting to share and automatically clean up resources such as Git repository checkouts. The post demonstrates RcMap through a hypothetical "Roast My Repo" app in which two AI reviewers share a single checkout, with each reviewer incrementing a reference count on connect and decrementing it on release so the directory is deleted when the count reaches zero. RcMap.make requires a lookup function that receives a Repository key and returns an Effect that acquires the checkout, and the Git service's checkout method requires a Scope.

by read8 min views8 publishedSep 21, 2026
Module of the Week - RcMap
Image: Effect (auto-discovered)

On Effect Office Hours, people often ask me to “roast” their Effect codebases. This takes some nerve on my part, given that my own GitHub profile is public.

Rather than give anyone a reason to look at my code, let’s outsource the roasting. We’ll build “Roast My Repo”, a hypothetical app with AI reviewers trained to bring out your inner imposter syndrome.

#

To gain access to your code, the app will clone your repository into a temporary folder. We will start with two AI reviewers: one examines your dependency graph and the other reviews your code. Both will need to access the same repository, so they will share a checkout instead of cloning the repository twice.

But if both review agents are sharing one checkout, how do we know when we can delete it?

We can track this with a count. Each reviewer adds one when it starts using the checkout and subtracts one when it’s done. When the count reaches zero, nobody needs the files anymore, so we can safely remove the directory.

Try connecting and disconnecting the reviewers below to see this in action. Click a reviewer to connect or release.

No checkout. Reference count: 0. Connect a reviewer to create a checkout.

This is called reference counting.

#

So far, our reviewers have all been reviewing the same repository. But what happens when a user submits a different one?

Suppose we’re reviewing acme/api and acme/website. They need separate checkouts, each with its own reference count.

Select **Reviewer A**, then click `acme/api` to connect them. Do the same with **Reviewer B** and `acme/website`. Now click `acme/website` again to release Reviewer B’s checkout. Watch it disappear while Reviewer A’s checkout stays available.

Select a reviewer, then click a repository to connect or release.

Reviewer A selected. Neither repository has a checkout.

We can keep track of these checkouts in a map, using the repository URL and commit as the key. Each entry holds a checkout and its reference count. Reviewers requesting the same key share that checkout. When its count reaches zero, we delete the checkout and remove the entry.

Select `acme/api` beneath **Reviewer A** to add it to the map. Select it beneath **Reviewer B** too: the reference count increases, but there’s still only one entry. Deselect it beneath both reviewers and watch the entry disappear.

Using a map allows us to access shared resources by key. Reference counting lets us automatically clean them up when they’re no longer in use. Effect’s `RcMap` combines these two ideas into a single data structure.

Let’s use RcMap to implement the checkout sharing we’ve just seen.

#

Let’s put our checkouts behind a Git service. Its checkout method takes a repository URL and desired commit and returns the path to a local checkout.

We’ll group the URL and commit in a Repository class. This is what we’ll pass to checkout and use as the key in our map:

RcMap compares keys using Effect’s equality rules. Two separately created Repository values with the same URL and commit count as the same key. In Effect v4, plain objects also have structural equality by default, so we could use those instead, but Data.Class is useful here for grouping the fields and getter together.

Inside the Git service, we create the map using the RcMap.make constructor. We must define a lookup function, which will receives a Repository and returns an Effect that acquires the checkout.

For this example, we’ll create a temporary directory and log where the clone would go. A full implementation would run Git before returning the path.

Constructing the map doesn’t create any directories yet. When checkout is invoked, it delegates to RcMap.get. If the key isn’t in the map, RcMap runs the lookup function which will create the checkout and insert an entry into the map. Otherwise, the reviewer shares the existing checkout.

Notice that the checkout method provided by the Git service requires a Scope.

That’s how RcMap tracks how long each reviewer needs the files. When the Scope associated with a given call to checkout closes, the reference count for that map entry is decremented. When no references remain, the temporary directory is removed.

Let’s run two reviews against the same repository. Each review gets its own scope, so it can release the checkout as soon as it’s done.

Effect.all runs both reviews concurrently against the same repository, so if one is still acquiring the checkout, the other waits for it. Once it’s ready, both receive the same directory path.

Placing Effect.scoped after Effect.flatMap keeps the scope open while the review runs. When the review finishes, the scope closes and releases its reference to the checkout.

Say the dependency review finishes first. Its scope closes, taking the reference count from two to one. The code review carries on using the directory. When that finishes, the count reaches zero and RcMap removes the directory. Neither review needs to know what the other is doing.

Both reviews finish, their associated Scope s are closed, and the checkout directory is deleted from the file system. A second later, another reviewer asks for the same repository. Now we have to re-clone the entire repository again.

This is pretty inefficient.

Luckily, RcMap provides us with the ability to keep entries around for a little while before cleaning them up. Let’s set the map’s idleTimeToLive to five seconds, giving another review a chance to reuse the same checkout before it’s deleted:

Now, when the reference count for a given checkout reaches zero, the checkout sticks around for five more seconds before being cleaned up.

Close all three scopes below to start the countdown, then click Get checkout before it reaches zero. You’ll get the same directory back. Close the new scope and wait five seconds to see it removed.

Suppose we only want to run an optional review if there’s already a checkout for that repository. If there isn’t, we’ll skip the review. Calling get won’t work here: it creates a checkout when the key is missing. We need a way to request an existing entry without creating one.

RcMap.getOption does exactly that. It returns Some(path) for an existing checkout, or None if the key is missing. If the checkout is still being acquired, it waits for the path rather than returning None.

Try clicking Get existing below with an empty map. Then, click Get checkout followed by Get existing and observe the difference.

Suppose we added a scheduler that assigns reviews to a pool of AI workers. The dependency review has finished, but the code review is still queued, waiting for a free worker. Nothing is using the checkout, so its idle countdown has started. We’d like to keep those files around for the queued review rather than clone the repository again.

The scheduler can call RcMap.touch periodically to reset the idle countdown while the review waits for a worker:

Keeping idle checkouts around saves us from cloning them again, but they still take up disk space. We can limit how many entries the map holds by setting the map’s capacity:

Attempting to add a new entry to a map that is at capacity will fail with a Cause.ExceededCapacityError.

The map below has a capacity of two entries. Request acme/api and acme/website, then try acme/docs. The third request fails, but requesting acme/api again still works because it shares an existing entry.

With a capacity set, RcMap.get can fail with Cause.ExceededCapacityError when a new entry would exceed the limit. Our GitService.checkout signature needs to include that error unless we map it to our existing GitError:

I’d keep ExceededCapacityError in the signature so callers can decide what to do when the map is full, such as re-queueing the review or retrying after a delay.

Suppose a review runs a tool that modifies files in the checkout. The repository URL and commit haven’t changed, but the files on disk have. We’d like the next review to get a clean copy.

Inside our Git service, we can remove the entry from the map with RcMap.invalidate:

The next time get is invoked for that repository, the map’s lookup function will be called and a new checkout will be created.

But what happens to reviews that were using the checkout we invalidated? Let’s take a look.

Click Invalidate below, then Get checkout. Notice what happens to the scopes referencing the invalidated checkout.

Invalidated map entries keep their references, so in our case the old checkout will remain on the file system until the last Scope associated with it is closed.

#

Effect’s reference-counted data structures are exceptionally useful in situations where you have multiple consumers that all need shared access to the same scoped resource.

We focused on RcMap in this post, which manages shared resources by key. Effect also provides RcRef for when you only need to share a single resource.

We hope you enjoyed this edition of Module of the Week! Until the next one - Happy Effecting!

Previous This Week in Effect - 136

── more in #ai-agents 4 stories · sorted by recency
── more on @effect 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/module-of-the-week-r…] indexed:0 read:8min 2026-09-21 · —