8000 WIP: refactor(agent) runtime around event-sourced protocol boundaries by Fodesu · Pull Request #47 · felinics/twilight · GitHub
[go: up one dir, main page]

Skip to content

WIP: refactor(agent) runtime around event-sourced protocol boundaries - #47

Closed
Fodesu wants to merge 461 commits into
felinics:feat/sdk-step-primitivesfrom
Fodesu:feat/agent-runtime
Closed

Fodesu wants to merge 461 commits into
felinics:feat/sdk-step-primitivesfrom
Fodesu:feat/agent-runtime

Conversation

@Fodesu
@Fodesu Fodesu commented Aug 28, 2026 •
Copy link
Copy Markdown
Member

Summary

see spec

@Fodesu
Fodesu force-pushed the feat/agent-runtime branch 2 times, most recently from 6dc5728 to ffe9493 Compare August 29, 2026 06:37
8000
@Fodesu Fodesu changed the title refactor(agent) runtime around event-sourced protocol boundaries WIP: refactor(agent) runtime around event-sourced protocol boundaries Aug 31, 2026
@Fodesu
Fodesu force-pushed the feat/agent-runtime branch from c30f579 to 258b850 Compare September 9, 2026 06:40
…e at Dispatch

executor.Backend (Validate/Prepare/Start/Attach/Status/Outcome/Cancel,
addressed by Ref) replaces the Port+BindingPort pair the Worker drove.
Dispatch evaluates the Route table once, prepares the Ref and persists
ExecutionRef{Provider, Ref} with the record before Start; every later
operation resolves record -> provider -> backend. The eight checkBinding
branches and the bound/unbound double path are gone; a record naming an
unknown provider is ErrUnknownProvider and is left untouched. The store
field is ExecutionRef (legacy alias dropped); PortBackend adapts
existing Port-shaped executors until they implement Backend directly.
… runs through Worker

loop.LocalExecutor now implements the Backend contract -- Prepare
derives the Ref from the AssignmentKey, Start/Attach/Status/Outcome/
Cancel address the Ref -- and no longer implements effect.Port. It
stays in loop because loop's tests drive it and executor/local imports
loop; executor/local exposes it as a Backend with Provider "local" and
a Route helper. app.Build's local mode composes executor.NewWorker over
Config.Executions (in-memory by default), so colocated and remote
deployments share one record lifecycle (RUN-EXE-8). MemoryStore bounds
its terminal records (RetainTerminal, default 1024). runtest, the loop
test helper and the authority/compact tests wrap the backend in a Worker.
spawn.Executor implements executor.Backend: Prepare derives the child
SessionID as Ref, Start drives the child, Attach adopts a child that
exists while no drive of it runs here, and Status/Outcome/Cancel address
the Ref. spawn.Intercept and its twelve forwarding methods are gone;
app.Build routes the spawn tool's Assignments to it beside the default
backend (local, a supplied Port, or the remote client). The Worker
stamps the record's key on every Outcome, since backends know Refs only.

Adoption after an owner dies is now the control plane's takeover of an
orphaned record; the restart test shares a file record store and runs
the second process's clocks ahead so the first lease reads as expired,
and the spawn drive waits through waiting_for_recovery while the
recovery lifetime delivers the adopted Outcome and drives the Run on.
The physical binding of an attempt is the Worker's ExecutionRef,
persisted in its record and handed to Backends as a Ref; nothing in
Agent Core's effect protocol names it any more. effect.Port keeps its
six AssignmentKey-addressed methods.
…rged

