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
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
461 commits
Select commit Hold shift + click to select a range
5bad50d
refactor(executor): Backend contract, ExecutionRef, routes chosen onc…
Fodesu Sep 17, 2026
faba175
refactor(executor): LocalExecutor is the colocated Backend; colocated…
Fodesu Sep 17, 2026
2058f4a
refactor(spawn): the subagent effect is a Backend routed by the Worker
Fodesu Sep 17, 2026
dfa72c0
refactor(effect): drop BindingPort and ExecutionBinding
Fodesu Sep 17, 2026
b2adc63
docs(run): RUN-EXE-3/8 revised, RUN-EXE-9/10 added; revision draft me…
Fodesu Sep 17, 2026
3188415
docs(run): conformance bullet follows the Backend contract
Fodesu Sep 17, 2026
f8f23a9
fix(writer): Fork claims before it creates; Delete is idempotent
Fodesu Sep 17, 2026
1819f18
fix(session): a fork inherits semantic history, not execution state
Fodesu Sep 17, 2026
e863c63
fix(executor): Worker.Close; Restart is a separate contract from Prepare
Fodesu Sep 17, 2026
c903ca9
feat(turn): attempt_ended is the Turn's own fact, written with run_ended
Fodesu Sep 17, 2026
ceade91
Revert "feat(turn): attempt_ended is the Turn's own fact, written wit…
Fodesu Sep 17, 2026
a59bd5f
docs(turn): why no Turn-side mirror of run_ended
Fodesu Sep 17, 2026
51df3b1
refactor(run): decouple the Run core from Session and commit through …
Fodesu Sep 17, 2026
73a74dd
refactor(chatlog): entry positions are ledger positions; Runs tables …
Fodesu Sep 17, 2026
a20845a
refactor(writer): compose the commit as a pipeline of stages; agent/r…
Fodesu Sep 17, 2026
647660d
docs(design): RunStore, Schema, unit of work, reconcile and ledger po…
Fodesu Sep 17, 2026
b38bbc2
fix(run): identity derivation and frozen-body encoding are part of Sc…
Fodesu Sep 18, 2026
36b8a3f
refactor(run): an input has one body; the Run records id and digest only
Fodesu Sep 18, 2026
f4c815e
feat(extension): authoritative vs derived projections; fork inheritan…
Fodesu Sep 18, 2026
b5e1850
fix: recovery read taxonomy, fail-closed fork, explicit ephemeral art…
Fodesu Sep 18, 2026
7e30e8a
refactor(effect): Assignment and Outcome are sealed sum types
Fodesu Sep 18, 2026
246682d
docs(design): sealed effect types, input digests, projection health, …
Fodesu Sep 18, 2026
e0317f7
fix: commits seal an operation intent; derived projections cannot blo…
Fodesu Sep 18, 2026
a1d965c
docs(design): commit intent replay semantics, derived projection reop…
Fodesu Sep 18, 2026
56c91db
chore: lint fixes in the files this round touched
Fodesu Sep 18, 2026
0820bbb
fix: declared durability, trust as a build capability, mandatory unit…
Fodesu Sep 18, 2026
f793b57
chore: lint pass on real findings
Fodesu Sep 18, 2026
b414925
chore: lint: saturating page limits, unparam/prealloc fixes, lint pol…
Fodesu Sep 18, 2026
6be9cdd
chore: lint: remaining findings
Fodesu Sep 18, 2026
d793ecb
chore: lint policy by value size, not by rule; constants for provider…
Fodesu Sep 18, 2026
890dad6
feat: Schema per Session segment, explicit migration through a fenced…
Fodesu Sep 18, 2026
254f1f0
refactor(run): bind the machine to injected digest and identity rules
Fodesu Sep 18, 2026
033b52c
refactor(run): split agent/run by architecture layer
Fodesu Sep 18, 2026
d66cc1a
docs(run): describe the agent/run package layout
Fodesu Sep 18, 2026
a29f749
docs(session): state the lineage as a tree with a single-parent invar…
Fodesu Sep 18, 2026
5151676
refactor(run): two tiers under agent/run; protocol to executor, recov…
Fodesu Sep 18, 2026
acd6ae7
refactor(session): logical streams become module-declared domains
Fodesu Sep 18, 2026
393479d
refactor(run): effect identity replaces execution claim
Fodesu Sep 19, 2026
daf7931
refactor(run): settlement commands carry their effect; declines get t…
Fodesu Sep 19, 2026
ff3da97
feat(session): target module binds a Session-level target, resolved p…
Fodesu Sep 19, 2026
70a65d4
docs(turn): regenerate and edit fork the conversation only, not the w…
Fodesu Sep 19, 2026
d6c6e3a
refactor(session): target resolution leaves the core; the seam stays
Fodesu Sep 20, 2026
69b4e80
fix(app): no memory fallback for stores; execution records join the d…
Fodesu Sep 20, 2026
8d6cefb
feat(executor): tools declare replay; Reconcile bounds deferred with …
Fodesu Sep 20, 2026
c6b8004
refactor(loop): every tool declares its replay policy; unknown is the…
Fodesu Sep 20, 2026
8caf686
feat(run): the tool replay policy travels with the call and its Assig…
Fodesu Sep 20, 2026
9219dbc
fix: model Unknown withdraws to Open; Close stops the Worker; Attach …
Fodesu Sep 20, 2026
c41d9a0
refactor(session): payload versions belong to event types; segment Sc…
Fodesu Sep 21, 2026
4f079a4
feat(session): attempt module binds a Turn to the Runs that execute it
Fodesu Sep 21, 2026
eb3974f
feat(effect): execution policy unifies replay and retry; Worker retri…
Fodesu Sep 21, 2026
b308753
fix(executor): dispatch answers are Known, retryable or Unknown; HTTP…
Fodesu Sep 21, 2026
3ba54e2
refactor(effect): retryability is a property of each failure, not of …
Fodesu Sep 21, 2026
255dfb8
fix(effect): a model failure's disposition is derived, never stored; …
Fodesu Sep 21, 2026
b1c6719
feat(session): kernel Ext slots on header and commit; claims keyed by…
Fodesu Sep 21, 2026
3dd880c
feat(session): CommitIndex is a component of the segment (SES-REP-5)
Fodesu Sep 21, 2026
0eadf46
feat(session): kernel versions live on segments; Advance moves a Sess…
Fodesu Sep 21, 2026
0c281fe
feat(session): Open verifies from a per-segment verified mark; the wr…
Fodesu Sep 21, 2026
adac7ef
refactor(session): claims live with their segments; Collect releases …
Fodesu Sep 21, 2026
48d102f
feat(session): Session ownership is a lease; the Writer heartbeats it…
Fodesu Sep 21, 2026
41fe840
feat(app): compaction runs between the steps of a Turn (APP-CKP-1/2, …
Fodesu Sep 21, 2026
5c07423
feat(executor): progress frames flow from the backend through the Wor…
Fodesu Sep 21, 2026
6b4377e
refactor(spawn): the subagent waits for an external response the Driv…
Fodesu Sep 21, 2026
fb1fee5
refactor: the process that owns a Session is the Owner; authority nam…
Fodesu Sep 21, 2026
cbd1965
style: gofmt after the owner rename
Fodesu Sep 21, 2026
54657e1
fix(executor): missing is proven absence; settled records are collect…
Fodesu Sep 21, 2026
889f10d
refactor(store): every store is durable; SQLite holds records, bindin…
Fodesu Sep 21, 2026
d37d8b6
refactor(sdk)!: one call per Generate; typed tool arguments, outputs …
Fodesu Sep 21, 2026
55fc0fb
docs(sdk): describe the single-call seam and the typed tool contract
Fodesu Sep 21, 2026
19f27a2
Merge feat/sdk-step-primitives: the agent tier adopts the typed SDK c…
Fodesu Sep 21, 2026
80cdea2
fix(sdk): response metadata is a value; a missing wire field stays empty
Fodesu Sep 21, 2026
476c134
Merge branch 'feat/sdk-step-primitives' into feat/agent-runtime
Fodesu Sep 21, 2026
f0621f4
refactor(model): response metadata mirrors the S 8000 DK value; schema roun…
Fodesu Sep 21, 2026
53c2e79
docs(sdk): describe the single-call seam and the typed tool contract
Fodesu Sep 21, 2026
8885ccf
Merge branch 'feat/sdk-step-primitives' into feat/agent-runtime
Fodesu Sep 21, 2026
35f7c0a
chore(mimo): name the pcm16 format constant
Fodesu Sep 21, 2026
b8e3600
Merge branch 'feat/sdk-step-primitives' into feat/agent-runtime
Fodesu Sep 21, 2026
2379a32
refactor(executor): the Worker recovers one execution on request; no …
Fodesu Sep 22, 2026
2aaef06
refactor!: agentcore/ is the kernel; agent/ is the reference agent bu…
Fodesu Sep 22, 2026
76eed83
refactor(sdk)!: the SDK stops at the tool definition; no executor, ap…
Fodesu Sep 22, 2026
88b5a5b
Merge branch 'feat/sdk-step-primitives' into feat/agent-runtime
Fodesu Sep 22, 2026
473c9ea
Merge branch 'feat/sdk-step-primitives' into feat/agent-runtime
Fodesu Sep 22, 2026
66e57dc
Merge branch 'feat/sdk-step-primitives' into feat/agent-runtime
Fodesu Sep 22, 2026
f0bbc64
Merge branch 'feat/sdk-step-primitives' into feat/agent-runtime
Fodesu Sep 22, 2026
5912912
refactor(executor)!: the execution store is a commit ledger; Executio…
Fodesu Sep 23, 2026
279749d
feat(observe): catch-up subscriptions from a CommitSeq; durable check…
Fodesu Sep 23, 2026
1b869a5
refactor(session,ledger)!: a commit does not record its writer's epoch
Fodesu Sep 23, 2026
f570b51
feat(process): the effect process ledger and its relay (RUN-EXE-15)
Fodesu Sep 23, 2026
beeebbe
refactor(session,ledger)!: history is immutable by store contract, no…
Fodesu Sep 23, 2026
63aded5
refactor(session)!: Ext is a module-keyed extension map
Fodesu Sep 23, 2026
4bf3572
refactor(session)!: no kernel wire version, no tip advance; spawn pro…
Fodesu Sep 23, 2026
c69169a
refactor(session,executor)!: four small simplifications of the ledgers
Fodesu Sep 23, 2026
21d95eb
refactor(extension)!: stream membership is a typed extractor, not a p…
Fodesu Sep 23, 2026
d3ac2c9
refactor(executor)!: the execution fold holds facts; the lease is rea…
Fodesu Sep 23, 2026
a9c3f78
refactor(run)!: the Run protocol has no version; its contracts are si…
Fodesu Sep 24, 2026
4521018
refactor(process,reconcile)!: the reconciler is the process manager; …
Fodesu Sep 24, 2026
2f66995
refactor(run)!: contracts are non-replaceable accessors; each fact ty…
Fodesu Sep 24, 2026
df2a8fa
refactor(run,chatlog)!: a Run does not know its Turn; settlement fact…
Fodesu Sep 24, 2026
d1179eb
refactor(run)!: step identities derive from their cause; the binding …
Fodesu Sep 24, 2026
e239b72
fix(process,reconcile): a planned redispatch is owed until dispatched…
Fodesu Sep 24, 2026
c495482
refactor(reconcile,driver,owner,app): the missing-effect policy is st…
Fodesu Sep 24, 2026
049493b
feat(run)!: every effect ends under a settlement that names it; RunEn…
Fodesu Sep 24, 2026
9b85a0c
refactor(runmod): each fact type keeps the codec of every payload ver…
Fodesu Sep 24, 2026
c168d8a
feat(sdk)!: the JSON the SDK carries through is RFC 8785 canonical at…
Fodesu Sep 24, 2026
e947388
refactor(jsonstable): one canonical JSON rule, the SDK's
Fodesu Sep 24, 2026
02c361e
feat(executor,reconcile)!: an Abort tombstone closes a key against la…
Fodesu Sep 24, 2026
c62061a
refactor(reconcile,executor): Abandon excludes an executor; Prepare d…
Fodesu Sep 24, 2026
7f6e25b
fix(executor/http): only the Server's own 400 and 409 are Known dispa…
Fodesu Sep 25, 2026
8f141d6
fix(reconcile): Deliver without a Lifetime is an error; Plan's doc na…
Fodesu Sep 25, 2026
1debabc
fix(sqlite): Seed writes an aborted key as the lone tombstone a real …
Fodesu Sep 25, 2026
1e108f5
fix(executor): a step the ledger refused is not acted on; GetOutcome …
Fodesu Sep 25, 2026
f62ace6
feat(effect,executor)!: GetOutcome is a read; a settlement stream say…
Fodesu Sep 25, 2026
c7ae3f3
feat(reconcile,loop,driver)!: wait on Outcomes through one Watcher pe…
Fodesu Sep 25, 2026
bff20ba
fix(compaction): the Summarizer waits for its one effect with effect.…
Fodesu Sep 25, 2026
6db6dfb
docs(design): cloud agent component boundaries
Fodesu Sep 25, 2026
fb513cc
feat(session): LeaseOf and ListLeases read a Session's writer lease (…
Fodesu Sep 25, 2026
f714b23
feat(executor/backendhttp): the Worker-to-Backend wire (CLD-WIR-1)
Fodesu Sep 25, 2026
5aded0a
feat(agent/models): the model catalog maps a preset's logical ModelRe…
Fodesu Sep 25, 2026
e3f556a
refactor(agent/models): credentials come from Secrets the deployment …
Fodesu Sep 25, 2026
85c42d1
refactor(agent/secrets): the credential seam is its own package; Dir …
Fodesu Sep 25, 2026
810bd87
refactor(executor,effect)!: one settlement protocol; Backend.Outcome …
Fodesu Sep 25, 2026
69c704c
refactor(executor): the supervision of one execution moves out of wor…
Fodesu Sep 25, 2026
b44df97
refactor(agentcore,agent): the kernel's tests run over reference stor…
Fodesu Sep 25, 2026
94ea185
test(executor): the settlement stream test subscribes under the hub's…
Fodesu Sep 26, 2026
cbaa269
feat(inbox,app): a durable command inbox per Session; owners apply pe…
Fodesu Sep 26, 2026
ba63ecd
refactor(executor)!: the HTTP bindings are deployment artifacts and m…
Fodesu Sep 26, 2026
71fcd39
refactor(agentcore): the colocated executor wiring moves to agent/exe…
Fodesu Sep 26, 2026
99007c3
refactor(agent): workspace and environment return as the application'…
Fodesu Sep 26, 2026
72fcecf
feat(run,loop)!: every tool declares its Placement; resolvers and rou…
Fodesu Sep 26, 2026
2fd5b92
refactor(chatlog,workspace)!: the three "checkpoints" get their own n…
Fodesu Sep 26, 2026
fc9532a
feat(agent)!: the workspace layer: Session binding facts, resolver, e…
Fodesu Sep 26, 2026
8cab078
feat(agent): workspace snapshots, inherited-binding policies and the …
Fodesu Sep 26, 2026
3f27492
refactor(agentcore,agent)!: the input content shape, the reply conven…
Fodesu Sep 26, 2026
6d9e9b6
feat(workspace,executor): the Snapshotter port with its HTTP binding;…
Fodesu Sep 26, 2026
a2066f5
feat(app/http): the owner's command face over HTTP
Fodesu Sep 26, 2026
01715ef
feat(agent): the four components as library packages, their thin bina…
Fodesu Sep 26, 2026
bb3ee37
fix(agent,agentcore): review fixes: partial snapshot writes, route co…
Fodesu Sep 26, 2026
b55aee1
feat(agent/store/postgres): the shared store over Postgres with pgx a…
Fodesu Sep 26, 2026
36c55c2
feat(agent/component,config): components choose their database throug…
Fodesu Sep 26, 2026
ba3976a
feat(agent/app,ownerservice): ownership held for active work and rele…
Fodesu Sep 26, 2026
66156bf
fix(agent/store/postgres,session,app): reopen without replaying the h…
Fodesu Sep 26, 2026
5e3db51
fix(session/extension,store): bounded claims paging, interval-governe…
Fodesu Sep 26, 2026
50758e4
perf(agent/store/postgres): reopen-path benchmarks; plain watermark q…
Fodesu Sep 26, 2026
effad48
perf(session,filestore,postgres): the handle holds none of the tip's …
Fodesu Sep 26, 2026
bdba6ae
chore(agent/store/postgres): one initial migration
Fodesu Sep 26, 2026
5157a12
refactor(turn,chatlog,app)!: a Turn is one Run; the attempt layer goes
Fodesu Sep 27, 2026
0c11f53
fix(session,filestore,postgres,app): lineage atomicity, no SessionID …
Fodesu Sep 27, 2026
a8b7e98
refactor(session): LedgerRef is CommitRef
Fodesu Sep 27, 2026
99c97c6
refactor(session)!: one file per concept; Backend and Store split by …
Fodesu Sep 27, 2026
c28f7a2
refactor(session)!: retain commits by stored path endpoints
Fodesu Sep 27, 2026
914e7b3
refactor(session)!: path spans are the reference relation
Fodesu Sep 27, 2026
30e917b
refactor(jsonstable): canonical identity primitives live beside canon…
Fodesu Sep 28, 2026
5f3fc1b
refactor!: remove agentcore/es; identity primitives find their owners
Fodesu Sep 28, 2026
5368517
refactor(session): the kernel speaks the shared commit vocabulary
Fodesu Sep 28, 2026
5190615
refactor(agentcore): comments name only their own layer
Fodesu Sep 28, 2026
8146249
feat(store/postgres): a path span names its session with a deferred f…
Fodesu Sep 28, 2026
61c9bef
docs(ledger): the package doc names the ledger and nothing more
Fodesu Sep 28, 2026
bd02fed
refactor(session)!: the adapter aggregate is Storage, not Backend
Fodesu Sep 28, 2026
2b5e9e0
docs(session): the segment comments state what the types hold
Fodesu Sep 28, 2026
4b5a96d
docs(session): the storage ports keep only the contract
Fodesu Sep 28, 2026
20cd354
docs(session): the storage port comments keep obligations, not motives
Fodesu Sep 28, 2026
bb76e25
test(writer)!: keep e2e and feat tests, drop white-box unit tests
Fodesu Sep 28, 2026
b2b84da
docs(session): rewrite the design doc as prose without rule IDs
Fodesu Sep 28, 2026
e316ecc
docs(extension): rewrite the module framework doc as prose without ru…
Fodesu Sep 28, 2026
8739d99
docs(artifact): rewrite the artifact core doc as prose without rule IDs
Fodesu Sep 28, 2026
65f84c4
docs(chatlog): rewrite the chatlog module doc as prose without rule IDs
Fodesu Sep 28, 2026
8906b17
docs(run): rewrite the run design doc as prose without rule IDs
Fodesu Sep 28, 2026
aafb38c
docs(run): split long paragraphs into themed paragraphs
Fodesu Sep 29, 2026
093b991
docs: remove design documents
Fodesu Sep 29, 2026
9d9457d
docs(architecture): add index.md
Fodesu Sep 29, 2026
3e83396
feat(runtime): add agentcore/runtime as the conversation process layer
Fodesu Sep 29, 2026
54abe9b
refactor(turn): split turn into domain, runtime, history and preset p…
Fodesu Sep 29, 2026
85549bb
refactor(preset): drop Streaming and SchemaVersion from the preset id…
Fodesu Sep 29, 2026
e6cbc0a
refactor(decision): own the Builder vocabulary and Catalog
Fodesu Sep 29, 2026
7559c87
refactor(loop): move the local execution contracts and backend out of…
Fodesu Sep 29, 2026
007ab51
refactor(preset): rename PublicTool to ToolContract and freeze at the…
Fodesu Sep 29, 2026
0e3de19
refactor(effect): carry the frozen model result in outcomes
Fodesu Sep 29, 2026
4f0148f
refactor(run): rename run/runtime to run/store
Fodesu Sep 29, 2026
f4a27bd
refactor(plan): move the state queries into run and drop the PromptIn…
Fodesu Sep 29, 2026
762c540
refactor(session)!: extension and chatlog move to the agentcore top l…
Fodesu Sep 29, 2026
4d513e2
refactor(session)!: extension and chatlog return to the session tree
Fodesu Sep 29, 2026
c100a75
refactor(ledger)!: the ledger owns the commit vocabulary
Fodesu Sep 29, 2026
089c4a2
refactor(ledger)!: the module framework moves into the ledger
Fodesu Sep 29, 2026
33a11a7
refactor(ledger)!: the dispatch and execution ledgers speak the ledge…
Fodesu Sep 29, 2026
eb362e2
refactor(turn)!: the Turn protocol moves into the runtime
Fodesu Sep 29, 2026
5bee776
refactor(runtime)!: the runtime starts no goroutine; the app hosts th…
Fodesu Sep 29, 2026
5143aec
refactor(run)!: the dispatch ledger is run/redispatch
Fodesu Sep 29, 2026
6a4aad7
refactor(driver)!: the Driver splits into Loops, Driver, Recovery and…
Fodesu Sep 29, 2026
e4962c0
refactor(ledger)!: the ledger keeps the commit vocabulary; module and…
Fodesu Sep 29, 2026
ae2bf20
refactor(core)!: chatlog and the Run session store leave the session …
Fodesu Sep 29, 2026
d4b2e9c
refactor(decision)!: the Prompt carries the frozen ModelRequest
Fodesu Sep 29, 2026
83387d4
refactor(core)!: agentcore no longer imports the SDK
Fodesu Sep 29, 2026
f89beb5
refactor(owner)!: the composition root is core; owner keeps the owner…
Fodesu Sep 29, 2026
2254dae
fix(turn): a stop's reason reaches the Turn's failed fact
Fodesu Sep 30, 2026
c09335b
refactor(core)!: core.New returns a closed object graph; Build rolls …
Fodesu Sep 30, 2026
c40937f
refactor(runtime)!: the conversation API converges to Submit/Advance/…
Fodesu Sep 30, 2026
ede9694
refactor(core)!: core composes the execution Worker and the event Bus
Fodesu Sep 30, 2026
860af66
refactor(app)!: kernel assembly choices move behind Config.Kernel
Fodesu Sep 30, 2026
8d7f262
refactor(sessionkernel)!: the Session-side semantic mechanism assembl…
Fodesu Sep 30, 2026
1067563
refactor(runtime)!: the execution side assembles as runtime.Execution…
Fodesu Sep 30, 2026
5eed5ba
refactor(app)!: core deleted; the composition root assembles Kernel, …
Fodesu Sep 30, 2026
5fc3ffe
fix(owner)!: Close refuses new Opens and waits for the ones in flight
Fodesu Sep 30, 2026
1d8434a
refactor(observe)!: the committed stream and the transient stream split
Fodesu Sep 30, 2026
f2b66b4
refactor(runtime)!: the Execution declares its sources; the Owner its…
Fodesu Sep 30, 2026
033dc5e
refactor(app)!: the deployable components compose the topology; app.N…
Fodesu Sep 30, 2026
ab10ca3
fix(runtime): a component failure reaches the transient stream once
Fodesu Sep 30, 2026
f94666e
fix(runtime): the redispatch budget is configurable
Fodesu Sep 30, 2026
127b354
refactor(reconcile)!: the Watcher alone probes kept targets for orphans
Fodesu Sep 30, 2026
2948ef7
refactor(effect)!: the executor's optional capabilities are declared,…
Fodesu Sep 30, 2026
4723beb
refactor(execution)!: driver and runtime.Execution become one package…
Fodesu Sep 30, 2026
22e9c6d
refactor(app,spawn,compaction)!: hosts take the Engine
Fodesu Sep 30, 2026
94ef4a0
refactor(spawn)!: the Responder drives children through the host's co…
Fodesu Sep 30, 2026
51f5230
fix(compaction): the summary effect's key derives from the context it…
Fodesu Sep 30, 2026
901b9fb
fix(effect): the Watcher asks for an orphan's recovery once per episode
Fodesu Sep 30, 2026
9a57557
refactor(owner)!: the Owner is the ownership table alone
Fodesu Sep 30, 2026
1b48908
refactor(app)!: the application opens and closes an owned Session in …
Fodesu Sep 30, 2026
3b06688
refactor(turn)!: the Turn protocol lives with the Turn's facts
Fodesu Sep 30, 2026
cf1fb2c
refactor(session)!: the Session lifecycle is its own package
Fodesu Sep 30, 2026
a53823e
style: gofmt the files the engine refactor left unformatted
Fodesu Sep 30, 2026
5874827
fix(execution): the dispatch re-offer policy is configurable
Fodesu Sep 30, 2026
eec081f
refactor(app)!: the application composes its Session-side services; s…
Fodesu Sep 30, 2026
4b1b006
refactor(conversation)!: runtime.SessionRuntime becomes conversation.…
Fodesu Sep 30, 2026
a9d32b7
chore: stale names and lint after the engine refactor
Fodesu Sep 30, 2026
3b620ac
fix(effect): a key many wait on is read once and answers every waiter
Fodesu Oct 1, 2026
650c4a9
refactor(loop,execution,conversation,app)!: the drive is asynchronous…
Fodesu Oct 1, 2026
99ad4b1
refactor(conversation,loop)!: a Turn's standing replaces the engine's…
Fodesu Oct 1, 2026
17a4136
refactor(loop)!: the Loop reports no observations; the engine publish…
Fodesu Oct 1, 2026
d3238af
refactor(loop)!: the Loop speaks effect's types; its aliases are gone
Fodesu Oct 1, 2026
0c5762b
refactor(sessionstore,execution)!: the takeover loop leaves the store…
Fodesu Oct 1, 2026
5dc7983
refactor(effect,reconcile)!: the Watcher is its own package; reconcil…
Fodesu Oct 1, 2026
a6ea38c
refactor(loop,execution,sessionstore): the prepare hook names the Sco…
Fodesu Oct 1, 2026
e61d879
refactor(ledger,run,turn,execution)!: one ownership-loss predicate
Fodesu Oct 1, 2026
18e31d5
refactor(run,decision)!: the prompt input is run's, not decision's
Fodesu Oct 1, 2026
0a7aff3
refactor(run)!: canonical JSON is jsonstable.Value everywhere
Fodesu Oct 1, 2026
f35a671
refactor(lifecycle,turn,app)!: the fork's Turn questions leave sessio…
Fodesu Oct 1, 2026
77a1008
refactor(turn,conversation): one Turn protocol interface, at its cons…
Fodesu Oct 1, 2026
86741e6
refactor(executor): the Worker reads Outcomes; the HTTP adapter encod…
Fodesu Oct 1, 2026
2d5ce15
refactor(observe): an Event says its Kind
Fodesu Oct 1, 2026
0d5192e
refactor(execution,app)!: the Engine exposes no registry or stream of…
Fodesu Oct 1, 2026
04dbc0e
refactor(checkpoint): the consumer checkpoint belongs to the agent layer
Fodesu Oct 1, 2026
8872ae7
refactor(app): the Session-side services are one composed group
Fodesu Oct 1, 2026
4a0eef4
refactor(run,loop,execution)!: a step's policy is frozen; the Engine …
Fodesu Oct 1, 2026
cf41a9e
fix(sessionstore): model_step_prepared stays at version 1
Fodesu Oct 2, 2026
9861d85
feat(module): Prerelease names the schema's stability
Fodesu Oct 2, 2026
ee885f9
feat(module)!: a payload version names its track: pre.N or N
Fodesu Oct 2, 2026
b6e9635
refactor(errors)!: each layer reports its own conditions; translation…
Fodesu Oct 2, 2026
0df9265
fix(localagent): Compose decides the effect port and leaves its Confi…
Fodesu Oct 2, 2026
a506465
refactor(app)!: the application closes over its services
Fodesu Oct 2, 2026
373a876
test(app): drive through the Session API, not the services behind it
Fodesu Oct 2, 2026
fdd28d3
test(loop): step the Loop as a host does, without a Run stand-in
Fodesu Oct 2, 2026
d40c432
refactor(prompt)!: the decision package is the prompt-building seam
Fodesu Oct 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,5 @@ go.work.sum
# .vscode/

tmp/
.twilight/
/twilight-agent
32 changes: 30 additions & 2 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ linters:
default: standard
enable:
- bodyclose
- depguard
- errcheck
- errname
- errorlint
Expand All @@ -26,6 +27,19 @@ linters:
- err113

settings:
depguard:
rules:
# agentcore is the protocol kernel: it depends on neither the SDK
# nor the agent built on it. Test code may use both.
agentcore:
files:
- "**/agentcore/**"
- "!$test"
deny:
- pkg: github.com/felinics/twilight/sdk
desc: agentcore does not import the SDK; convert in agent/sdkconv
- pkg: github.com/felinics/twilight/agent/
desc: agentcore does not import the agent built on it
errcheck:
check-type-assertions: true
goconst:
Expand All @@ -36,6 +50,16 @@ linters:
- diagnostic
- performance
- style
settings:
hugeParam:
# Identity-carrying protocol values (AssignmentKey, Schema,
# Assignment, artifact.Ref, session.Commit with its Ext slot) are
# 80-128 bytes of string headers and are passed by value on purpose
# so a callee cannot mutate the caller's copy. Only genuinely large
# values are worth a pointer.
sizeThreshold: 136
rangeValCopy:
sizeThreshold: 136
gocyclo:
min-complexity: 40
nakedret:
Expand All @@ -58,14 +82,18 @@ linters:
- gocritic
- unparam
- prealloc
# providertest is test infrastructure shared by the provider packages.
- path: ^provider/providertest/
- gocyclo
# Conformance harnesses are test code compiled into non-_test files so
# adapters can import them; they get the test exclusions.
- path: ^(agentcore/artifact/artifacttest|agentcore/checkpoint/checkpointtest|agentcore/executor/store/storetest|agentcore/inbox/inboxtest|agentcore/run/redispatch/redispatchtest|agentcore/run/sessionstore/storetest|agentcore/session/sessiontest|agentcore/runtime/runtimetest|provider/providertest)/
linters:
- errcheck
- gosec
- goconst
- gocritic
- unparam
- gocyclo
- prealloc

run:
timeout: 5m
Expand Down
24 changes: 24 additions & 0 deletions .pi/subagents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"maxConcurrent": 10,
"maxConcurrentForeground": 0,
"defaultMaxTurns": 0,
"graceTurns": 5,
"defaultJoinMode": "smart",
"backgroundByDefault": true,
"schedulingEnabled": true,
"scopeModels": false,
"strictAgentFiles": false,
"disableDefaultAgents": false,
"toolDescriptionMode": "full",
"fleetView": true,
"agentMentions": "model",
"rememberAgents": true,
"widgetMode": "background",
"outputTranscript": true,
"worktreeIsolation": true,
"maxSubagentDepth": 2,
"reportUsage": false,
"showCost": false,
"showModel": false,
"viewerMarkdown": "assistant"
}
291 changes: 291 additions & 0 deletions agent/app/activation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
package app

