Skip to content

Getting Placement Right

Most REX contributions are sent back for rework for one reason. It is not failing tests, broken builds, missing types, or merge conflicts; those are easy to fix. It is placement: putting the feature at the wrong level of generality in the module hierarchy.

This document explains why placement is hard, what “right” looks like for REX, and how to make the decision before any code is written.


A feature in the wrong place is rarely “broken.” It works. The tests pass. The build is clean. It is wrong in a subtler way:

  • It ossifies an assumption that does not belong at that layer (a generic module quietly starts to know about a specific study, site, or client).
  • It duplicates logic that should have lived one level up (the same idea is now in two child modules and will drift apart over time).
  • It pollutes a generic module with something specific to one researcher’s needs (every future contributor now has to reason around that special case).

These mistakes look fine in isolation. They only become visible when the next researcher tries to reuse the module, and by then the technical debt is higher than the investment of placing it correctly the first time.

The REX design target is “what serves REX as a framework,” not “what makes this study work.” The bar for placement is therefore higher than in a typical single-product codebase. Treat the placement decision as the most important architectural call of the contribution. Get it right before any code is written.


REX modules sit at different levels of generality. From most general to most specific:

LevelExamplesWhat belongs here
Foundationrex-coreInfrastructure every module needs: lifecycle, storage, configuration, message passing, identifier management.
Shared infrastructurerex-spider, rex-listsCapabilities reused by multiple downstream modules. Logic that is not tied to any single site or study.
Domain modulesrex-page-manipulation, rex-history, rex-search-mirrorA coherent capability that crosses sites: DOM manipulation, browsing history, search result mirroring.
Child modulesrex-spider-chatgpt, rex-spider-perplexitySite-specific or context-specific logic that only makes sense for one target.

The rule of thumb: aim for the highest level of generality that is still appropriate. A change that benefits all spider modules belongs in rex-spider, not in rex-spider-chatgpt. A change that only makes sense for ChatGPT belongs in rex-spider-chatgpt, not in rex-spider.

“Highest appropriate” has a ceiling, too. Pushing something into rex-core because it could technically live there is just as wrong as pushing it into a child module: rex-core is for things every module needs, not things that might be useful to several.


Before you decide: describe the feature properly

Section titled “Before you decide: describe the feature properly”

You cannot place a feature you have not described. A one-line title (“add tracking for X”, “support Y”) is not enough. Before you start exploring repos, write down (or tell your AI assistant) answers to each of these:

  • What the feature does. The observable behavior, end to end.
  • Who it is for. Only your study, or potentially any researcher using REX?
  • What triggers it. A user action, a page event, a config flag, a schedule?
  • What data it touches or produces. What is read, what is written, what is sent where?
  • Which sites or contexts it applies to. One site, a category of sites, all sites?
  • Why it cannot be done with existing modules. What is missing today?

If your answers are vague, that is the first signal to slow down.


Before deciding which public module a feature belongs in, decide whether it belongs in a public module at all. The rules are in The Public/Private Split. In short: if a researcher at a completely different university would not find the feature useful, it belongs in your private extension. A feature can also be split, with the generic mechanism in a public module and the study-specific configuration in your private extension.


1. Read module-map.md against your description

Section titled “1. Read module-map.md against your description”

Open Module Map and scan for every module that could plausibly host your feature, including modules you did not initially think of. Pay attention to the “Extend this module when” and “Do NOT extend this module for” sections for each candidate.

If a plausible candidate module is not cloned in your working directory yet, clone it. You cannot decide whether a module is the right home without reading its source.

READMEs are often minimal. The source files (.mts files in each module’s src/ directory) are the authoritative record of what the module does. For each candidate module, read enough source to answer:

  • Does this module already do something close to what I need?
  • If I added my feature here, would it fit the module’s existing responsibility, or would it pull the module in a new direction?
  • Are there utilities here I could extend rather than re-implement elsewhere?

3. Check whether the feature is actually shared

Section titled “3. Check whether the feature is actually shared”

If you are tempted to add a feature to a child module, ask: would any other child module want this? If yes, the feature belongs one level up. The classic mistake is adding the same idea to rex-spider-chatgpt and rex-spider-perplexity independently: both should have used (or extended) rex-spider.

The reverse mistake is also real: pushing something into a shared module when only one child module will ever use it. That bloats the shared module and forces every downstream consumer to compile and reason about code that does not serve them.

4. Check whether a new module is warranted

Section titled “4. Check whether a new module is warranted”

Sometimes the right answer is none of the existing modules. Before proposing a new one, confirm it meets at least one of these criteria:

  1. Requires a permission other modules in the bundle do not. Isolating the permission reduces the surface area for extensions that do not need it.
  2. Conceptually distinct and desirable in isolation. A researcher could reasonably want this module without the rest of its current neighbors.
  3. Existing module’s config has grown complex enough to warrant splitting. A single module’s config has branched into multiple unrelated feature sets.
  4. Separable without duplication. The split does not require copying shared logic. If it does, extract the shared logic into a common module first.

If none apply, extend an existing module. If multiple apply, state which in your plan.

5. Confirm the placement before writing code

Section titled “5. Confirm the placement before writing code”

Before you write the first line of code, state out loud (or in your PR plan):

  • The module the feature is going into
  • The level in the hierarchy
  • Why that level is the highest appropriate one
  • Why no existing functionality in that module or another already covers it

If you are working with an AI assistant, have it state this back to you. If something feels off, that is the signal to revisit, not to push ahead and hope the review surfaces it.


What “wrong placement” looks like in review

Section titled “What “wrong placement” looks like in review”

Common patterns that get sent back:

SymptomWhat is probably happeningWhere it usually belongs
Two child modules grow similar functions a few weeks apartShared idea placed at the child levelOne level up, in a shared-infrastructure module
A generic module references a specific site, study, or clientStudy-specific logic leaked into a public moduleThe private extension repo
rex-core grows a feature only some modules usePushed too high in the hierarchyA shared or domain module
A new module duplicates utilities from an existing oneNew module was unnecessaryExtend the existing module
A new module is permission-isolated but only one extension uses itNew module was unnecessaryThe private extension repo

The agent-facing rules of engagement for placement, source-reading, and the rest of the REX conventions are in AGENTS.md (see Working with an AI Assistant). These are the parts worth knowing as a contributor working alongside it:

  • The assistant is instructed to require a concrete feature description from you before it explores anything. If it starts exploring before you have described the feature, push back; that order matters.
  • The assistant is instructed to clone or ask you to clone any plausible candidate module before deciding on placement. If it proposes a placement without having read the source of every plausible candidate, push back.
  • The assistant is instructed to take your disagreement on placement seriously: you often know the domain better than the exploration can reveal. Use that.