agent-run.md §6 lists the Backend contract, Route and ExecutionRef in
place of BindingPort; RUN-EXE-1/3/6/8 carry the record -> provider ->
backend lifecycle, and RUN-EXE-9 (ExecutionRef) and RUN-EXE-10 (backend
chosen once, record is the only source of execution identity) are new.
agent-runtime.md SPN-1/2/4 describe spawn as a routed Backend and
adoption as control-plane takeover of an orphaned record; workspace and
test-cloud-agent wording follows. The revision draft is deleted.
Fork spans two consistency domains (Session store, artifact ledger).
It now activates the prefix retention claim first and creates the child
root second, releasing the claim on a definite Create failure; a crash
between the two leaks a claim instead of a child whose inherited bodies
can be collected. Delete treats a missing root as already done so a call
that failed after the root was dropped can be repeated to release the
remaining claims. EXT-WRT-8/9 revised; EXT-PRJ-8 (projection inheritance
policy) is specified here and implemented in the next commit.
A child's ancestry still reads the parent's whole commit prefix, but the
streams in it mean different things: the session stream is inherited,
run streams belong to the segment that wrote them. ReadStream returns
only the child's own events for non-session streams; projections
declare an InheritPolicy (default: session batches only from inherited
commits; chatlog and turn surfaces opt into InheritAll because their
content is projected from run facts). Authority.Fork refuses a point
where the parent has an active Turn (History.ActiveAt). An inherited
attempt reads as finished from the surface; its Run is ErrRunNotFound.
History no longer uses CommitSeq 0 as a not-found sentinel.
SES-FRK-5, EXT-PRJ-8, AUTH-FRK-1; the SPN-4 Close sentence lands with
the Worker commit that follows.
Worker owns its goroutines: a cancellable lifecycle and a WaitGroup
cover the reconcile loop, heartbeats and watchers; Close stops and
waits, leaving records leased for the next incarnation.
Application.Close closes the Worker Build composed.
Backend.Prepare stays idempotent by AssignmentKey; the new
Backend.Restart(previous, a) allocates the next generation's Ref when a
takeover finds the previous execution missing. The superseded
ExecutionRef is kept on the record (Superseded) instead of overwritten.
LocalExecutor derives a #generation suffix; spawn keeps the child Ref.
RUN-EXE-3/8/9 revised.
runmod.Config.Attachers lets another module add its session-stream
facts to the group that records a command's Run facts, from the
Writer's view (RUN-CMT-9). turn.AttemptEnder writes
twilight/turn/attempt_ended{TurnID, RunID, Attempt, End} after
run_ended and before the caller's Attach. The turn surface now folds
the session stream only: run_ended leaves its Consumes and Requires,
and its Inherits returns to the default. run streams are execution
history that neither a fork nor the surface depends on. This revises
the 2026-09-17 rule that attempt outcomes are projected from run facts
without a Turn-side fact. TRN-EVT-1/3, TRN-PRJ-1, TRN-SCP-1 revised.
…one unit of work

agent/run speaks only Run types: RunStore is the port bound to the caller's
write capability (Bind), Scope replaces SessionID, RunPosition is its own
type, the envelope names no store, and Protocol splits into Schema =
Machine + WireSchema + Canonical + SnapshotCodec with one variant registry.
agent/session/unit is the single cross-module transaction coordinator:
Turn, chatlog and Run each contribute a Part; CommitRequest.Attach is gone.
agent/run/reconcile owns takeover: it compares Executing targets with the
execution store and produces Run commands; Loop/Driver/store no longer
interpret executor observations. The machine projection drops Ended
(creation is guarded by the kernel's stream index via View.StreamHead),
turn RunOwner is bounded to active Runs, FrozenValues registers Bindings
itself, effect.Port -> ExecutionPort, executor.Backend -> ExecutionBackend.
…end with the Run

A fold stamps every DecodedEvent with its session.Position (commit seq and
event index in batch order), so the chatlog projections record the position
of the event that produced an entry instead of keeping a counter that a
restored snapshot had to rescan for. CheckpointCreatedPayload.CoveredThrough
is that Position (pre-release wire change; chatlog golden updated). Both
chatlog projections consume run_ended and forget the Run's Turn there, so
Surface.Runs and Context.Runs are bounded by the Runs active now, like the
turn surface's RunOwner.
…untime -> agent/environment

The Writer keeps only the kernel handle, the head, the fence and the
CommitID/stream index. Encoding and stream affinity (encode.go), artifact
admission and retention claims (admission.go), transactional projection
folding and the projection cache (projector.go), observer fan-out
(observers.go) and the replay fingerprint (fingerprint.go) are stages
composed onto Commit, each holding its own state. Behaviour is unchanged.
agent/runtime, the physical environment materialization boundary used by
workspace, is renamed agent/environment so 'runtime' no longer names four
different things.
…sitions

agent-run.md: section 5 describes run.RunStore / Scope / RunPosition, the
Schema aggregate with its four contracts and the single variant registry,
SessionRunStore.Bind and the Run Parts, unit.Commit as the only cross-module
coordinator, the machine projection without Ended (creation guarded by
View.StreamHead), RUN-CMT-3 as a Part, and reconcile.Reconciler in place of
Reattacher/RecoveryDisposition. agent-turn.md, agent-runtime.md and README
follow; agent-session-chatlog.md describes ledger Positions in place of the
projection counter; agent-session-extension.md adds View.StreamHead.
…hema

Schema gains Identity (every RunID-scoped derivation) and Bodies (the
frozen-body envelope codec); Canonical gains DigestToolCallBinding. The
package-level Derive* functions and EncodeFrozen* are gone, so nothing
persisted is derived outside the Run's own schema version: the run store
freezes bodies under the command envelope's SchemaVersion instead of a
hard-coded V1, decoding reads the version from the stored envelope, and
the chatlog remembers each Run's schema (RunOwner) so materialization
derives CallIDs under it. DeriveTakeoverClaim stays owner-level.
AgentInput is {ID, Digest}: InputAccepted and PendingInputs no longer copy
the input content that input_submitted already holds (RUN-WIR-4), so an
input body exists once in the ledger and edit/fork never touch a second
copy. chatlog.Commands.Submit returns the digest, DeliverInputs compares
digests inside the unit, Retry replays digests from the surface. Decide and
CreateGroup reject an input without a digest. Pre-release wire change to
input_accepted; the schema golden is updated.
…ce per stream

A projection commands plan against (run machine, turn surface, chatlog
surface and context) is Authoritative: its fold refuses a commit it cannot
fold. Every other projection is a derived read model: a fold failure marks
it unhealthy for the Writer's lifetime (reads report
ErrProjectionUnhealthy, the cache is not refreshed for it) and never blocks
the facts, so one extension cannot brick a Session (EXT-PRJ-9). Write-time
invariants belong to unit-of-work Parts. InheritPolicy is now a per-stream
predicate (InheritSemantic, InheritAll, InheritStreams) instead of a
two-valued enum keyed on the physical stream kind.
…ifacts, DriveResult, drive budgets

reconcile: an Outcome read that the executor answers definitively
(ErrExecutionNotFound, the new effect.ErrOutcomeUnavailable, which
executor.ErrUnknownProvider wraps) stops the watcher and reports through
Fail; transport failures retry up to ReadRetries; only not-ready waits
without limit. Driver reports through Ports.Fail.
writer.Fork: an event type the registry does not know, or a binding-bearing
payload it cannot decode, refuses the fork instead of assuming it references
nothing (EXT-WRT-8 fail closed).
authority: a durable Store or Content Store with in-memory Bindings/Ledger is
refused unless Artifacts.Ephemeral opts in.
driver: Drive returns DriveResult{TurnResponse, AlreadyDriving}; the
already_driving disposition no longer extends the Turn vocabulary.
app: RouteRetries and DrainBudget replace the literal 4 and 64; exhausting
the drain budget is ErrDrainBudget with the Results so far.
Assignment carries one Body (ModelAssignment | ToolAssignment); Kind is
derived, and the JSON shape the execution store persists and the HTTP
protocol carries is unchanged, with decoding refusing a kind that does not
match its body. Outcome carries one Result: ModelSucceeded, ModelFailed
{Code, Message} with wire-stable codes, ToolExecutionSucceeded/Failed/
Unknown, Cancelled or Unknown; there is no Go error and no flag pair that
can disagree. Outcome.Status derives the ExecutionStatus in one place; the
protocol envelope maps to and from the variants.
…fork fail-closed, durability and drive vocabulary
…ck reopen; durability is one bundle

session.Commit gains Intent, the digest of the operation that produced it,
covered by the commit digest (SES-APP-4). writer and unit judge a replayed
CommitID by intent when both sides declare one -- same is already applied,
different is a conflict -- without preparing a Part, so a second settlement
of the same attempt with another result is ErrCommandConflict as RUN-CMT-5
promised; Turn commands declare their intents too.
projector.rebuild folds a derived projection to its last good commit and
marks it unhealthy instead of refusing OpenWriter (EXT-PRJ-9 across reopen).
authority requires the whole durable bundle (content, bindings, ledger) next
to a durable Store unless Artifacts.Ephemeral. wireV1 speaks through its
own variant registry; Authoritative is reserved to twilight modules.
…en, authoritative capability, durability bundle
Wrap both errors with %w, check type assertions on projection states,
rename shadowed builtins, drop the always-nil error of encode, convert
NewRun to RunCreated directly, and guard the binding cardinality compare.
… intent, per-wire fact naming

authority.Durability: stores declare Durable(); memory implementations say
false, filestore says true, silence counts as durable, so an explicit memory
binding store or ledger cannot slip into a durable bundle.
extension.BuildRegistryWithExtensions: trust is how a module reaches the
registry; extensions may not declare Authoritative nor use SourceTwilight.
unit.Work.Intent is required; a replay of a commit sealed without an intent
is a conflict, never a silent replay. factCodec validates through its own
WireSchema, FoldRun names facts through the Run's schema, and FactTypes is
the union over every schema's registry.
Fodesu and others added 28 commits October 1, 2026 18:52
Watch replaced the callbacks of a key already registered, so when two
callers awaited the same effect -- a caller's settlement and the
background step reaching the same compaction, which derives its key from
the context it replaces -- only the later one was ever answered and the
first waited for good.

Each key now holds every registration and the probe state they share: one
read, one probe, and the answer reaches all of them; cancel drops one.
…; Loop.Run is gone

The Loop steps a Run: Advance commits the next transitions and dispatches,
Deliver settles an Outcome; it waits on nothing. The Engine awaits every
dispatched key on the shared Watcher under the Session's lifetime, settles
the Outcome through the Loop off the Watcher's goroutine and reports it as
Notify; a takeover's kept targets join the same table, Detach cancels what
is still awaited, and a fenced settlement refuses every later step.

Drive returns one step's DriveResult (Dispatched, InFlight, Waiting,
Finished, AlreadyDriving); an answered wait continues in the same step. A
tool step entered again honours its scheduling window. The Controller's
Advance is one step. Session.Send, Settle, Resume and Wait keep the
settled contract at the product edge by waiting on the Session's event
stream between steps; Owned.Drive does the same for a bare Handle.
… mechanics in the Controller's answers

The Controller reported InFlight and AlreadyDriving -- the engine's await
count and the Loop's lock state -- and the host rebuilt from them whether
to keep waiting, with a timer for the lock case. TurnProgress now carries a
Standing: Ended, Carried (this process awaits its effects; a notice
follows) or Blocked (it waits on something not here). Submit's absorbed
answer is Absorbed.

A Run takes one step at a time: Loop.Advance waits for a step in progress
instead of refusing with ErrRunAlreadyRunning, so a concurrent step is no
longer a state anyone reports. ResumeWaitingForRecovery is ResumeExecuting:
it describes the Run, and who carries the wait is the reader's knowledge.
…es progress itself

