feat(personalization): OperatorProfileBoundEvent + bind-at-start + inject about context
Records operator profile as a snapshot event at session start (invariants #8/#9), populates SessionState.boundProfile via reducer, and injects the about text as a pinned L0 SYSTEM context entry in every stage. Gated by PersonalizationConfig.enabled.
This commit is contained in:
@@ -17,3 +17,13 @@ data class SessionWorkspaceBoundEvent(
|
||||
val workspaceRoot: String,
|
||||
val allowedPaths: List<String>,
|
||||
) : EventPayload
|
||||
|
||||
@Serializable
|
||||
@SerialName("OperatorProfileBound")
|
||||
data class OperatorProfileBoundEvent(
|
||||
val sessionId: SessionId,
|
||||
val about: String,
|
||||
val approvalMode: String,
|
||||
val preferredModels: List<String>,
|
||||
val conventions: List<String>,
|
||||
) : EventPayload
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.correx.core.events.events.ArtifactValidatingEvent
|
||||
import com.correx.core.events.events.ChatSessionStartedEvent
|
||||
import com.correx.core.events.events.ChatTurnEvent
|
||||
import com.correx.core.events.events.RouterNarrationEvent
|
||||
import com.correx.core.events.events.OperatorProfileBoundEvent
|
||||
import com.correx.core.events.events.SessionWorkspaceBoundEvent
|
||||
import com.correx.core.events.events.ContextTruncatedEvent
|
||||
import com.correx.core.events.events.EventPayload
|
||||
@@ -91,6 +92,7 @@ val eventModule = SerializersModule {
|
||||
subclass(ChatTurnEvent::class)
|
||||
subclass(RouterNarrationEvent::class)
|
||||
subclass(SessionWorkspaceBoundEvent::class)
|
||||
subclass(OperatorProfileBoundEvent::class)
|
||||
subclass(L3MemoryRetrievedEvent::class)
|
||||
subclass(ContextTruncatedEvent::class)
|
||||
subclass(ExecutionPlanLockedEvent::class)
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package com.correx.core.events.serialization
|
||||
|
||||
import com.correx.core.events.events.EventPayload
|
||||
import com.correx.core.events.events.OperatorProfileBoundEvent
|
||||
import com.correx.core.events.types.SessionId
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class OperatorProfileBoundEventSerializationTest {
|
||||
|
||||
private val sample = OperatorProfileBoundEvent(
|
||||
sessionId = SessionId("sess-1"),
|
||||
about = "A seasoned backend engineer who prefers concise, idiomatic Kotlin.",
|
||||
approvalMode = "auto",
|
||||
preferredModels = listOf("llama-3-70b", "qwen2.5-72b"),
|
||||
conventions = listOf("no var", "no mutable state"),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `round-trips as polymorphic EventPayload`() {
|
||||
val encoded = eventJson.encodeToString(EventPayload.serializer(), sample)
|
||||
assertTrue(encoded.contains("\"type\":\"OperatorProfileBound\""), "SerialName must be present: $encoded")
|
||||
val decoded = eventJson.decodeFromString(EventPayload.serializer(), encoded)
|
||||
assertEquals(sample, decoded)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `decodes hand-written OperatorProfileBound JSON`() {
|
||||
val json = """
|
||||
{"type":"OperatorProfileBound","sessionId":"s-99","about":"senior dev","approvalMode":"suggest","preferredModels":["model-a"],"conventions":["no null"]}
|
||||
""".trimIndent()
|
||||
val decoded = eventJson.decodeFromString(EventPayload.serializer(), json)
|
||||
assertTrue(decoded is OperatorProfileBoundEvent)
|
||||
decoded as OperatorProfileBoundEvent
|
||||
assertEquals("s-99", decoded.sessionId.value)
|
||||
assertEquals("senior dev", decoded.about)
|
||||
assertEquals("suggest", decoded.approvalMode)
|
||||
assertEquals(listOf("model-a"), decoded.preferredModels)
|
||||
assertEquals(listOf("no null"), decoded.conventions)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `round-trips with empty lists`() {
|
||||
val empty = OperatorProfileBoundEvent(
|
||||
sessionId = SessionId("s-0"),
|
||||
about = "",
|
||||
approvalMode = "",
|
||||
preferredModels = emptyList(),
|
||||
conventions = emptyList(),
|
||||
)
|
||||
val encoded = eventJson.encodeToString(EventPayload.serializer(), empty)
|
||||
val decoded = eventJson.decodeFromString(EventPayload.serializer(), encoded)
|
||||
assertEquals(empty, decoded)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user