Skip to content

Instantly share code, notes, and snippets.

@bioshazard
Created September 3, 2026 13:12
Show Gist options
  • Select an option

  • Save bioshazard/511d39767b4f5fbce46c86d8c4c44f34 to your computer and use it in GitHub Desktop.

Select an option

Save bioshazard/511d39767b4f5fbce46c86d8c4c44f34 to your computer and use it in GitHub Desktop.
Effect TS as Core / Impl / App change control

Protected Core Architecture for an Effect React SPA

Goal

Build the React SPA so that AI coding agents can change implementation code often without changing product rules by accident.

Separate stable product rules from replaceable implementation code.

Treat changes to the stable part as exceptional.


Main Structure

Use three main areas:

src/
  core/
    domain/
    schemas/
    errors/
    services/
    use-cases/

  implementation/
    api/
    storage/
    services/
    layers/

  app/
    runtime/
    hooks/
    components/
    routes/

core/

core/ defines what the system means.

Put these items in core/:

  • domain types
  • Effect schemas
  • data constraints
  • product rules
  • domain errors
  • service contracts
  • business use cases

Example:

UserId
User
InvalidUser
UserRepository contract
createUser()

Do not put React code, browser APIs, storage code, or HTTP code in core/.

implementation/

implementation/ defines how the system performs external work.

Put these items here:

  • HTTP clients
  • API adapters
  • storage adapters
  • browser integrations
  • service implementations
  • Effect Layers

Implementation code can change often.

It must continue to satisfy the contracts in core/.

app/

app/ connects the system to React.

Put these items here:

  • React components
  • routes
  • hooks
  • application startup
  • Effect runtime setup
  • production Layer assembly

Dependency Rule

Use one-way dependencies.

core
↑
implementation
↑
app

More precisely:

implementation -> core
app            -> core
app            -> implementation

Never allow:

core -> implementation
core -> app

core/ must not know about React, fetch, local storage, IndexedDB, analytics tools, or other external systems.


Effect Design

Effect works well with this structure.

Use Effect Schema for domain data and constraints.

Use Effect service definitions for dependencies.

Put the service contract in core/.

Put the service implementation and Layer in implementation/.

Example:

core/services/Auth.ts
        ↓
implementation/auth/AuthHttp.ts
        ↓
implementation/layers/Production.ts
        ↓
app/runtime.ts

A test can replace:

AuthHttp

with:

AuthTest

The business use case does not change.


Agent Rule

Give agents different authority in different folders.

For core/:

Treat this directory as a stable product contract. Do not change schemas, constraints, errors, service contracts, or business rules unless the task explicitly requires a contract change.

For implementation/:

You can refactor this directory freely. All changes must continue to satisfy the contracts and tests in core/.

This turns the folder structure into a change-control system.


Enforcement

Do not depend on folder names alone.

Enforce the boundary with code and repository rules.

Use:

  • import-boundary lint rules
  • tests for core/
  • contract tests
  • schema fixtures where useful
  • AGENTS.md files with folder-specific rules
  • CI checks for unexpected core/** changes
  • CODEOWNERS or extra review for core/**

A change to implementation/** is normal.

A change to core/** requires explicit intent.


Optional Stronger Boundary

Use separate packages if agents still cross the folder boundary:

packages/
  core/
  web/

Expose only approved items from the core package.

Package boundaries provide stronger control than folder rules.

Do this only if the added build and package structure gives enough value.


What Belongs in Core

Use this test:

Does this code describe the product, or does it describe one way to implement the product?

Product meaning belongs in core/.

Implementation choices do not.

Good core/ examples:

OrderId
Order
CheckoutError
CanCancelOrder
InventoryService contract
submitOrder()

Bad core/ examples:

fetchJson()
useDebounce()
queryClient
Axios configuration
localStorage wrapper
React context

Keep core/ small.

Do not turn it into a general shared/ directory.


Why This Helps AI Coding Agents

AI agents work better when a task has a fixed contract.

This task is relatively safe:

Implement this service so that it satisfies this existing contract.

This task is less safe:

Change authentication.

Stable contracts reduce the number of decisions that an agent must make.

They also make unwanted changes easier to detect.

The code diff becomes useful evidence:

implementation changed -> expected

core changed -> inspect why

Relation to Existing Practices

This design combines three established ideas.

Clean or Hexagonal Architecture

Define business rules and contracts separately from external systems.

Spec-Driven Agent Development

Treat schemas, contracts, tests, and design rules as more stable than generated implementation code.

Agent Harness Design

Put rules in the repository.

Use tests, lint rules, folder instructions, and CI to enforce them.

Do not depend only on prompts.

HumanLayer advocates much of the spec-first and contract-first approach.

Recent agent-development guidance also favors repository rules, scoped agent instructions, tests, and machine-enforced boundaries.

The additional step here is explicit:

Use the source tree to encode how much authority an AI agent has to change each part of the system.


Effect Version Rule

If the project uses Effect v3, pin that version.

Tell agents to use Effect v3 APIs and documentation.

Do not let an agent silently migrate code to another major Effect version while doing an unrelated task.

A framework version change is a contract-level change.

Treat it as such.


Final Model

Use this model:

core           = what the system means
implementation = how the system performs external work
app            = how React runs and presents the system

Then apply this policy:

Protect the core.
Replace the implementation.
Compose everything in the app.

The main purpose is not code organization.

The purpose is controlled change.

The architecture must make accidental changes to product meaning difficult, visible, and reviewable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment