| concept | PropelLoop | |||||
|---|---|---|---|---|---|---|
| status | stable | |||||
| tracked_sources |
|
|||||
| related |
|
|||||
| last_verified | 2026-06-04 |
PROPEL is AMPERE's autonomous cognitive cycle: Perceive → Recall → Observe → Plan → Execute → Learn.
Every animated agent runs this loop continuously. Each phase is a discrete
cognitive step that emits structured events, writes typed memory cells, and
hands a refined context object to the next phase. The phases collectively
form one Arc run (ArcRunId), which is the unit of observability the rest
of the system reports against.
The loop is not a hot path through a single function — it is a contract
between independent reasoning services (PerceptionEvaluator,
PlanGenerator, PlanExecutor, OutcomeEvaluator, KnowledgeExtractor)
composed by AgentReasoning. Each service owns one phase's transformation.
The loop is the load-bearing answer to "how does an animated agent decide what to do next?". Three forces shaped it:
- Recall before action. LLMs are notoriously bad at remembering what
this very system has already tried. PROPEL forces a Recall step
before any planning, so prior
ExecutionOutcomes andKnowledgeentries are surfaced into the prompt, not silently re-discovered. - Observation is a distinct step. The leap from "I have ideas and memories" to "I have a chosen approach" requires reading the current state of the environment, including what changed since the last Arc run. Folding Observe into Plan made the planning prompt too crowded and recalled knowledge went unused against stale state.
- Loop closes via Knowledge. Without the explicit closing phase that stores knowledge, the loop is open-loop and the agent never improves. The Learn phase is where the autocatalytic property lives.
The phase boundaries exist because of cognitive load on the LLM, not on the
runtime. Each phase has a focused prompt (optionally narrowed further by a
PhaseSpark), a clear input/output contract, and a
single point at which we emit telemetry.
ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/domain/reasoning/AgentReasoning.kt— the facade composing all phase services.agents/domain/reasoning/PerceptionEvaluator.kt— Perceive: distillsAgentStateintoIdeas.agents/domain/reasoning/PlanGenerator.kt— Observe + Plan: combines ideas, recalledKnowledge, and the ticket into aPlan.agents/domain/reasoning/PlanExecutor.kt— Execute: dispatches eachTaskthroughToolExecutionEngine.agents/domain/reasoning/OutcomeEvaluator.kt— first half of Learn: turns raw tool returns into typedExecutionOutcomes.agents/domain/reasoning/KnowledgeExtractor.kt— second half of Learn: distils outcomes intoKnowledge.agents/domain/cognition/sparks/PhaseSpark.kt+PhaseSparkManager.kt— phase-aware prompt augmentation (PERCEIVE | RECALL | OBSERVE | PLAN | EXECUTE | LEARN).trace/ArcRunTrace.kt—PropelPhaseis the telemetry record per phase.docs/AGENT_LIFECYCLE.md— the human-readable narrative.
- Recall precedes Plan. No
Planmay be generated without first callingAgentMemoryService.recallRelevantKnowledgeand feeding the result intoPlanGenerator. Skipping Recall when context "feels obvious" is the canonical failure mode. - Each phase emits its own boundary events.
CognitivePhaseEvent.PhaseEntered/PhaseExitedmark phase transitions when the phase manager has a bus;ProviderCallStartedEvent/ProviderCallCompletedEventcarry acognitivePhase; memory writes carry the phase that produced them; tool calls are tagged via the active phase.ArcTraceProjectionrelies on this to bucket activity per phase. A phase that runs without emitting boundary events is invisible to the trace, which is equivalent to it not having run. - The loop closes through
Knowledge. Every successful Arc run ends withKnowledgeExtractorwriting at least oneKnowledgeentry tagged with therun_id. An Arc run that produced outcomes but no Knowledge entry has not closed the loop and will not contribute to future Recall. - Phase order is fixed. Perceive → Recall → Observe → Plan → Execute → Learn. The declaration order in
CognitivePhasematches the PROPEL acronym;enumValues<CognitivePhase>()yields the cycle. New phases are added by extending the enum and updating every service that switches on it; phases are never reordered or skipped per call site. - Observe is not a side-effect of Plan. When recalled
Knowledgecontradicts anIdeaor current state has drifted since the last run, that contradiction must be resolved in Observe and recorded in the Plan's rationale, not silently dropped during Plan generation.
- Add a phase — extend
CognitivePhase(inPhaseSpark.kt), add a correspondingPhaseSparkdata object with prompt contribution, updatePhaseSpark.forPhase, add a service implementing the phase transform, wire it throughAgentReasoning, and updateArcTraceProjection.phaseNameForso the trace knows about the new phase. - Hook into a phase — prefer
CognitivePhaseEvent.PhaseEntered/PhaseExitedwhen an event bus is wired. For older spark-stack consumers, subscribe toSparkAppliedEventfiltering onphaseSparkName(). Do not assumePhaseSparks are always enabled — they're gated onAgentConfiguration.cognitiveConfig.phaseSparks.enabledorAMPERE_PHASE_SPARKS=true. - Read what one Arc run did —
ArcTraceProjection.project(runId)returns anArcRunTracewith onePropelPhaseper phase. This is the "playback" of a cognitive cycle. Use this for debugging, not for orchestration. - Validate a change to the loop — run
./gradlew jvmTest(the primary gate) and verify the loop still produces aKnowledgeentry tagged with the run id.
- Short-circuiting Recall when the context "feels obvious" — the whole point of Recall is to override the agent's confidence with prior outcomes. Plans that look obvious are exactly the ones where past failures live.
- Treating Learn as bookkeeping — the closing phase is where the system learns. If your change records outcomes but doesn't extract
Knowledge, you've made the loop open-loop. - Inlining a "quick LLM call" outside a phase — every model invocation should be wrapped in a
ProviderCallStartedEvent/ProviderCallCompletedEventpair tagged withcognitivePhase. Calls outside this contract don't appear inArcRunTraceand break the glass-brain guarantee. - Folding Observe into Plan — recalled
Knowledgebecomes a passive context dump rather than an explicit selection between alternatives. The Plan emerges with no record of why this approach over the others. - Holding state across runs in the agent object — agents are animated, not stateful in memory. Cross-run state belongs in
OutcomeMemoryRepository/KnowledgeRepository, keyed by ids retrievable in Recall. Anything else is invisible to the trace and fragile across restarts.