Skip to content

Commit 6e4b358

Browse files
wow-mileyclaude
andcommitted
AMPR-161 #482: fix CI by moving library test to jvmTest; refresh concepts
- Move PhaseSparkLibraryTest from commonTest to jvmTest. The test calls DefaultPhaseSparkLibrary.load() which relies on the JVM classpath fallback for resource access. Android unit tests (testDebugUnitTest) don't have the bundled .spark.md files on their unit-test classpath, causing all six library assertions to fail in CI. This matches the existing pattern in ProviderPricingCatalogTest (also jvmTest only). At runtime on Android, Res.readBytes works through Compose Resources, so the implementation itself remains commonMain — only the test moves. - Update docs/concepts/spark-system.md to reflect the new Spark fields (phaseContributions, agentRole, requestedToolIds), composition rules (additive, not exclusive), declarative .spark.md support, and the new source files. Bump last_verified. Concept-Verified: CognitiveRelay Concept-Verified: PluginPermissions Concept-Verified: PropelLoop Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 96c8d35 commit 6e4b358

2 files changed

Lines changed: 53 additions & 16 deletions

File tree

ampere-core/src/commonTest/kotlin/link/socket/ampere/agents/domain/cognition/sparks/PhaseSparkLibraryTest.kt renamed to ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/domain/cognition/sparks/PhaseSparkLibraryTest.kt

File renamed without changes.

docs/concepts/spark-system.md

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ tracked_sources:
55
- ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/domain/cognition/**
66
- ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/domain/event/SparkAppliedEvent.kt
77
- ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/domain/event/SparkRemovedEvent.kt
8+
- ampere-core/src/commonMain/composeResources/files/sparks/**
89
related: [PropelLoop, CognitiveRelay, PluginPermissions, CognitionTrace]
9-
last_verified: 2026-04-29
10+
last_verified: 2026-05-17
1011
---
1112

1213
# Spark System
@@ -16,18 +17,45 @@ last_verified: 2026-04-29
1617
A `Spark` is a specialization layer that narrows an agent's cognitive
1718
focus. Rather than separate agent classes for each role / project / task,
1819
AMPERE accumulates specialization on a single agent through a stack of
19-
Sparks. Each Spark contributes:
20-
21-
- **Prompt content** — markdown appended to the system prompt.
22-
- **Tool narrowing** — an optional `allowedTools: Set<ToolId>` the Spark permits.
23-
- **File access narrowing** — an optional `FileAccessScope` the Spark permits.
20+
Sparks. Each Spark contributes pure data — no Kotlin lambdas — so Sparks
21+
remain shareable as `.spark.md` artifacts:
22+
23+
- **Always-on prompt content** — markdown via `promptContribution`.
24+
- **Per-phase prompt sections**`phaseContributions: Map<CognitivePhase, String>`
25+
whose entry for the current phase is appended after the base contribution.
26+
- **Role label fragment** — optional `agentRole` (e.g. `"Cooking Domain"`)
27+
concatenated across the stack into the agent's effective role.
28+
- **Tool requests (additive)**`requestedToolIds: Set<ToolId>`, unioned
29+
across the stack; declares which tools this Spark needs.
30+
- **Tool narrowing (subtractive)** — optional `allowedTools: Set<ToolId>`
31+
the Spark permits, intersected across the stack.
32+
- **File access narrowing** — optional `FileAccessScope` the Spark permits.
2433

2534
Concrete subtypes include `RoleSpark`, `ProjectSpark`, `TaskSpark`,
26-
`LanguageSpark`, `CoordinationSpark`, and `PhaseSpark`. The `SparkStack`
35+
`LanguageSpark`, `CoordinationSpark`, `PhaseSpark`, and `DeclarativePhaseSpark`
36+
(loaded from bundled `.spark.md` files). The `SparkStack`
2737
composes them in order; the system prompt is rebuilt from the live stack
28-
before each LLM interaction. Applying or removing a Spark emits
29-
`SparkAppliedEvent` / `SparkRemovedEvent` so the trace can show *exactly*
30-
what specialization was active at any point in a run.
38+
before each LLM interaction, parameterized by the agent's current
39+
`CognitivePhase` so per-phase guidance flows through. Applying or removing
40+
a Spark emits `SparkAppliedEvent` / `SparkRemovedEvent` so the trace can
41+
show *exactly* what specialization was active at any point in a run.
42+
43+
### Declarative Sparks
44+
45+
`DeclarativePhaseSpark`s are parsed from `.spark.md` files under
46+
`composeResources/files/sparks/`. A spark file has YAML-style frontmatter
47+
(id, name, whenToUse, phases, tags, agentRole, requestedToolIds,
48+
modelPreference) followed by a markdown body. The body may contain
49+
`## When Perceiving / Planning / Executing / Learning` sections, which the
50+
parser extracts into `phaseContributions`; text outside those headers
51+
becomes the base `promptContribution`.
52+
53+
`DefaultPhaseSparkLibrary` loads the bundled fixtures at construction time;
54+
`PhaseSparkLibrary.selectFor(SparkSelectionContext)` filters by phase
55+
eligibility, tag intersection, and keyword match against `whenToUse`.
56+
`PhaseSparkManager` consults the library when
57+
`AmpereSpikeFlags.declarativeSparksEnabled` is on, applying both the
58+
built-in `PhaseSpark.forPhase(phase)` and any selected declarative sparks.
3159

3260
## Why it exists
3361

@@ -54,15 +82,20 @@ exceed parent permissions, so adding a Spark is monotone safe.
5482
## Where it lives
5583

5684
- `agents/domain/cognition/Spark.kt` — the interface; not sealed (subpackages need to extend).
57-
- `agents/domain/cognition/SparkStack.kt` — composition; intersection for tools, intersection-then-union semantics for file access.
85+
- `agents/domain/cognition/SparkStack.kt` — composition; `buildSystemPrompt(phase)` concatenates every spark's contribution plus its per-phase section; `effectiveAgentRole()` concatenates role fragments; `effectiveRequestedTools()` unions; `effectiveAllowedTools()` intersects; intersection-then-union semantics for file access.
5886
- `agents/domain/cognition/FileAccessScope.kt` — read/write/forbidden patterns.
5987
- `agents/domain/cognition/CognitiveAffinity.kt` — Spark selection signals.
60-
- `agents/domain/cognition/sparks/RoleSpark.kt` — role specialization (Code, PM, Reviewer, …).
88+
- `agents/domain/cognition/sparks/RoleSpark.kt` — role specialization (Code, Planning, Research, Operations) carrying `agentRole` + `requestedToolIds`.
6189
- `agents/domain/cognition/sparks/ProjectSpark.kt`, `AmpereProjectSpark.kt` — project-level context.
6290
- `agents/domain/cognition/sparks/TaskSpark.kt` — task-shaped narrowing.
6391
- `agents/domain/cognition/sparks/LanguageSpark.kt` — language-specific guidance.
6492
- `agents/domain/cognition/sparks/CoordinationSpark.kt` — multi-agent coordination context.
65-
- `agents/domain/cognition/sparks/PhaseSpark.kt` + `PhaseSparkManager.kt``PERCEIVE | PLAN | EXECUTE | LEARN`.
93+
- `agents/domain/cognition/sparks/PhaseSpark.kt` + `PhaseSparkManager.kt``PERCEIVE | PLAN | EXECUTE | LEARN`; manager applies built-in + selected declarative sparks as a list, pops in reverse on phase exit.
94+
- `agents/domain/cognition/sparks/DeclarativePhaseSpark.kt` — markdown-authored `PhaseSpark` with `eligiblePhases` + per-phase `phaseContributions`.
95+
- `agents/domain/cognition/sparks/SparkParser.kt` — hand-rolled frontmatter parser; extracts `## When <Phase>` sections.
96+
- `agents/domain/cognition/sparks/PhaseSparkLibrary.kt`, `DefaultPhaseSparkLibrary.kt` — read-only catalog with deterministic `selectFor` ordering.
97+
- `agents/domain/cognition/sparks/AmpereSpikeFlags.kt``declarativeSparksEnabled: Boolean = false`; gates declarative spark application.
98+
- `composeResources/files/sparks/*.spark.md` — bundled declarative spark fixtures.
6699
- `agents/domain/event/SparkAppliedEvent.kt`, `SparkRemovedEvent.kt` — observability.
67100

68101
## Invariants
@@ -72,15 +105,19 @@ exceed parent permissions, so adding a Spark is monotone safe.
72105
- **Apply/remove are paired and observed.** Every `SparkAppliedEvent` has a matching `SparkRemovedEvent` (or end-of-run cleanup). `ArcTraceProjection` uses these events to reconstruct phase context.
73106
- **Tool-set composition is intersection.** When two Sparks both specify `allowedTools`, the effective set is `A ∩ B`, not `A ∪ B`. A change that switches to union is a permission expansion and violates the narrowing invariant.
74107
- **PhaseSparks add context only.** They do not narrow tools (`allowedTools = null`) or file access (`fileAccessScope = null`). Their job is prompt augmentation, not capability gating.
75-
- **Spark `name` follows `Type:Subtype`.** `Role:Code`, `Phase:Perceive`, `Project:ampere`. The trace projection extracts subtype from this prefix; ad-hoc names break trace bucketing.
108+
- **Spark `name` follows `Type:Subtype`.** `Role:Code`, `Phase:Perceive`, `Project:ampere`, `PhaseSpark:cooking-domain`. The trace projection extracts subtype from this prefix; ad-hoc names break trace bucketing. Declarative sparks use `PhaseSpark:<id>` so trace bucketing that keys on `Phase:` still treats built-in phases distinctly.
109+
- **Sparks are pure data.** No Kotlin lambdas on the `Spark` interface — behavioral guidance is expressed as markdown in `promptContribution` / `phaseContributions`, interpreted by the LLM. This is what makes a `.spark.md` file a complete, shareable unit of customization.
110+
- **Composition is additive, not exclusive.** When `N` sparks are on the stack, `buildSystemPrompt` includes contributions from all `N` — not "the topmost wins". `effectiveAgentRole` concatenates fragments with `" + "` (e.g. `Code Writer + Cooking Domain`). `effectiveRequestedTools` unions.
76111

77112
## Common operations
78113

79-
- **Add a new Spark type** — implement `Spark` (or extend an existing sealed family like `PhaseSpark`), define `name`, `promptContribution`, optionally `allowedTools` / `fileAccessScope`, mark it `@Serializable` with a stable `@SerialName`.
114+
- **Add a new Spark type** — implement `Spark` (or extend an existing sealed family like `PhaseSpark`), define `name`, `promptContribution`, optionally `allowedTools` / `fileAccessScope` / `phaseContributions` / `agentRole` / `requestedToolIds`, mark it `@Serializable` with a stable `@SerialName`.
115+
- **Author a declarative spark** — write a `.spark.md` file under `composeResources/files/sparks/` with frontmatter (id, name, whenToUse required) and a markdown body, optionally with `## When <Phase>` sections for phase-specific guidance. Add the path to `DefaultPhaseSparkLibrary.DEFAULT_SPARKS`.
80116
- **Apply a Spark transiently**`SparkStack.push(spark)` and ensure a matching `pop` in `finally`. `PhaseSparkManager` handles this for phase boundaries.
81-
- **Compose a per-agent stack**`RoleSpark` + `ProjectSpark` at agent construction, then `PhaseSpark` pushed/popped per phase, then `TaskSpark` pushed/popped per task.
117+
- **Compose a per-agent stack**`RoleSpark` + `ProjectSpark` at agent construction, then `PhaseSpark` pushed/popped per phase (potentially multiple when declarative library is active), then `TaskSpark` pushed/popped per task.
82118
- **Inspect the active stack** — subscribe to `SparkAppliedEvent` / `SparkRemovedEvent` on the bus, or read `SparkStack.current`.
83119
- **Enable phase sparks** — set `AgentConfiguration.cognitiveConfig.phaseSparks.enabled = true` (optionally per-phase) or `AMPERE_PHASE_SPARKS=true` globally.
120+
- **Enable declarative phase sparks** — set `AmpereSpikeFlags.declarativeSparksEnabled = true` and inject a `PhaseSparkLibrary` into the agent (via `SparkBasedAgent.setPhaseSparkLibrary` or `PhaseSparkManager.createWithLibrary`). Default is off; flip in `try { ... } finally { ... = false }` blocks in tests.
84121

85122
## Anti-patterns
86123

0 commit comments

Comments
 (0)