import (
"context"
"errors"
"fmt"
"sync"
"time"

"github.com/felinics/twilight/agentcore/owner"
"github.com/felinics/twilight/agentcore/preset"
"github.com/felinics/twilight/agentcore/session"
)

// Activation is the ownership model under which a Session is held for
// active work only (APP-ACT-1): a command that reaches this process opens
// the Session here when no live owner holds it, the Session is driven while
// there is work, and once it has been quiescent for IdleRelease its
// ownership is released. Two Turns of one Session may then run on two
// processes over the same stores, and a gateway may send a command to any
// replica.
type Activation struct {
// Preset is the preset an activation opens a Session with.
Preset preset.PresetID
// Options is the SessionOptions template of an activated Session; its
// Preset and ResumeActive are set by the activation.
Options SessionOptions
// IdleRelease is how long a Session stays quiescent (no active Turn, no
// background drive, no pending command) before its ownership is
// released; zero keeps an activated Session open until Close.
IdleRelease time.Duration
// Scan is the period at which this process looks for Sessions that
// need an owner (APP-ACT-3): pending inboxes and expired leases. Zero
// disables the scan; activation then happens only through Enqueue.
Scan time.Duration
// ScanLimit bounds each scan's candidates from each source; zero
// selects DefaultScanLimit. A bounded page keeps the scan's cost
// independent of the number of Sessions; what it leaves out is read
// on a later scan or by another replica.
ScanLimit int
}

