Repository navigation
Version Packages (alpha) - #148
Merged
Merged
Conversation
|
Important Review skippedBot user detected. To trigger a single review, invoke the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
github-actions
Bot
force-pushed
the
changeset-release/next
branch
from
October 22, 2026 05:42
f108511 to
8f5994d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and publish to npm yourself or setup this action to publish automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to next, this PR will be updated.
nextis currently in pre mode so this branch has prereleases rather than normal releases. If you want to exit prereleases, runchangeset pre exitonnext.Releases
@statelyai/agent@2.0.0-alpha.29
Minor Changes
#155
a6d05dcThanks @davidkpiano! - Declare models once.createAiSdkExecutorsandcreateTanStackAiExecutorsnow accept a machine built bysetupAgent(...).createMachine, or thesetupAgent(...)result itself, and read themodelsdeclared insetupAgent({ models }):createAiSdkExecutors(machine)replacescreateAiSdkExecutors({ models })when the map is already on the setup. The options form is unchanged and still takessettings. A wrong-shaped argument, including a setup or machine that declared no models, now throws when the executors are built, naming what it got. Core stays provider-free.#161
48f6a9fThanks @davidkpiano! - Closed choices anddecideNext. A decision can list the concrete events a model may pick instead of asking it to write a payload.agent.decideinputpayloads: one payload list per event type, withouttype. Each payload becomes anAgentDecisionOptiononrequest.options; an event type with nopayloadskey is offered as{ type }, and an empty list (escalate: []) withholds it. Only options the snapshot accepts are offered, and a choice that matches none is retried asrejected-by-guard. When the snapshot accepts no option, the invoke fails at once withAgentDecisionExhaustedError(no attempts, no model call), which itsonErrorhandles.createAiSdkExecutorspresents a closed choice as onechoose_optiontool whose input is an enum of option ids such asrefund:20. An id that is not offered, or a call to another tool, is retried, with the valid ids in the attempt's reason.decideNext(snapshot, options)asks a model to pick one event that any XState snapshot accepts now, including a plaincreateMachine/setupmachine with noagent.*invoke. It takesdecide,model,allowedEvents(exact types or wildcards), and optionalpayloads(snapshot),lookahead(describe each option's state and context change;truereports every changed context key, an array of keys reports only those, and each value is cut at 120 characters),system,prompt,name,maxRetries, andsignal. It returns{ event, attempts, request }and never sends: the host sends after re-checkingcan.#164
9da8d10Thanks @davidkpiano! -decideNextreports what the guards blocked. The result haswithheld: ChosenEvent[]: everyallowedEventstype and payload, built like the offered options, thatsnapshot.can(event)rejected. It is filled when nothing is offered too.AgentDecisionRequest.withheldholds the same list, and a closed choice under the runtime (agent.decidewithpayloads) sets it as well, so a decision'srequest.starttrace shows it.#161
48f6a9fThanks @davidkpiano! - Decision models in the models map.createAiSdkExecutorsaccepts an AI SDK decision model, such as TypeSafe's Jev, as amodelsentry next to the language models. A decide request whose model ref names a decision model is answered throughexperimental_decide.payloadsonagent.decide, ordecideNext. A request withoutoptionsthrows.choicequestion. Each option id is a label, and its criterion is the option's description, or the event as JSON when it has none. The instructions are the request'ssystemandprompt, and the state is the request'sinput.confidencewhen the model reports it. Branch on it in your own code.#161
48f6a9fThanks @davidkpiano! -describeMachine.describeMachine(machine, { snapshot? })renders any machine, from a config or from TypeScript, as deterministic plain text for a model to read:Machine <id>, thenGoal: <description>, then every state with its transitions (guard, target, context keys set), then the event schemas with their fields (the agent machine's, or a plain XState machine's ownschemas.events). Passsnapshotto mark the active states with*. Use it as the prompt for a model that proposes an improved machine, or as context for a decider.#161
730d64cThanks @davidkpiano! -AgentEventLogStore.listThreads. An optional store method,listThreads({ prefix?, limit?, cursor? }), returns{ threadIds, cursor? }: the threads holding at least one entry, paged by an opaque cursor.createInMemoryEventLogStore()implements it, andassertEventLogStoreConformancechecks it only when a store has it, so existing stores keep compiling and passing.#161
48f6a9fThanks @davidkpiano! - Payload and output branching inexplorePathsandcanReach. Exploration no longer applies every event as a bare{ type }, so guards that read the payload are explored too.Breaking:
explorePathsandcanReachno longer taketextorinvokes. Move each canned value into a one-elementoutputslist:text: { parse: { total: 42 } }becomesoutputs: { parse: [{ total: 42 }] }.simulateAgent'sscript.textandscript.invokesqueues are unchanged.payloads(snapshot): payloads per event type at each branch point. A type with an entry forks once per payload, at decisions and idle waits; guard-rejected variants count inprunedByGuard. A decide input's ownpayloadsbecome that decision's branch set.enum,const,boolean,null, or a union of those) contributes its values, and an optional one also the omitted case, up to 64 combinations. Plain XState machines are filled from their ownschemas.events. A type the fill cannot enumerate (a non-finite field, or over 64) is listed in the newreport.unexpandedEvents, so you know whichpayloadsentries to add. A type with no schema is listed too when a transition for it could read the payload (a function transition, a guard, amatchespattern, or acontextmapper).outputs: every output to try per src, for text requests and invoked actors alike. A one-element list is a canned output. Two or more fork one branch each, soonDonebranching on the output is explored down every arm. Each forked branch records{ type: 'xstate.done.actor.<id>', output }in the path.dedupeBy(snapshot): a key for settled snapshots, each explored once, so converging branches stay tractable.maxNodes(default 100,000) caps the branch points expanded, bounding work and memory on loopy machines wheremaxPathsalone cannot.hitPathCapis true when either cap cut exploration short.canReachresults carrycomplete. It is false when a cap ormaxDepthcut the search short, a branch stoppedneeds-output, or an event type was not fully expanded, soreachable: falseis a proof only whencompleteis true.#164
9da8d10Thanks @davidkpiano! -foldTracesturns many runs into per-state stats. From@statelyai/agent/testing:foldTraces(traces, { failed })takes one trace per run, live or serialized, and returns{ state, visits, failures, withheld }per state. Visits readmachine.transition'sfrom.valueandsnapshot.value;withheldsums a decision'srequest.withheldfrom its firstrequest.start.#161
730d64cThanks @davidkpiano! - Interactions can offer one button per payload, not only one per event type.meta.interaction.choices: a list of{ label, event, style? }, each carrying the full event it sends ("Seat 2" forACCUSE { seat: 2 }), or a context path string holding an array of the same shape, for choices computed at runtime.getInteraction(snapshot).choicesresolves either form, interpolates{context.path}in labels, and keeps only the events the schema andsnapshot.canaccept. Theeventslist is unchanged.eventFromInteraction(snapshot, { choice })returns a choice's event by index or label. A label several choices share throws and asks for the index.textEventalso takes{ type, field }to name the field free text fills;getInteractionreports it astextField. The bare form still fillstext.getInteractionandeventFromInteractionfall back to a plain XState machine'sschemas.eventswhen nosetupAgentschemas are registered.AgentInteractionChoice.#161
730d64cThanks @davidkpiano! - Decision attempts in the event log. The log now keeps the decision attempts the machine did not accept, on the entry they explain, so a decider can be improved from its traces:agent.decideinvoke that lands after failed tries records them on the delivered event's entry asentry.attempts.code: "decision-exhausted"andattemptson itsxstate.error.actorentry's normalizederror.decideNextpasses itsattemptswith the event:runToQuiescence(runtime, { event, attempts })(orrunAgent(machine, { event, attempts })), orruntime.transition(state, event, { attempts })in a host-owned loop.getDecisionAttempts(entries)in@statelyai/agent/loglists them as{ index, eventType, attempts }[].Replay ignores both fields, and logs written before them replay unchanged.
AGENT_EVENT_SCHEMA_VERSIONstays1:assertAgentLogEntryalready accepted unknown envelope fields, so older readers accept the new ones.#164
9da8d10Thanks @davidkpiano! -recordLog(actor)records a plain actor run. From@statelyai/agent/log: attach it to acreateActoractor beforestart(), andlog.entriesis an event log of the run (init entry, then every event the root machine processed, invoke outputs inline) thatreplayfolds with no actor executed. An actor created from asnapshotstarts its log from that snapshot.#152
cc5a7a6Thanks @davidkpiano! -runAgent. One call runs a machine to quiescence:runAgent(machine, { input, executors })isrunToQuiescence(createAgentRuntime(machine, options), options)with a single options object, and returns the sameAgentRunResult. Resume with{ snapshot: result.persist(), event }. A blocking host can passonIdle(snapshot) => Promise<event>to answer idle states in place.createAgentRuntimeandrunToQuiescenceare unchanged; write the loop yourself when the host is non-blocking, pushes events mid-run, or needs its own stop or checkpoint policy.#149
545fceeThanks @davidkpiano! - TanStack AI adapter.@statelyai/agent/tanstack-aiaddscreateTanStackAiExecutors, the{ generateText, streamText, decide }executor set over TanStack AI'schat(), alongside the AI SDK and OpenAI adapters. Any TanStack AI text adapter is a model:models,resolveModel, andsettingswork as increateAiSdkExecutors; a model entry can be{ adapter, settings }.maxSteps(ingenerateTextandstreamText), responsemessages, forced-tool decisions, usage summed over model turns, andAgentTruncatedErrorall follow the shared adapter contract.toolChoicemap ontomodelOptionsfor theopenai,anthropic, andgeminiproviders. For others, map them in asettingsfunction.@tanstack/ai(>=0.66.0 <1) is a new optional peer dependency.#164
9da8d10Thanks @davidkpiano! -machine.transitiontraces carryfrom.from: { value: StateValue }is the state value the event arrived in, emitted bycreateAgentRuntime/runAgentand bytraceTransitions, and kept byserializeTraceEvent. The first transition of a stream (the init event, or a restore on resume) has no earlier state, so itsfrom.valueequals itssnapshot.value.#161
48f6a9fThanks @davidkpiano! -verifyAgentConfig.@statelyai/agent/validateadds one call that runs every pre-run gate on a model-authoredAgentWorkflowConfigand stops at the first failure:validate(shipped JSON Schema),vocabulary(the actors, requests, named guards, actions, decision events, and model refs inallow),lower(setupAgent.fromConfig),lint(lintAgentMachine), andexplore(explorePathsperexplore.inputsentry, every path ending inexplore.terminals). Withallowgiven, a raw{ "@expr": ... }slot anywhere in the config failsvocabulary, since xstate would evaluate it in place of the literals the lists check.explorealso fails on an emptyexplore.inputs, on amaxPathsormaxNodescap that cut exploration short, and onunexpandedEvents(event types whose payloads were not all tried; addpayloadsentries). Otherexplorekeys pass through toexplorePaths; script text requests and invoked actors withoutputs, nottextorinvokes, as forexplorePaths(see the breaking note there). It never throws on a bad config. Each gate is{ name, ok, detail? }, anddetailis a model-readable repair prompt.#165
9858657Thanks @davidkpiano! - Require XState6.0.0-alpha.69(was6.0.0-alpha.64) as both the peer and dev dependency.Breaking: XState
alpha.69changes what your code and your stored data see:serializeMachineis nowmachineToJSON,createMachineFromConfigis nowmachineFromJSON, andExecutableActionObjectFromLogicis nowExecutableEffectFrom.timersas an array, andversionmoved tomachine.version.xstate.initandxstate.stop(were@xstate.initand@xstate.stop). The agent runtime's own init event inonTransitionand the event log is still@xstate.init."xstate.error.actor.*"is no longer a validonkey. Use"xstate.error.actor".timers: { schedule, cancel }) still receives{ id, delay }, butidis now an opaque JSON string. Pass it back unchanged as{ type: "xstate.timer", id }.Patch Changes
#147
9d32376Thanks @davidkpiano! - Move judgments to the AI SDK's non-deprecated decision API (ai7.0.133,@ai-sdk/typesafe-ai3.0.16):experimental_evaluate→experimental_decideExperimental_EvaluationModel/Experimental_EvaluationQuestion→Experimental_DecisionModel/Experimental_DecisionQuestiontypeSafeAi.evaluationModel("jev-latest")→typeSafeAi.decisionModel("jev-latest")doDecideinstead ofdoEvaluate. The SDK now hands a model itsstateas parts, so a plain value arrives as[{ type: "json", value }].#166
126603fThanks @davidkpiano! - The AI SDK adapter passes provider-defined tools such asopenai.tools.webSearch()through unchanged. Before, it rebuilt them as plain tools without theirid, so the provider never ran them.#161
730d64cThanks @davidkpiano! -canReachnow returns a shortest witness. It searches breadth first by path length, so the witness has the fewest events of any path withinmaxDepth, with each recorded invoke outcome (xstate.done.actor.*,xstate.error.actor.*) counted as one event. A retry loop no longer pads it.completekeeps its meaning,dedupeBystays sound,maxNodesbounds the breadth-first frontier, andexplorePathsstill walks depth first with an unchanged report.#163
2847e22Thanks @davidkpiano! -examples/code-review-loop: a review that approves a patch but still flags anovelissue now goes to a person instead of shipping.checkingShipenforces the same rule, so thecanReachproof covers it.docs/from-a-loop.mdgains Part 2, which ports the software-factory loop inexamples/code-review-loop/before.tsto the machine and improves its triage from traces.#159
4e68ca8Thanks @davidkpiano! - Fix:agent.decidehonors the invoke input'smaxRetries.createAgentRuntimeandsimulateAgentused the builtin decision logic's fixed budget of 2 retries and ignoredmaxRetriesset in anagent.decideinvoke's input. The lowered request now carries the input'smaxRetriesand both hosts prefer it, falling back to the logic's configured value (socreateDecisionLogic({ maxRetries })behaves as before).#160
aa875e7Thanks @davidkpiano! - Void async actors journal. An invokedcreateAsyncLogicactor whoserunresolvesundefined(ornull, which XState'sdata ?? outputcoalesce turns intoundefined) no longer makes the event log throwNonSerializableAgentEventErroron itsxstate.done.actorevent. The journaled entry omits theoutputkey instead; replay folds it identically.#153
45d6383Thanks @davidkpiano! - Events declared with the{}shorthand insetupAgent({ events: { APPROVE: {} } })now convert to an empty-object JSON Schema when offered to a model. Listing one underagent.decide({ allowedEvents })withcreateAiSdkExecutorspreviously failed with "Standard schema vendor 'statelyai-agent' does not support JSON Schema conversion."#161
4672646Thanks @davidkpiano! -explorePathsandcanReachfollow a decision that cannot succeed the way a live run does.{ type: 'xstate.error.actor.<id>' }and takes the invoke'sonError, or ends in anerrorterminal without one. It used to end without a branch, socanReachcould report a state behind thatonErroras unreachable while a live run reached it.errorsentry for a decision src, the failure and the candidate events are sibling branches, each one level ofmaxDepthdeep. The candidates used to spend two levels, so a path withinmaxDepthcould stop asmax-depth.idleterminal and lists those types inunexpandedEvents, socanReachreportscomplete: false. The path used to end with nothing recorded, which could make an empty payload table look like a proof.payloadsentry of[]for a type a wait accepts lists that type inunexpandedEvents, because its branches were skipped.#149
545fceeThanks @davidkpiano! - Tested against the latest integration packages:ai7.0.133,@ai-sdk/openai4.0.89,openai7.30,@anthropic-ai/sdk0.132, LangChain 1.5/1.6, Mastra 1.75, Braintrust 3.37, and TanStack AI 0.66. Peer ranges are unchanged. The package is now built with TypeScript 7 and tsdown 0.23.#162
1e46eb7Thanks @davidkpiano! - Parallel invokes replay from the store. A run whose parallel state invokes two or more actors no longer records a state hash that its own replay cannot reproduce. When a second child settled before the first child's completion was delivered, the hash on that completion's entry included the second child asdone, while replay (which never starts children) holds it active, so resuming the thread from the store alone threwAgentReplayDivergenceError. Each entry'sverification.stateHashis now taken from the pure fold of the log, the statereplayreaches. Logs recorded without that race keep the same hashes; a log already recorded with the race still fails verification and can be resumed with itssnapshotcache.#151
c8f68ceThanks @davidkpiano! - Two more examples.examples/rank-candidatesranks a list against a brief: Jev scores each candidate and judges near-duplicate pairs, while code applies the threshold, sort, per-bucket quota and top-k cut-off, with a reason for every cut.examples/answer-reusejudges the help article a search already found before spending an agent call, investigates at most once, and settles idle for a person when the answer still does not pass.#162
d99266dThanks @davidkpiano! - Two more environment and policy examples.examples/code-review-loopkeeps the review loop fixed: generate, then a self-check and an independent review in parallel, then checks, then ship, retry or ask a person.canReachproves it never ships without passing checks and approval. A JSON triage policy decides between retrying and escalating, and it is improved from event-log traces against hidden tests. It runs on Claude Code in headless mode.examples/tic-tac-toeis a plain XState game that knows nothing about AI, with the agent playing O at its boundary throughdecideNext. A JSON policy over low-level board features climbs from "first empty cell" to a policy thatcanReachproves never loses to any opponent.@statelyai/agent-demo@0.0.1-alpha.17
Patch Changes
#147
9d32376Thanks @davidkpiano! - Move judgments to the AI SDK's non-deprecated decision API (ai7.0.133,@ai-sdk/typesafe-ai3.0.16):experimental_evaluate→experimental_decideExperimental_EvaluationModel/Experimental_EvaluationQuestion→Experimental_DecisionModel/Experimental_DecisionQuestiontypeSafeAi.evaluationModel("jev-latest")→typeSafeAi.decisionModel("jev-latest")doDecideinstead ofdoEvaluate. The SDK now hands a model itsstateas parts, so a plain value arrives as[{ type: "json", value }].Updated dependencies [
a6d05dc,9d32376,126603f,730d64c,2847e22,4e68ca8,48f6a9f,9da8d10,48f6a9f,48f6a9f,aa875e7,45d6383,730d64c,4672646,48f6a9f,9da8d10,545fcee,730d64c,730d64c,1e46eb7,c8f68ce,9da8d10,d99266d,cc5a7a6,545fcee,9da8d10,48f6a9f,9858657]:@statelyai/example-cloudflare-agent-host@0.0.1-alpha.17
Patch Changes
a6d05dc,9d32376,126603f,730d64c,2847e22,4e68ca8,48f6a9f,9da8d10,48f6a9f,48f6a9f,aa875e7,45d6383,730d64c,4672646,48f6a9f,9da8d10,545fcee,730d64c,730d64c,1e46eb7,c8f68ce,9da8d10,d99266d,cc5a7a6,545fcee,9da8d10,48f6a9f,9858657]:@statelyai/example-cloudflare-workers-ai-host@0.0.1-alpha.17
Patch Changes
a6d05dc,9d32376,126603f,730d64c,2847e22,4e68ca8,48f6a9f,9da8d10,48f6a9f,48f6a9f,aa875e7,45d6383,730d64c,4672646,48f6a9f,9da8d10,545fcee,730d64c,730d64c,1e46eb7,c8f68ce,9da8d10,d99266d,cc5a7a6,545fcee,9da8d10,48f6a9f,9858657]:@statelyai/example-next-host@0.0.1-alpha.17
Patch Changes
a6d05dc,9d32376,126603f,730d64c,2847e22,4e68ca8,48f6a9f,9da8d10,48f6a9f,48f6a9f,aa875e7,45d6383,730d64c,4672646,48f6a9f,9da8d10,545fcee,730d64c,730d64c,1e46eb7,c8f68ce,9da8d10,d99266d,cc5a7a6,545fcee,9da8d10,48f6a9f,9858657]:@statelyai/example-tanstack-ai-stream@0.0.1-alpha.17
Patch Changes
a6d05dc,9d32376,126603f,730d64c,2847e22,4e68ca8,48f6a9f,9da8d10,48f6a9f,48f6a9f,aa875e7,45d6383,730d64c,4672646,48f6a9f,9da8d10,545fcee,730d64c,730d64c,1e46eb7,c8f68ce,9da8d10,d99266d,cc5a7a6,545fcee,9da8d10,48f6a9f,9858657]: