Two days ago we built the drive system — four axes of intrinsic motivation that read existing social cognition outputs and produce a motivational profile. The obvious question: now what? A drive intensity of 0.7 on curiosity means nothing if it never becomes an action.

The gap turned out to be narrower than I expected. The engine already has a complete goal lifecycle: formation, decomposition, revision, abandonment, routing signals, completion marking. Six evaluators and a planner, all wired up and working. The only problem is that goal formation starts from GoalFormationEvaluator.evaluate(workerName, caseInstance, insights) — it needs a running case. Autonomous goals fire during idle time when no case is active.

So the design was less “build a goal system” and more “extract the case-independent parts of the one that already exists.”

The extraction

GoalFormationService pulls validation, deduplication, capacity checking, and audit logging out of the evaluator into a standalone SPI. The evaluator keeps its cooldown, memory retrieval, and strategy invocation — it just delegates registration to the service now. Similarly, GoalRemovalService extracts the shared “remove goals + re-register descriptor + audit” pattern that both the revision evaluator and our new drive lifecycle need.

One deliberate decision: I didn’t refactor GoalRevisionEvaluator to use the removal service. The revision flow does a comprehensive descriptor replacement — promotions, demotions, description revisions, and removals in a single atomic register call. Splitting the removal into a separate service call would cause two registration round-trips with stale state between them. The service exists for simple removal use cases (drive relevance re-evaluation). The revision evaluator’s flow is fundamentally different, and forcing delegation would add complexity without benefit.

The compositor that doesn’t register

GoalProposalOrchestrator follows the same compositor pattern as DriveOrchestrator: tick() evaluates and caches, currentProposals() returns cached state, no side effects. The scheduler reads proposals and calls GoalFormationService.propose() separately.

This matters because goal registration is effectful — it modifies the agent descriptor and writes an audit log. If the orchestrator registered goals directly, a double-tick (replay, retry, crash recovery) would produce duplicate registrations. The compositor guarantee prevents this: tick produces the same result regardless of how many times it runs. The effectful step happens exactly once, when the scheduler decides to act.

Drive-sourced goals always use SECONDARY priority. The engine’s existing priority system handles sequencing — SECONDARY goals execute only when no PRIMARY case-assigned work is available. The priority system IS the idle-time mechanism. No idle detection needed.

The bug that isn’t obvious

Claude caught something during review that I’d have missed in manual testing: the orchestrator’s mapper loop calls ALL mappers for ALL above-threshold axes. A curiosity mapper receiving a COMPETENCE intensity happily reads its own source data and returns a CURIOSITY proposal — but carrying the COMPETENCE intensity value. The axis in the output is correct; only the numeric intensity is wrong. And since intensity values are plausible at any point in [0, 1], the proposals look valid.

The fix is a one-line guard at the top of each mapper: if (intensity.axis() != DriveAxis.CURIOSITY) return null. The nested loop pattern is correct for CDI discovery where mappers don’t declare their axis — but each mapper must self-filter.

This is exactly the kind of bug that passes all obvious tests. Every mapper returns proposals for the right axis. Every proposal has a plausible intensity. The corruption only shows when you compare the proposal’s intensity against the originating drive’s actual intensity — and nothing in the test suite was doing that comparison across axes.

What Layer 2 doesn’t do

Drive-sourced goals are always SECONDARY. There’s no pathway for a drive to preempt case-assigned work — that requires governance that doesn’t exist yet. Cross-axis goal composition (“learn about X by engaging with Y”) requires a holistic self-model. Dynamic priority based on drive intensity needs narrative justification for elevation.

All of these are Layer 3 territory: narrative identity. The agent needs a coherent sense of purpose before it can justify taking initiative over assigned work. Without that justification, runaway drives could preempt legitimate case-bound goals — an ungoverned pathway where accumulated curiosity silently starves production work.

The priority system is the safety valve. SECONDARY means “when nothing else needs doing.” That constraint is the right default until governance exists to relax it.


<
Previous Post
YAML Viewer and Visual Feedback
>
Next Post
The Panels That Already Existed