// DefaultScanLimit is the page each scan reads when ScanLimit is zero.
const DefaultScanLimit = 64

// ErrNoActivation reports an Activate without Config.Activation.
var ErrNoActivation = errors.New("app: no activation is configured")

// activationTimeout bounds one background activation: an Open with its
// takeover disposition and the inbox pass.
const activationTimeout = time.Minute

// Activate opens the Session for work under the Activation options, or
// wakes it when this process already holds it (APP-ACT-1). Another live
// owner is session.ErrOwned: the command is durable and that owner applies
// it. Concurrent activations of one Session in this process coalesce.
func (app *Application) Activate(ctx context.Context, sid session.SessionID) (*Session, error) {
for {
if s, ok := app.Opened(sid); ok {
s.Wake()
return s, nil
}
if app.activation == nil {
return nil, ErrNoActivation
}
app.actMu.Lock()
if wait, busy := app.activating[sid]; busy {
app.actMu.Unlock()
select {
case <-wait:
continue
case <-ctx.Done():
return nil, ctx.Err()
}
}
done := make(chan struct{})
app.activating[sid] = done
app.actMu.Unlock()
s, err := app.activate(ctx, sid)
app.actMu.Lock()
delete(app.activating, sid)
app.actMu.Unlock()
close(done)
return s, err
}
}

