agentleFS
flooooooooooowflooooooooooow/flow-skills/skills/flow-effects/SKILL.md

flow-effects

Skill1 starsChanged 3 months agoMITView on GitHub

Use Flow's algebraic effects to separate what code needs from who provides it. Use for logging, clocks, I/O, randomness, dependency injection, mocking, test seams, or any request to swap an implementation without changing call sites.

The file

We split this skill into the rules it gives an agent

3 rules. About 0 are common, 2 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Use strict mode whenever you are verifying that handlers are actually reached.

    line 122
  2. Very specific:

    Keep capabilities as stateless policy.

    line 131
  3. Distinctive:

    Run the same logic under two handler stacks and assert the results differ in the way the design predicts.

    line 158

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Algebraic effects

An effect declares operations. Business logic calls them. A handler supplies them for a dynamic scope. Call sites never change.

Requires FLOW_HOST=python.

Declare, call, handle

effect Log {
    info(msg: string) -> void,
    warn(msg: string) -> void,
}

effect Clock {
    now() -> i32,
}

effect Inventory {
    stock_of(sku: i32) -> i32,
    reserve(sku: i32, qty: i32) -> i32,
}

Business logic depends only on the effect. No handler is passed in.

function place_order(sku: i32, qty: i32) -> i32 {
    Log.info("order received")
    let available: i32 = Inventory.stock_of(sku)
    if available < qty {
        Log.warn("insufficient stock")
        return -1
    }
    return Inventory.reserve(sku, qty)
}

A handler is a capability. It names the effect or effects it implements and gives a function per operation. Note the commas after each member.

capability WallClock {
    effect Clock,
    function now() -> i32 {
        return 1721224800
    },
}

capability OpsLogger {
    effect Log,
    function info(msg: string) -> void {
        let t: i32 = Clock.now()      # a handler may call other effects
        printf("  [%d] INFO  %s\n", t, msg)
    },
    function warn(msg: string) -> void {
        printf("  WARN  %s\n", msg)
    },
}

capability TestBackend {
    effect Inventory, Notify,          # one capability, several effects
    function stock_of(sku: i32) -> i32 {
        return 99
    },
    function reserve(sku: i32, qty: i32) -> i32 {
        return 1
    },
    function send(recipient: string, msg: string) -> void {
    },
}

A handler is installed for a scope:

function main() -> i32 {
    let mut accepted: i32 = 0

    handle Clock with WallClock {
        handle Log with OpsLogger {
            handle Inventory with WarehouseInventory {
                accepted = place_order(1001, 2)
            }
        }
    }

    handle Inventory with TestBackend {
        accepted = place_order(1001, 2)
    }

    return 0
}

The same place_order runs against production and test handlers. Nested handles shadow outer ones and the outer handler is restored on exit.

The trap

With no handler installed, an effect operation falls back to a zeroed default and returns quietly. A program can look healthy while doing nothing.

Two defences:

FLOW_STRICT_EFFECTS=1 FLOW_HOST=python flow run program.flow

and, at compile time:

FLOW_STRICT_EFFECTS=1 FLOW_HOST=python flow compile program.flow --strict-effects

Use strict mode whenever you are verifying that handlers are actually reached.

Do not pass handlers as parameters

Dispatch goes through the effect's current handler pointer. Passing a stateful struct into a function does nothing; the call still dispatches through the installed handler or the default. Several older examples carry a header explaining that they were rewritten for exactly this reason.

Keep capabilities as stateless policy. Put mutable state in ordinary let mut locals in the caller and thread it through the handle scope.

Read these first

cat examples/effects/showcase.flow            # one service, many worlds
cat examples/effects/dependency_injection.flow
cat examples/effects/async_primitives.flow    # stdlib Async and AsyncIO
cat examples/effects/state_effects.flow
cat docs/effects-showcase.md                  # includes the honest limitations

lib/stdlib/async.flow provides Async and AsyncIO. There is no fiber scheduler; SimulatedAsync is a deterministic synchronous stand-in and BlockingAsyncIO.sleep_ms blocks. Say this rather than implying concurrency.

When effects earn their place

Good fits: logging, clocks, randomness, storage, network, telemetry, anything you want to fake in a test.

Poor fits: pure arithmetic, tight numerical inner loops, anything where the indirection buys nothing.

Verify

Run the same logic under two handler stacks and assert the results differ in the way the design predicts. That is the property effects give you, so it is the property to test.

if production_result == test_result {
    println("FAIL: handlers were not distinguished")
    return 1
}

Report the command with FLOW_STRICT_EFFECTS=1 included, so the reader knows unhandled operations would have aborted.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.