The Loop emitted committed observations (agent_committed, tool_started,
tool_completed, run_finished) on an EventSink that every host discarded:
the committed stream already carries them from the Writer. The sink, the
Event vocabulary and the serialization behind them are gone; Advance and
Deliver take no sink. The executor's progress frames, the one provisional
observation, are published by the engine to the transient stream as it
awaits each effect, under the frame's own kind.

LoopResult loses ExecutionRecovery and Reason: Executing lists the effects
a waiting Run has in flight, and whether this process awaits them is the
host's knowledge.
effectalias.go re-exported effect's assignment, outcome, status and
error vocabulary under the loop package, so every user of the Loop
imported two spellings of one type. The aliases are deleted: loop and
its callers name effect.AssignmentKey, effect.Outcome,
effect.ToolExecutionOutcome and the rest directly, and loop.Executor
is effect.ExecutionPort. Loop keeps only its own API: Settings,
TargetResolver, EffectContext, DispatchPolicy, LoopResult and
ErrModelUnavailable, which moves to contract.go.

The Loop's duplicate Executor field is dropped; Ports.Execution is the
single port. Deliver no longer carries step and call IDs out of the
switch that reads them.
… adapter

SessionRunStore.RecoverInterrupted iterated the active Runs and called
back into a sessionstore.Reconciler interface, so the storage adapter
carried the takeover policy's loop and an interface only execution
implemented. The adapter now offers one read, ActiveRuns: the snapshot
of every active Run of the Session from the Writer's projection.

execution.recovery.Recover walks those snapshots itself and reconciles
each under the Session's lifetime; the sessionstore.Reconciler interface
is gone. reconcile gains ReconcileAll, Reconcile over many snapshots,
which the conformance suites and the loop tests use through a harness
helper in place of the removed method.
…e only decides

effect is the port vocabulary; the Watcher is a client over it, so it
moves to effect/watch (watch.Watcher, DefaultPoll/Reconnect/Probe). Its
implicit port discovery through PortsOf on first Watch is gone: whoever
builds a Watcher names its Settlements and Recover ports.

reconcile.Reconciler loses Deliver, Fail, Watcher and Lifetime: a kept or
deferred target stays Executing and waiting on its Outcome is the
caller's, which execution's recovery already does under the Session's
lifetime. The four Watcher tests that lived in reconcile become watch's
own; the loop takeover tests wait through a Watcher directly.
…pe, not a store

PrepareHook received the Loop's bound RunStore so that execution could
pull the Session's Writer back out of it through an anonymous interface
on sessionstore's bound store. The hook now receives the Run's Scope an
BD0
d
the input; execution finds the Writer of that Session among the
lifetimes it owns (recovery.writerOf) and hands it to the Planner.
bound.Writer() and the type assertion are gone.
The Writer already reports a superseded owner as a ledger.Error with
CodeOwnershipLost; above it, sessionstore wrapped that into
store.ErrOwnershipLost, turn.Coordinator wrapped it again, and
execution and loop each kept a predicate over two or three spellings.
ledger.IsOwnershipLost is now the single predicate: store.ErrOwnershipLost,
sessionstore.ownershipError, the Coordinator's remap, responders'
lostOwnership and loop's ownershipLost are deleted, and the lifetime's
fence is a ledger.Error itself.
plan.Next built decision.Input, so the Run core's plan imported the
decision layer above it for one struct of Run facts. The struct is now
run.PromptInput; decision.Builder, loop's PrepareHook, execution's
Planner and the prompt builder take it, and plan imports run alone.
run.CanonicalJSON was an alias of jsonstable.Value with three wrappers
over jsonstable's constructors, so the same value had two names and
packages that needed only the JSON type imported the Run core for it.
The alias and wrappers are deleted: every field, parameter and test
names jsonstable.Value, Parse, MustParse and FromValue directly, and
seven packages drop their run import.
…n/lifecycle

session/lifecycle imported turn and chatlog for History, the fork guard
that refused a point inside an active Turn, and the ForkBeforeTurn and
ForkBeforeInputs variants. Lifecycle now knows no module's facts: Fork
takes a commit and forks there.

