a3s-effect 0.1.0

Effect-style actor runtime for projecting an A3S Code harness from an immutable event log
Documentation

a3s-effect

中文

a3s-effect is the actor runtime for an A3S Code harness. It is checked out at packages/effect. It keeps two ideas separate, and the rest of the crate follows from that split.

A transition is a program value. It names its success, its expected error, and the services it needs. Nothing in that description calls a model, a tool, or a compactor. Effect::run is the edge that executes it.

A thread is an immutable fact log. Components fold the log into a view and the transitions that view enables. The runtime runs those transitions, appends the facts they return, and folds again. Resume reads the same log. A confirmation and a question stay parked until an answer fact arrives. Neither wait is a process-local timer.

This crate is not a port of another agent framework and it does not embed the TypeScript Effect runtime. The charts below are the four claims the code already implements.

A program is a value until it is run

Effect is a description. Building one, mapping it, or wrapping it in retry does not enter the service. The construct test counts the service call at zero until Effect::run, then one.

Fourteen public methods return another Effect: succeed, fail, die, from_async, map, and_then, catch_fail, retry, timeout, with_span, zip_par, race, bracket, and provide. One method executes the description.

The program is a value until Effect::run

A defect is not retried

Exit has three stops. Exit::Fail carries an ActorError and is the expected failure. Exit::Die is a defect. Exit::Interrupt is cancellation from a parent scope.

retry repeats Exit::Fail only. In the operator test, a failure with one retry left runs twice. A defect with three retries left runs once. catch_fail handles Exit::Fail and lets Exit::Die through.

A defect is not retried

The log decides the next transition

resume folds the facts already stored. Each component's step consumes one fact. output returns the view and the transitions that state enables. A transition whose key is already a cause on some fact is not selected again, so a second resume of a finished text turn adds nothing and does not call the model.

If two components enable the same key, projection returns ActorError::DuplicateTransition before either effect body runs.

A text turn stops after model.turn. A confirmation thread grows through the answer and the tool. A question thread grows through question.answered and lands on the second model row, not on a tool result. A duplicate key stops at the ingress fact.

The log decides the next transition

Both waits end on a fact

The coding scheduler parks in CodingPhase::Confirm until a confirmation.answered fact, and in CodingPhase::Question until a question.answered fact. The question view keeps allow_free_text from the model decision. While parked, the fold enables no transition, so resume takes zero steps. There is no timeout in that path.

After confirmation.answered with approved: true, the tool transition runs once. After question.answered, the next model turn runs once. A denial completes the turn without calling the tool.

Both waits end on a fact, not a timer

What else the same rules cover

These are the same two mechanisms, not extra philosophies.

Mechanism Where it lives
Retries, sibling cancellation, and release on every exit Effect::retry, Effect::zip_par, Effect::bracket
Services in the type, swapped at the edge the S parameter and Effect::provide
Spans recorded by the runtime Effect::with_span
A log line that is not a fact parse_fact_json
A harness that cannot run HarnessConfig::new rejects a zero step limit or zero model attempts

coding_actor is sugar for the stock Meta Harness tree: system, tools, budget, compact, and infer(scheduler) on one log. Hosts can also assemble those parts with compose (system, tools, budget, compact, infer, HarnessGraph). Nested infer([...]) namespaces child transition keys; the stock Infer part keeps an un-prefixed scheduler so existing fact-log causes stay stable.

let config = HarnessConfig::new(4, 8_000, 32, 2, vec!["You are a coding harness.".into()], vec![])?;
let actor = coding_actor(config);

Run the tests from this directory:

cargo test