Chapter 4: Automatic Derivation
Automatic derivation is where ChronoAI starts to feel different from ordinary state management. You write one timeline, and dependent timelines update through the DAG.
reactionFrame(): Derived Values
A reactionFrame is a computation recipe attached to one timeline.
import { reactionFrame, useTimeline } from 'chronoai';
const rawInput = useTimeline<string>({ name: 'raw-input' });
useTimeline<string>({
name: 'clean-input',
fill: reactionFrame({
triggers: { rawInput },
compute({ rawInput }) {
return rawInput?.value?.trim() ?? '';
},
}),
});
When raw-input changes, clean-input is recomputed.
Key Options
| Option | Purpose |
|---|---|
triggers | Timelines that cause recomputation |
compute | Pure function that returns the derived value |
maxFrames | Optional retention limit for generated auto frames |
compute should be deterministic. Side effects belong in commands, not reaction frames.
Limiting Auto Frames
Derived data can become large. maxFrames limits how many auto-generated frames are retained.
reactionFrame({
triggers: { rawInput },
maxFrames: 10,
compute({ rawInput }) {
return rawInput?.value ?? '';
},
});
This only affects auto frames generated by the reaction. Manual key frames are not removed by this option.
Multiple Dependencies
A reaction can depend on several timelines.
const userInput = useTimeline<string>({ name: 'user-input' });
const systemPrompt = useTimeline<string>({ name: 'system-prompt', fill: 'hold' });
useTimeline<Array<{ role: string; content: string }>>({
name: 'messages-ready',
fill: reactionFrame({
triggers: { userInput, systemPrompt },
compute({ userInput, systemPrompt }) {
if (!userInput?.value) return [];
return [
{ role: 'system', content: systemPrompt?.value ?? '' },
{ role: 'user', content: userInput.value },
];
},
}),
});
deps vs read()
Trigger values tell the graph when the reaction should run. read() can inspect additional timelines, but only triggers establish dependency edges.
Use triggers for real dependencies. Use reads for contextual lookup.
useAutoLink: Global Automatic Links
reactionFrame belongs to one output timeline. useAutoLink is a graph node that can read and write multiple timelines.
useAutoLink('derive-summary', {
triggers: [userInput, contentBody],
compute(ctx) {
const input = ctx.read(userInput)?.value;
const body = ctx.read(contentBody)?.value;
ctx.set('summary', `${input ?? ''} -> ${body ?? ''}`);
},
});
Use reactionFrame for one derived value. Use useAutoLink when a computation writes several outputs or coordinates multiple timelines.
touch() and recalculate()
touch()
touch(ref) marks an upstream timeline as changed even when the value stays the same.
agent.touch('system-prompt');
This is useful when external context changed but the frame value did not.
recalculate()
recalculate(ref) asks the engine to recompute downstream results.
Use it for explicit refresh flows, debugging, or editor tooling.
Summary
reactionFramederives one timeline from triggers.useAutoLinkcreates a graph node that can write multiple outputs.- Triggers define graph dependencies.
- Commands, not reactions, should perform side effects.
Next
Continue with Chapter 5: Lazy Frames and Advanced Fill Strategies.