feat(toolintent): record the session's active task as a session-local fact

Claiming a task now emits SessionWorkingTaskEvent{taskId, affectedPaths} into the
session's own stream (via a SessionFactRecorder port + event-store adapter), and
SessionContextProjection folds it into SessionContext.activeTask. So a gate learns
"which task is this session working, and its scope" by reading the session stream
— no cross-stream scan, no kernel->tasks dependency. Re-emitted on an affected_paths
edit by the claimant, so the snapshot scope stays current; latest wins on replay.

This is the data source the write-scope gate needs (next).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 20:35:40 +00:00
parent 47a109e49f
commit 30321e08a5
10 changed files with 131 additions and 2 deletions
@@ -1,6 +1,7 @@
package com.correx.core.events.events
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.TaskId
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@@ -10,6 +11,20 @@ data class ChatSessionStartedEvent(
val sessionId: SessionId,
) : EventPayload
/**
* Records, in the session's own stream, that this run claimed [taskId] and is working it with the
* declared [affectedPaths] write scope — a session-local fact so the gates know the active task
* without scanning task streams. Re-emitted when the claimant edits affected_paths, so the folded
* scope stays current; SessionContextProjection takes the most recent.
*/
@Serializable
@SerialName("SessionWorkingTask")
data class SessionWorkingTaskEvent(
val sessionId: SessionId,
val taskId: TaskId,
val affectedPaths: List<String> = emptyList(),
) : EventPayload
@Serializable
@SerialName("SessionWorkspaceBound")
data class SessionWorkspaceBoundEvent(
@@ -12,6 +12,7 @@ import com.correx.core.events.events.ArtifactValidatingEvent
import com.correx.core.events.events.BriefEchoMismatchEvent
import com.correx.core.events.events.BriefGroundingCheckedEvent
import com.correx.core.events.events.ChatSessionStartedEvent
import com.correx.core.events.events.SessionWorkingTaskEvent
import com.correx.core.events.events.ClarificationAnsweredEvent
import com.correx.core.events.events.ClarificationRequestedEvent
import com.correx.core.events.events.ChatTurnEvent
@@ -139,6 +140,7 @@ val eventModule = SerializersModule {
subclass(BriefEchoMismatchEvent::class)
subclass(RiskAssessedEvent::class)
subclass(ChatSessionStartedEvent::class)
subclass(SessionWorkingTaskEvent::class)
subclass(ChatTurnEvent::class)
subclass(RouterNarrationEvent::class)
subclass(SessionWorkspaceBoundEvent::class)