Initiative and learning
InitiativeCoordinator lets a project choose its own actions without a goal; LearningCoordinator manages reflection and experiments.
InitiativeCoordinator
The coordinator takes project observations and calls a real model to choose among wait, work, share and learn. The host supplies a handler for each action and persistence.
import { InitiativeCoordinator } from './packages/sdk/src/index.mjs';
const initiative = new InitiativeCoordinator({
project,
model,
apiKey,
provider,
handlers, // host implementations of work / share / learn
policy: {
tokenBudget: 200000,
decisionMaxTokens: 2048,
workBudgetTokens: 24000,
maxConcurrent: 1,
cooldownMs: 10000,
},
saveState: (state) => writeJsonAtomically(statePath, state),
onEvent: emit,
});
await initiative.observe({
id: 'project-opened:1',
type: 'project_opened',
summary: 'The user just created the project and has not asked for anything yet.',
});
const result = await initiative.tick({ presence: { typing: false, activeTurn: false }, signal });
await initiative.idle();writeJsonAtomically, handlers and emit belong to the host. Pass the real input state and active turn as presence: it only changes when to speak, not the background judgment.
A complete runnable example:
node packages/sdk/examples/proactive-project.mjs \
--model ./model.json \
--title "Offline backup tool" \
--description "Verifiable, recoverable backups for indie developers" \
--workspace ./packages/sdk --interactiveStopping repeat work
The host can supply a workCheck hook, called after the model decides work and before it is queued. Covered work is not queued and takes the duplicate path, with review backing off; lastDecision, the result and the event carry admission, and the next decision sees previousDecision at the end of its request. A failing hook lets the work through. The Rna host wires it to JEV’s work.coverage judgment.
Other host hooks
messageCheck(decision): a host check of the text of share and ask; when it returns violations the model is asked to rewrite.decisionReasoning(input): the host picks the reasoning effort for this decision by returning{ reasoning }; returning nothing or throwing keeps the model's own setting.policy.notificationMuted: mute. Shares at the inbox and digest tiers go through; inline and notify shares and questions are held.
Long-running
decisionTimeoutMs,actionTimeoutMsand separate failure backoff.archiveHistory({ actions, seenEventIds })must succeed before hot history is trimmed; without an archiver nothing is deleted.- Custom functions that ignore cancellation are isolated; unknown side effects stay
needs_reviewinstead of being “recovered” by retries.
LearningCoordinator
The host provides saveState, review and execute, and feeds lightweight episodes with stable identities through ingest(). tick() persists its claim before calling the model.
Every reflection must choose one outcome:
| Outcome | Meaning |
|---|---|
no_change | Nothing should change under current evidence |
defer | Postponed with a due time |
experiment | A falsifiable hypothesis, queued as a frozen experiment |
Repeated failures on the same evidence back off and enter awaiting_evidence after the third; only genuinely accepted new external evidence lifts it, never a repeated ingest or timer. After a restart, unfinished reviews and experiments are marked interrupted; unknown experiments are never replayed automatically.
EvolutionCoordinator
Freezes the project baseline, calls the host evaluator and runs baseline and candidate on the same cases. Only when improvement, safety, cost and no-regression gates all pass does it emit evolution_candidate_ready. Trial, promotion and rollback are explicit host calls; a candidate model’s own score never counts as evaluation.
The Rna app now defaults to gene-tree evolution: one gene at a time, verified by real work. Paired evaluation of whole candidates remains as an optional deep check.