feat(freestyle): soft-lock steering preemption (Slice 5)
- DecisionKind gains PREEMPT; DecisionJournalState tracks planLocked. - DefaultDecisionJournalReducer: ExecutionPlanLockedEvent flips planLocked (no record); post-lock steering notes recorded as PREEMPT with a 'PRIORITY DIRECTIVE' summary, pre-lock stay STEERING. - SessionOrchestrator.buildSteeringNoteEntries: post-lock notes surfaced as pinned L0/SYSTEM 'PRIORITY DIRECTIVE' entries ahead of the plan; pre-lock keep L2. Graph re-routing intentionally deferred (priority surfacing only). - Tests: PreemptSteeringJournalTest, SteeringPreemptionTest.
This commit is contained in:
+5
-2
@@ -2,6 +2,7 @@ package com.correx.core.journal
|
||||
|
||||
import com.correx.core.events.events.ApprovalDecisionResolvedEvent
|
||||
import com.correx.core.events.events.ArtifactValidatedEvent
|
||||
import com.correx.core.events.events.ExecutionPlanLockedEvent
|
||||
import com.correx.core.events.events.InitialIntentEvent
|
||||
import com.correx.core.events.events.RetryAttemptedEvent
|
||||
import com.correx.core.events.events.StageFailedEvent
|
||||
@@ -14,6 +15,7 @@ import com.correx.core.journal.model.DecisionRecord
|
||||
|
||||
class DefaultDecisionJournalReducer : DecisionJournalReducer {
|
||||
override fun reduce(state: DecisionJournalState, event: StoredEvent): DecisionJournalState {
|
||||
if (event.payload is ExecutionPlanLockedEvent) return state.copy(planLocked = true)
|
||||
val record = when (val p = event.payload) {
|
||||
is InitialIntentEvent -> DecisionRecord(
|
||||
sequence = event.sessionSequence,
|
||||
@@ -22,8 +24,9 @@ class DefaultDecisionJournalReducer : DecisionJournalReducer {
|
||||
)
|
||||
is SteeringNoteAddedEvent -> DecisionRecord(
|
||||
sequence = event.sessionSequence,
|
||||
kind = DecisionKind.STEERING,
|
||||
summary = "User steering: ${p.content}",
|
||||
kind = if (state.planLocked) DecisionKind.PREEMPT else DecisionKind.STEERING,
|
||||
summary = if (state.planLocked) "PRIORITY DIRECTIVE (overrides plan): ${p.content}"
|
||||
else "User steering: ${p.content}",
|
||||
stageId = p.stageId?.value,
|
||||
)
|
||||
is ApprovalDecisionResolvedEvent -> DecisionRecord(
|
||||
|
||||
@@ -5,4 +5,5 @@ import kotlinx.serialization.Serializable
|
||||
@Serializable
|
||||
data class DecisionJournalState(
|
||||
val records: List<DecisionRecord> = emptyList(),
|
||||
val planLocked: Boolean = false,
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.correx.core.journal.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
enum class DecisionKind { INTENT, STEERING, APPROVAL, TRANSITION, RETRY, FAILURE, ARTIFACT }
|
||||
enum class DecisionKind { INTENT, STEERING, PREEMPT, APPROVAL, TRANSITION, RETRY, FAILURE, ARTIFACT }
|
||||
|
||||
/**
|
||||
* One distilled decision. [sequence] is the source event's sessionSequence, used for
|
||||
|
||||
+6
-2
@@ -38,6 +38,7 @@ import com.correx.core.events.events.NewEvent
|
||||
import com.correx.core.events.events.OrchestrationPausedEvent
|
||||
import com.correx.core.events.events.OrchestrationResumedEvent
|
||||
import com.correx.core.events.events.RiskAssessedEvent
|
||||
import com.correx.core.events.events.ExecutionPlanLockedEvent
|
||||
import com.correx.core.events.events.SteeringNoteAddedEvent
|
||||
import com.correx.core.events.events.ToolExecutionRejectedEvent
|
||||
import com.correx.core.events.events.ToolInvocationRequestedEvent
|
||||
@@ -787,12 +788,15 @@ abstract class SessionOrchestrator(
|
||||
|
||||
private suspend fun buildSteeringNoteEntries(sessionId: SessionId): List<ContextEntry> {
|
||||
val events = eventStore.read(sessionId)
|
||||
var locked = false
|
||||
return events.mapNotNull { event ->
|
||||
when (val p = event.payload) {
|
||||
is ExecutionPlanLockedEvent -> { locked = true; null }
|
||||
is SteeringNoteAddedEvent -> ContextEntry(
|
||||
id = ContextEntryId(UUID.randomUUID().toString()),
|
||||
layer = ContextLayer.L2,
|
||||
content = p.content,
|
||||
layer = if (locked) ContextLayer.L0 else ContextLayer.L2,
|
||||
content = if (locked) "PRIORITY DIRECTIVE — overrides the locked plan: ${p.content}"
|
||||
else p.content,
|
||||
sourceType = "steeringNote",
|
||||
sourceId = p.stageId?.value ?: sessionId.value,
|
||||
tokenEstimate = estimateTokens(p.content),
|
||||
|
||||
Reference in New Issue
Block a user