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
@@ -238,6 +238,7 @@ fun main() {
taskDocumentResolver,
taskArtifactResolver,
taskSessionResolver,
com.correx.apps.server.tasks.EventStoreSessionFactRecorder(eventStore),
)
val toolRegistry = InfrastructureModule.createToolRegistry(
buildToolConfig(
@@ -0,0 +1,36 @@
package com.correx.apps.server.tasks
import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.NewEvent
import com.correx.core.events.events.SessionWorkingTaskEvent
import com.correx.core.events.stores.EventStore
import com.correx.core.events.types.EventId
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.TaskId
import com.correx.infrastructure.tools.task.SessionFactRecorder
import kotlinx.datetime.Clock
import java.util.UUID
/**
* Adapter for [SessionFactRecorder]: appends a [SessionWorkingTaskEvent] to the session's own stream
* when it claims (or re-scopes) a task, so [com.correx.core.toolintent.SessionContextProjection]
* folds it into the active task without scanning task streams.
*/
class EventStoreSessionFactRecorder(private val eventStore: EventStore) : SessionFactRecorder {
override suspend fun recordWorkingTask(sessionId: SessionId, taskId: TaskId, affectedPaths: List<String>) {
eventStore.append(
NewEvent(
metadata = EventMetadata(
eventId = EventId(UUID.randomUUID().toString()),
sessionId = sessionId,
timestamp = Clock.System.now(),
schemaVersion = 1,
causationId = null,
correlationId = null,
),
payload = SessionWorkingTaskEvent(sessionId, taskId, affectedPaths),
),
)
}
}