Commits
Make the issue-triage example its own nested Go module so the genqlient
toolchain and runtime stay out of the parent's dependency graph (the
library stays stdlib-only on a running machine's path).
GitHub access:
- github/ is a genqlient-generated, type-safe client built from the
vendored public schema; collect/sync each do one GraphQL round-trip,
plus typed issue/project mutations. gh survives only as a token
fallback (gh auth token); repo resolves from GITHUB_REPOSITORY or the
git origin remote.
Agent harness:
- Replace the $ISSUES_AGENT <verb> <number>+stdin convention with a
command template: AGENT='pi -p {{prompt}}' ({{prompt}} substituted as a
literal argv element, no shell; stdin fallback when absent). Per-verb
AGENT_<VERB> override the base; only AGENT is required. Prompts are Go
text/templates built by the example (content embedded), overridable via
AGENT_PROMPT_DIR/<verb>.tmpl. New examples/issues/agent package holds the
mechanism. Rename ISSUES_TEST -> VERIFY_CMD.
Also:
- cmd/issuesctl drives the whole machine against a live issue in dry-run
(GitHub writes suppressed) to iterate on prompts.
- record failure on the terminal collect/sync error edges so a
non-retryable failure surfaces its cause.
Promote the Stately schema from the test-fixture schema/testdata/ to a
first-class schema/machineSchema.json (byte-preserving move; provenance kept
as a link to statelyai/schema). Docs (architecture, schema-subset) now link
the vendored copy instead of Stately's GitHub blob URL and drop the stale
internal/schema/ path.
Repoint every example machine.json's $schema at the vendored file by relative
path -- valid, offline, no network reference -- and add one to simple for
parity. The loader ignores $schema, so regeneration is a no-op.
Declare $schema (string) at the vendored schema's root so a $schema-carrying
document validates under additionalProperties:false; this matches the loader's
existing rule (root-only, nested rejected). A minimal, deliberate deviation
from upstream verbatim -- structure is untouched, and the schema pin test
expects it so a re-vendor that drops it fails loudly.
Add schema.TestVendoredSchemaSurface to guard the vendored file (nothing
embeds it). Verified: strict Draft-2020-12 validation passes for all four
examples.
Scaffolding and every examples/*/gen.go now emit '//go:generate go tool
statesman generate'; README install/quickstart switch to 'go get -tool' +
'go tool statesman <verb>'. Clean cutover -- no go install / $PATH path left.
go.mod gains a 'tool' directive so the repo dogfoods it; go mod tidy then
promotes golang.org/x/tools to a direct require and adds the missing go.sum
entry (codegen and statesmantest import it directly).
Add cmd/statesman tests: a resolveJSONPath table test and an init
scaffold->build integration test (offline, via a local replace), lifting
package coverage 2.1% -> 35.0%.
Docs: fix the stale architecture status header (design draft -> v1) and
record the go tool decision in DECISIONS.md. Decided against CI for now.
A worked example driving LLM agents from shell. The issues coordinator collects
and classifies an issue, then for bugs investigates and fixes it via the
investigate/fix sub-machines (fromMachine, seeded from context), and summarises
and syncs back to GitHub. collecting/syncing wear the retry/timeout pattern;
classify branches on the result. Exercises the recent runtime work: guard
fallbacks (ShouldRetry/HasAttemptsLeft), IsTransient in the error-edge
overrides, and Snapshot.InvokeRestarts. Real exec adapters
(ISSUES_AGENT/ISSUES_TEST); Sync/FakeActor tests are CI-runnable.
A sub-machine invoke (`src: func() *statesman.Machine[CCtx, CEvt]`) resolved
but was never registered, so the child never started. Now:
- detectKind recognizes the *Machine[CCtx,CEvt] return as AdapterMachine and
extracts the child type args.
- emitRegisterInvokes wires it with MachineActor; the child packages are
imported automatically; the done event carries the child's terminal Context
as Output, the error event its ErrorReason.
- MachineActor takes a per-spawn factory (restart-safe) + an input mapper, and
seeds the child via the new pre-start Machine.SetInitialContext — the machine
analogue of a promise input mapper. Codegen emits the <Src>Input mapper for
machine invokes too.
Covered by internal/codegen/testdata/submach.
Close gaps that made retries/timeouts over network calls error-prone:
- runtime: BackoffActor adapter for dynamic (exponential/jittered) backoff
via the machine TimerService, since `after` delays are static.
- runtime: Snapshot.InvokeRestarts surfaces per-invoke re-spawns so an
observer can alarm on a runaway timed retry loop.
- runtime: IsTransient(err) classifies retryable transport timeouts.
- codegen: a guard type on 2+ callsites collapses to one context-only
fallback method, with optional per-callsite overrides (decision 15).
- codegen: `generate` warns when a promise invoke has no onError and no
after (a failed/hung call then has no exit and stalls the actor).
Tests race-clean; orderpkg fixture migrated to the guard fallback.
New statesman/diagram package walks a Definition once to emit either a
Mermaid stateDiagram-v2 string (docs) or a Unicode/ANSI outline tree
(terminal); no image output, so the binary keeps zero render deps.
- Mermaid(def), Text(def, opts...), Live(ctx, m, w, opts...)
- statesman diagram CLI verb: --format mermaid|term (TTY default),
--watch (mtime poll, keep-last-good), -o, --ascii, --verbose
- Live overlays a running machine's active states/status/version/timers
via Subscribe (this machine's own config only)
- Machine.Definition() accessor so Live can read the immutable tree
- schema.Load accepts a root $schema meta-keyword for Stately round-trip
- examples/issues machine (WIP: taking/giving targets not yet wired)
- docs: DECISIONS, schema-subset, README, architecture
Rename the per-package machine files to machine-named, dot-separated
forms and make the machine id the filename, not the directory:
machine.json -> <id>.machine.json
machine_gen.go -> <id>.machine.gen.go (generated; .gen. marks it)
types.go -> <id>.events.go
impl.go -> <id>.behavior.go
actors.go / delays.go keep their domain names
- emit: //go:embed and the facade key off def.ID; extract shared
implMethods() so the Implementations interface and the stub skeleton
use one signature computation.
- stub: write the machine-named files and emit an Impl behavior
skeleton (one panicking method per action/guard/invoke-input
callsite) into <id>.behavior.go, strictly additive. Closes the
long-documented-but-unimplemented D51 gap.
- cli: loadDef globs the single *.machine.json per package and requires
its prefix to equal the machine id; generate writes <id>.machine.gen.go;
init scaffolds <id>.machine.json. Drop the never-enforced id==dirname.
- dot over snake_case avoids Go's _test.go / _GOOS build-constraint
suffixes.
Regenerate examples/simple and testdata/orderpkg; add stub_test.go.
Update DECISIONS, architecture (D47/D49/D50/D51), README, TODO.
Make the issue-triage example its own nested Go module so the genqlient
toolchain and runtime stay out of the parent's dependency graph (the
library stays stdlib-only on a running machine's path).
GitHub access:
- github/ is a genqlient-generated, type-safe client built from the
vendored public schema; collect/sync each do one GraphQL round-trip,
plus typed issue/project mutations. gh survives only as a token
fallback (gh auth token); repo resolves from GITHUB_REPOSITORY or the
git origin remote.
Agent harness:
- Replace the $ISSUES_AGENT <verb> <number>+stdin convention with a
command template: AGENT='pi -p {{prompt}}' ({{prompt}} substituted as a
literal argv element, no shell; stdin fallback when absent). Per-verb
AGENT_<VERB> override the base; only AGENT is required. Prompts are Go
text/templates built by the example (content embedded), overridable via
AGENT_PROMPT_DIR/<verb>.tmpl. New examples/issues/agent package holds the
mechanism. Rename ISSUES_TEST -> VERIFY_CMD.
Also:
- cmd/issuesctl drives the whole machine against a live issue in dry-run
(GitHub writes suppressed) to iterate on prompts.
- record failure on the terminal collect/sync error edges so a
non-retryable failure surfaces its cause.
Promote the Stately schema from the test-fixture schema/testdata/ to a
first-class schema/machineSchema.json (byte-preserving move; provenance kept
as a link to statelyai/schema). Docs (architecture, schema-subset) now link
the vendored copy instead of Stately's GitHub blob URL and drop the stale
internal/schema/ path.
Repoint every example machine.json's $schema at the vendored file by relative
path -- valid, offline, no network reference -- and add one to simple for
parity. The loader ignores $schema, so regeneration is a no-op.
Declare $schema (string) at the vendored schema's root so a $schema-carrying
document validates under additionalProperties:false; this matches the loader's
existing rule (root-only, nested rejected). A minimal, deliberate deviation
from upstream verbatim -- structure is untouched, and the schema pin test
expects it so a re-vendor that drops it fails loudly.
Add schema.TestVendoredSchemaSurface to guard the vendored file (nothing
embeds it). Verified: strict Draft-2020-12 validation passes for all four
examples.
Scaffolding and every examples/*/gen.go now emit '//go:generate go tool
statesman generate'; README install/quickstart switch to 'go get -tool' +
'go tool statesman <verb>'. Clean cutover -- no go install / $PATH path left.
go.mod gains a 'tool' directive so the repo dogfoods it; go mod tidy then
promotes golang.org/x/tools to a direct require and adds the missing go.sum
entry (codegen and statesmantest import it directly).
Add cmd/statesman tests: a resolveJSONPath table test and an init
scaffold->build integration test (offline, via a local replace), lifting
package coverage 2.1% -> 35.0%.
Docs: fix the stale architecture status header (design draft -> v1) and
record the go tool decision in DECISIONS.md. Decided against CI for now.
A worked example driving LLM agents from shell. The issues coordinator collects
and classifies an issue, then for bugs investigates and fixes it via the
investigate/fix sub-machines (fromMachine, seeded from context), and summarises
and syncs back to GitHub. collecting/syncing wear the retry/timeout pattern;
classify branches on the result. Exercises the recent runtime work: guard
fallbacks (ShouldRetry/HasAttemptsLeft), IsTransient in the error-edge
overrides, and Snapshot.InvokeRestarts. Real exec adapters
(ISSUES_AGENT/ISSUES_TEST); Sync/FakeActor tests are CI-runnable.
A sub-machine invoke (`src: func() *statesman.Machine[CCtx, CEvt]`) resolved
but was never registered, so the child never started. Now:
- detectKind recognizes the *Machine[CCtx,CEvt] return as AdapterMachine and
extracts the child type args.
- emitRegisterInvokes wires it with MachineActor; the child packages are
imported automatically; the done event carries the child's terminal Context
as Output, the error event its ErrorReason.
- MachineActor takes a per-spawn factory (restart-safe) + an input mapper, and
seeds the child via the new pre-start Machine.SetInitialContext — the machine
analogue of a promise input mapper. Codegen emits the <Src>Input mapper for
machine invokes too.
Covered by internal/codegen/testdata/submach.
Close gaps that made retries/timeouts over network calls error-prone:
- runtime: BackoffActor adapter for dynamic (exponential/jittered) backoff
via the machine TimerService, since `after` delays are static.
- runtime: Snapshot.InvokeRestarts surfaces per-invoke re-spawns so an
observer can alarm on a runaway timed retry loop.
- runtime: IsTransient(err) classifies retryable transport timeouts.
- codegen: a guard type on 2+ callsites collapses to one context-only
fallback method, with optional per-callsite overrides (decision 15).
- codegen: `generate` warns when a promise invoke has no onError and no
after (a failed/hung call then has no exit and stalls the actor).
Tests race-clean; orderpkg fixture migrated to the guard fallback.
New statesman/diagram package walks a Definition once to emit either a
Mermaid stateDiagram-v2 string (docs) or a Unicode/ANSI outline tree
(terminal); no image output, so the binary keeps zero render deps.
- Mermaid(def), Text(def, opts...), Live(ctx, m, w, opts...)
- statesman diagram CLI verb: --format mermaid|term (TTY default),
--watch (mtime poll, keep-last-good), -o, --ascii, --verbose
- Live overlays a running machine's active states/status/version/timers
via Subscribe (this machine's own config only)
- Machine.Definition() accessor so Live can read the immutable tree
- schema.Load accepts a root $schema meta-keyword for Stately round-trip
- examples/issues machine (WIP: taking/giving targets not yet wired)
- docs: DECISIONS, schema-subset, README, architecture
Rename the per-package machine files to machine-named, dot-separated
forms and make the machine id the filename, not the directory:
machine.json -> <id>.machine.json
machine_gen.go -> <id>.machine.gen.go (generated; .gen. marks it)
types.go -> <id>.events.go
impl.go -> <id>.behavior.go
actors.go / delays.go keep their domain names
- emit: //go:embed and the facade key off def.ID; extract shared
implMethods() so the Implementations interface and the stub skeleton
use one signature computation.
- stub: write the machine-named files and emit an Impl behavior
skeleton (one panicking method per action/guard/invoke-input
callsite) into <id>.behavior.go, strictly additive. Closes the
long-documented-but-unimplemented D51 gap.
- cli: loadDef globs the single *.machine.json per package and requires
its prefix to equal the machine id; generate writes <id>.machine.gen.go;
init scaffolds <id>.machine.json. Drop the never-enforced id==dirname.
- dot over snake_case avoids Go's _test.go / _GOOS build-constraint
suffixes.
Regenerate examples/simple and testdata/orderpkg; add stub_test.go.
Update DECISIONS, architecture (D47/D49/D50/D51), README, TODO.