History is turn.History, with BeforeStart and BeforeInputs as the fork
points and RequireNoActiveTurnAt as the precondition; the Application
composes them with Lifecycle.Fork, so app.Fork guards every fork and the
two Turn-relative forks are app-level compositions.
…umer

turn.Commands and turn.Reader existed only to be embedded by
conversation.Turns. The Controller now declares the four methods it
needs itself and turn keeps the Coordinator alone.
…es them

GetOutcomeEnvelope handed the HTTP binding the stored wire envelope so
the Worker carried a method shaped for one transport. The server now
reads the Outcome through GetOutcome and renders it with
protocol.EncodeOutcome, the same codec that stored it.
Consumers told a committed Event from a progress frame or a background
failure by which fields were set. Event carries Kind (committed,
progress, failed), the producers set it, and the HTTP wire and the
Session's wait read it instead of probing Err.
… its own

Engine.Presets and Engine.Progress handed the host the preset registry
and the transient stream the Engine had created, so the application
registered presets and published failures through its Engine. Both are
now the application's: it creates them, keeps them as Presets and
Progress, and passes them in Config; Config.Presets is required and
Config.Progress is where the Engine publishes.
Nothing in agentcore reads or writes a checkpoint.Store; only the
agent's SQLite and Postgres adapters implement it and the component
stores expose it. The package moves to agent/checkpoint with its
conformance suite.
Application carried twelve flat fields for the services composeSessions
builds over one Store. They are now SessionServices, built once and
embedded, so the Application reads as its three parts: the Session
services, the Owner with the Engine, and the registry and stream it
hands the Engine.
…knows Runs, not Turns

A model step's scheduling and malformed-result retries came from the
preset through the Loop at settlement time, so settling a Run needed its
preset, and the Engine found the preset by reading the Turn surface: the
execution layer imported turn for one lookup.

run.StepPolicy is frozen onto the step at Prepare (model_step_prepared
writes version 2 and keeps the version 1 codec; an absent policy reads
as zero). SubmitModelResult drops Scheduling, RejectModelResult drops
Disposition: Decide reads both from the step. The Loop is one per
deployment: Advance takes the Run's Builder, Deliver and Redispatch need
no preset. Engine.Drive takes (RunID, PresetRef); the Controller and
Owned read both from the Turn they drive. execution no longer imports
turn, and the per-preset loops table is a builders table.
Nothing has shipped, so the step policy joins the fact's one and only
wire shape; the version 1 codec kept for a policy-less shape, and the
superseded codec type that served it, are removed. Every fact type is at
version 1 with one codec again.
Whether the wire shapes the registry writes are committed is a property
of the whole ledger format, not of any one event type's version number,
and nothing has shipped yet. module.Prerelease says so in one place:
while it is true a shape changes in place at version 1 and no event
type carries a codec history. module.NoHistory checks a descriptor for
that, and the sessionstore and app tests hold every first-party module
to it until the constant is turned off at the release.
PayloadVersion was a bare integer, so nothing said whether a shape was
still being iterated or was a release's commitment. It is now a
two-track value written as `v` in its wire form: "pre.N" is a prerelease
version, where a shape changes in place and the number moves so an
earlier build's payload reads as Unknown rather than misread, and a type
has one prerelease codec at a time (the registry refuses a prerelease
version beside any other codec); "N" is a stable version, which keeps a
codec for every stable version before it. Every prerelease version
orders before every stable one.

The run, chatlog, turn and workspace modules write pre.1. The
module.Prerelease constant and its guards are replaced by this.
… is explicit

Three structured error types shared one idiom but not one meaning:
ledger.Error carried the Writer's conditions (ownership_lost,
unknown_outcome, projection_unhealthy) beside the registry's, the
Writer rewrote session codes into ledger codes, turn built
session.Error values for its fork guards, and callers matched by
constructing a target value, with session's Is also comparing the
Operation.

