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:
2026-06-21 10:45:43 +00:00
parent 04d7e26482
commit c616982b7b
14 changed files with 269 additions and 1 deletions
@@ -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