From harness-claude
Spawns and manages child actors for independent, concurrent state machines communicating via message passing. Useful for dynamic entity collections like chat rooms or file uploads.
How this skill is triggered — by the user, by Claude, or both
Slash command
/harness-claude:xstate-actor-patternThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Spawn and manage child actors for independent, concurrent state machines that communicate via message passing
Spawn and manage child actors for independent, concurrent state machines that communicate via message passing
createMachine definitions in their own files.spawn inside assign actions to create child actors dynamically. Store actor references in context.spawnChild from setup() or the actors config with invoke for static actor creation.send (targeting a specific actor) or sendTo.sendParent or by reaching a final state.stop when they are no longer needed — leaked actors cause memory issues.// upload.machine.ts — child machine for a single file upload
import { createMachine, assign, sendParent } from 'xstate';
interface UploadContext {
file: File;
progress: number;
}
type UploadEvent = { type: 'PROGRESS'; percent: number } | { type: 'CANCEL' };
export const uploadMachine = createMachine<UploadContext, UploadEvent>({
id: 'upload',
initial: 'uploading',
states: {
uploading: {
invoke: {
src: 'uploadFile',
onDone: 'complete',
onError: 'failed',
},
on: {
PROGRESS: { actions: assign({ progress: (_, e) => e.percent }) },
CANCEL: 'cancelled',
},
},
complete: {
type: 'final',
entry: sendParent((ctx) => ({ type: 'UPLOAD_COMPLETE', file: ctx.file.name })),
},
failed: {
on: { RETRY: 'uploading' },
},
cancelled: { type: 'final' },
},
});
// uploader.machine.ts — parent machine that spawns upload actors
import { createMachine, assign, spawn, ActorRefFrom } from 'xstate';
import { uploadMachine } from './upload.machine';
interface UploaderContext {
uploads: Array<{ id: string; ref: ActorRefFrom<typeof uploadMachine> }>;
}
type UploaderEvent =
| { type: 'ADD_FILE'; file: File }
| { type: 'CANCEL_UPLOAD'; id: string }
| { type: 'UPLOAD_COMPLETE'; file: string };
const uploaderMachine = createMachine<UploaderContext, UploaderEvent>({
id: 'uploader',
initial: 'active',
context: { uploads: [] },
states: {
active: {
on: {
ADD_FILE: {
actions: assign({
uploads: (ctx, event) => [
...ctx.uploads,
{
id: event.file.name,
ref: spawn(uploadMachine.withContext({ file: event.file, progress: 0 })),
},
],
}),
},
CANCEL_UPLOAD: {
actions: (ctx, event) => {
const upload = ctx.uploads.find((u) => u.id === event.id);
upload?.ref.send({ type: 'CANCEL' });
},
},
UPLOAD_COMPLETE: {
actions: assign({
uploads: (ctx, event) => ctx.uploads.filter((u) => u.id !== event.file),
}),
},
},
},
},
});
Actor model basics: Each actor has its own state, processes messages sequentially, and communicates only via message passing. No shared memory. This eliminates race conditions by design.
invoke vs spawn:
invoke — creates an actor tied to a specific state node. The actor starts when the state is entered and stops when the state is exited. Best for service calls with a clear lifecycle.spawn — creates an actor tied to the machine's lifetime. The actor persists across state transitions until explicitly stopped. Best for dynamic collections.XState v5 actor types: fromPromise, fromObservable, fromCallback, fromTransition, and child state machines. Each is a different actor "logic" type:
// v5 style
const machine = setup({
actors: {
fetchUser: fromPromise(async ({ input }: { input: { id: string } }) => {
const res = await fetch(`/api/users/${input.id}`);
return res.json();
}),
},
}).createMachine({
/* ... */
});
Lifecycle management: Always clean up spawned actors. In v4, use stop action. In v5, actors are garbage-collected when their parent stops, but explicit cleanup is still recommended for resource-heavy actors.
Testing actors: Test child machines in isolation first. Then test the parent machine's coordination logic separately. This keeps tests focused and fast.
https://stately.ai/docs/actors
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeModels concurrent, independent state regions using XState parallel states. Useful for UI with independent concerns (e.g., bold and italic in a text editor) or concurrent processes (e.g., upload with validation).
Builds, tests, and debugs event-driven state machines in Laravel using EventMachine. Activates on state machine definitions, behaviors, test assertions, parallel states, delegation, timers, and endpoints.
Guides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.