func (app *Application) activate(ctx context.Context, sid session.SessionID) (*Session, error) {
if app.activation.Preset == "" {
return nil, errors.New("app: activation requires a preset")
}
ref, err := app.PresetRef(app.activation.Preset)
if err != nil {
return nil, err
}
opts := app.activation.Options
opts.Preset, opts.ResumeActive = ref, true
s, err := app.OpenSession(ctx, sid, opts)
if errors.Is(err, owner.ErrSessionOpen) {
if s, ok := app.Opened(sid); ok {
s.Wake()
return s, nil
}
}
return s, err
}

// activateInBackground is Enqueue's activation of a Session this process
// does not hold: the caller gets its durable entry at once, the Open runs
// under the application's lifetime.
func (app *Application) activateInBackground(sid session.SessionID) {
app.releases.start()
go func() {
defer app.releases.done()
ctx, cancel := context.WithTimeout(app.bg, activationTimeout)
defer cancel()
if _, err := app.Activate(ctx, sid); err != nil && !expectedActivationError(err) {
app.warn(fmt.Errorf("app: activating %s: %w", sid, err))
}
}()
}

// expectedActivationError is an outcome that needs no report: another live
// owner holds the Session, this process is between closing and reopening
// it, or the application is shutting down.
func expectedActivationError(err error) bool {
return session.IsOwned(err) || errors.Is(err, owner.ErrSessionOpen) ||
errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded)
}

