feat(context): inject CLAUDE.md / AGENTS.md as L0 standing context
On session start, discover CLAUDE.md and AGENTS.md at the bound workspace root and inject their (concatenated, header-labeled) contents as an L0 system context entry for every stage — mirroring the .correx/project.toml ProjectProfile path end to end. Recorded as AgentInstructionsBoundEvent (invariant #9) so replay reads the recorded fact, not the live file; folded into SessionState.boundAgentInstructions and rendered by buildAgentInstructionsEntry right after the project profile. AgentInstructionsLoader reads root-only, both files if present. Bound via ServerModule.bindAgentInstructions alongside bindProjectProfile. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -11,11 +11,13 @@ import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.events.types.StageId
|
||||
import com.correx.core.events.types.TransitionId
|
||||
import com.correx.core.events.events.RepoKnowledgeHit
|
||||
import com.correx.core.kernel.orchestration.buildAgentInstructionsEntry
|
||||
import com.correx.core.kernel.orchestration.buildArtifactKindVocabularyEntry
|
||||
import com.correx.core.kernel.orchestration.buildProjectProfileEntry
|
||||
import com.correx.core.kernel.orchestration.buildRelevantFilesEntry
|
||||
import com.correx.core.kernel.orchestration.buildRetryFeedbackEntry
|
||||
import com.correx.core.kernel.orchestration.criticArtifactIds
|
||||
import com.correx.core.sessions.BoundAgentInstructions
|
||||
import com.correx.core.sessions.BoundProjectProfile
|
||||
import com.correx.core.transitions.graph.StageConfig
|
||||
import com.correx.core.transitions.graph.TransitionEdge
|
||||
@@ -87,6 +89,21 @@ class ContextFeedbackTest {
|
||||
assertEquals("projectProfile", entry.sourceType)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `agent instructions render as single L0 entry`() {
|
||||
val entry = buildAgentInstructionsEntry(
|
||||
BoundAgentInstructions(
|
||||
sources = listOf("CLAUDE.md", "AGENTS.md"),
|
||||
content = "# CLAUDE.md\n\nbe careful\n\n# AGENTS.md\n\nuse tools",
|
||||
),
|
||||
)
|
||||
assertEquals(ContextLayer.L0, entry.layer)
|
||||
assertEquals(EntryRole.SYSTEM, entry.role)
|
||||
assertEquals("agentInstructions", entry.sourceType)
|
||||
assertTrue(entry.content.contains("# CLAUDE.md"), "content: ${entry.content}")
|
||||
assertTrue(entry.content.contains("# AGENTS.md"), "content: ${entry.content}")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `criticArtifactIds resolves the from-stage produces on a back-edge`() {
|
||||
val graph = graphWith(
|
||||
|
||||
@@ -10,7 +10,9 @@ import com.correx.core.events.events.WorkflowStartedEvent
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.events.types.StageId
|
||||
import com.correx.core.events.types.TransitionId
|
||||
import com.correx.core.events.events.AgentInstructionsBoundEvent
|
||||
import com.correx.core.events.events.ProjectProfileBoundEvent
|
||||
import com.correx.core.sessions.BoundAgentInstructions
|
||||
import com.correx.core.sessions.BoundProjectProfile
|
||||
import com.correx.core.sessions.BoundWorkspace
|
||||
import com.correx.core.sessions.DefaultSessionReducer
|
||||
@@ -327,6 +329,50 @@ class DefaultSessionReducerTest {
|
||||
assertEquals("kernel project", result.boundProjectProfile?.about)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `AgentInstructionsBoundEvent reduces into boundAgentInstructions`() {
|
||||
val result = reducer.reduce(
|
||||
state = initialState(),
|
||||
event = stored(
|
||||
sessionId = sessionId,
|
||||
payload = AgentInstructionsBoundEvent(
|
||||
sessionId = sessionId,
|
||||
workspaceRoot = "/repo",
|
||||
sources = listOf("CLAUDE.md", "AGENTS.md"),
|
||||
content = "# CLAUDE.md\n\nbe careful",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
BoundAgentInstructions(
|
||||
sources = listOf("CLAUDE.md", "AGENTS.md"),
|
||||
content = "# CLAUDE.md\n\nbe careful",
|
||||
),
|
||||
result.boundAgentInstructions,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `boundAgentInstructions is preserved by subsequent unrelated events`() {
|
||||
val withInstructions = initialState().copy(
|
||||
boundAgentInstructions = BoundAgentInstructions(
|
||||
sources = listOf("CLAUDE.md"),
|
||||
content = "# CLAUDE.md\n\nrules",
|
||||
),
|
||||
)
|
||||
|
||||
val result = reducer.reduce(
|
||||
state = withInstructions,
|
||||
event = stored(
|
||||
sessionId = sessionId,
|
||||
payload = WorkflowStartedEvent(sessionId, workflowId = "wf", startStageId = StageId("s1")),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(listOf("CLAUDE.md"), result.boundAgentInstructions?.sources)
|
||||
}
|
||||
|
||||
private fun initialState() =
|
||||
SessionState(
|
||||
status = SessionStatus.CREATED
|
||||
|
||||
Reference in New Issue
Block a user