Now each layer owns what it alone can report and translates at its
edge: ledger keeps invalid/unknown_event/codec with constructors and
Is predicates; writer declares ErrOwnershipLost, ErrUnknownOutcome,
ErrProjectionUnhealthy and ErrClosed as its own and wraps the kernel's
report; sessionstore maps writer.ErrOwnershipLost to the Run port's
store.ErrOwnershipLost, which loop and execution match; turn reports
ErrNoForkPoint. session.Error and artifact.Error keep their types and
codes, gain named predicates (IsNotFound, IsOwned, ...) over errors.As,
and lose their Is methods; every match goes through a predicate.
…g alone

A Port beside a Sandbox left the Worker with the sandbox route alone:
model effects had no route and the Turn failed. The Port is the
Worker's default route again (executor.Default over PortBackend), as
Config.Port promised.

Compose refuses a prefilled Execution.Executor instead of silently
skipping the Worker, and refuses Config.Executions when no Worker is
composed, so a store nothing reads is not mistaken for one in use. The
sandbox backend joins a copy of the caller's WorkspaceConfig rather
than being written into it, so a Config composed twice yields two
agents with their own backends. Agent documents that its Close, not
the inner application's, releases the Worker and the sandbox backend.
Tests cover the Port+Sandbox route, the refusals, the untouched Config
and the close of both backends; two app comments stop naming the
deployments above them.
SessionServices was embedded and exported, so every service the
application composed -- Lifecycle, Writers, Turns, Chatlog, Runs -- was
reachable beside the methods that guard them: app.Lifecycle.Fork went
around app.Fork's quiescence check, app.Lifecycle.Delete around
DeleteSession's close-and-acquire, Writers around the Owner, and
Session.Handle() handed any holder of a Session the Writer to commit
Turn protocol beside the Controller. Owner, Execution, Presets and
Progress were exported the same way.

Every one of them is now unexported; what the application admits is its
method set. Reads the services alone provided gain methods: TurnStatus,
RunRecord and Materialize on the Application, Queue and Withdraw on the
Session. Owned keeps its Handle: driving through the Handle is what
Acquire is for.
The tests reached the Writer through Session.Handle and committed Turn
protocol beside the Controller; they now submit, stop and withdraw
through the Session and read Runs, Turn status and entries through the
Application's read methods. The stores a test inspects directly are the
ones it built.
DriveForTest and the Driven wrapper reproduced a host loop in test code:
their own outcome polling, read-error classification and cancel handling.
The production primitives already cover it: Loop.Advance, Loop.Deliver and
watch.Watcher.Await.

Single-step tests now call the step they assert on. End-to-end tests go
through one small helper that composes only those three, and the feature
harness inlines the same loop. The mid-execution cancel test does what a
host would: Cancel the effect, then await and Deliver its Outcome under a
context of its own.

Co-Authored-By: Claude <noreply@anthropic.com>
agentcore/decision was named for a layer that no longer exists: the
policy catalog it once held became data on AgentPreset, and only the
prompt builder seam remained. The name was wider than the contents and
collided with reconcile.Decision and the commit verdicts.

agentcore/decision -> agentcore/prompt (Builder, Prompt, Catalog,
Sources, BuilderFactory, ErrUnknownBuilder); agent/prompt ->
agent/contextprompt (Builder, New, V1). Engine Config.Decisions is
Config.PromptBuilders. The V1 ref is "twilight/contextprompt/v1", so
the preset digest changes; the golden records the new digest. Comments
that said "decision identity" now say preset.

Co-Authored-By: Claude <noreply@anthropic.com>
@Fodesu
Fodesu commented Oct 4, 2026
Copy link
Copy Markdown
Member Author

Superseded by the merged Agent Core stack (#75–#109). This WIP branch targets the obsolete feat/sdk-step-primitives base and is no longer part of the current architecture.

@Fodesu Fodesu closed this Oct 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

0