// scanLoop runs scan every Activation.Scan until Close.
func (app *Application) scanLoop() {
defer app.loops.Done()
ticker := time.NewTicker(app.activation.Scan)
defer ticker.Stop()
for {
select {
case <-app.bg.Done():
return
case <-ticker.C:
app.scan(app.bg)
}
}
}

// scan activates the Sessions that need an owner and have none here
// (APP-ACT-3): those with pending commands (CLD-CMD-4) and those whose
// lease has expired, which a dead owner left. Open arbitrates between
// replicas: a live foreign lease is ErrOwned and skipped.
func (app *Application) scan(ctx context.Context) {
limit := app.activation.ScanLimit
if limit <= 0 {
limit = DefaultScanLimit
}
candidates := map[session.SessionID]struct{}{}
pending, err := app.inbox.Sessions(ctx, limit)
if err != nil {
app.warn(fmt.Errorf("app: scanning pending inboxes: %w", err))
}
for _, sid := range pending {
candidates[sid] = struct{}{}
}
expired, err := app.svc.Store.ExpiredLeases(ctx, limit)
if err != nil {
app.warn(fmt.Errorf("app: scanning expired leases: %w", err))
}
for _, l := range expired {
candidates[l.Session] = struct{}{}
}
for sid := range candidates {
if _, ok := app.Opened(sid); ok {
continue
}
if ctx.Err() != nil {
return
}
if _, err := app.Activate(ctx, sid); err != nil && !expectedActivationError(err) {
app.warn(fmt.Errorf("app: activating %s from the scan: %w", sid, err))
}
}
}

// startIdleRelease runs the Session's quiescence watch (APP-ACT-2) when
// the activation asks for one.
func (s *Session) startIdleRelease() {
act := s.app.activation
if act == nil || act.IdleRelease <= 0 {
return
}
tick := act.IdleRelease / 4
if tick < 10*time.Millisecond {
tick = 10 * time.Millisecond
}
s.host.serve(func(ctx context.Context) {
ticker := time.NewTicker(tick)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
}
if s.host.idleFor(act.IdleRelease) && s.quiescent(ctx) {
s.app.releaseIdle(s)
return
}
}
})
}

// quiescent reports the Session has no active Turn and no pending command.
// A read failure is not quiescence.
func (s *Session) quiescent(ctx context.Context) bool {
status, err := s.Status(ctx)
if err != nil || status.Active != "" {
return false
}
if s.app.inbox != nil {
pending, err := s.app.inbox.Pending(ctx, s.sid)
if err != nil || len(pending) > 0 {
return false
}
}
return true
}

// releaseIdle closes the quiescent Session, releasing its ownership, then
// re-activates it when a command landed in the inbox meanwhile: the wake
// that command sent may have reached an applier already stopped.
func (app *Application) releaseIdle(s *Session) {
app.releases.start()
go func() {
defer app.releases.done()
ctx, cancel := context.WithTimeout(context.WithoutCancel(app.bg), activationTimeout)
defer cancel()
if err := s.Close(ctx); err != nil {
app.warn(fmt.Errorf("app: releasing idle %s: %w", s.sid, err))
}
if app.bg.Err() != nil {
return
}
pending, err := app.inbox.Pending(ctx, s.sid)
if err != nil || len(pending) == 0 {
return
}
if _, err := app.Activate(ctx, s.sid); err != nil && !expectedActivationError(err) {
app.warn(fmt.Errorf("app: reactivating %s: %w", s.sid, err))
}
}()
}

// group counts goroutines whose start may race a wait, which a
// sync.WaitGroup forbids.
type group struct {
mu sync.Mutex
n int
idle chan struct{}
}

func (g *group) start() {
g.mu.Lock()
if g.n == 0 {
g.idle = make(chan struct{})
}
g.n++
g.mu.Unlock()
}

func (g *group) done() {
g.mu.Lock()
g.n--
if g.n == 0 {
close(g.idle)
}
g.mu.Unlock()
}

func (g *group) wait(ctx context.Context) error {
g.mu.Lock()
idle, n := g.idle, g.n
g.mu.Unlock()
if n == 0 {
return nil
}
select {
case <-idle:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
Loading
0