Chapter 3: Timeline Basics
This chapter explains how the time pointer moves and how to read state across time.
Time Pointer and Relative Time
The Agent keeps a current timepoint. Reads and writes are resolved relative to this pointer unless you pass an explicit timepoint.
await agent.write('user-input', 'first');
await agent.advance();
await agent.write('user-input', 'second');
agent.read('user-input', 'current')?.value; // second
agent.read('user-input', 'prev')?.value; // first
Common timepoint expressions:
| Expression | Meaning |
|---|---|
current | Current pointer |
prev | Previous timepoint |
first | First timepoint |
latest | Latest available frame |
{ offset: -2 } | Two steps before current |
5 | Absolute timepoint 5 |
advance(): Move Forward
advance() moves the pointer to the next timepoint and runs lifecycle hooks.
await agent.write('user-input', 'hello');
await agent.advance();
await agent.write('user-input', 'again');
Use advance() for a new request, a new turn, or any moment where the application should record a new state.
rewind() and seek()
rewind(to?): Move Back and Discard Future Data
rewind() is destructive. It moves the pointer back and drops frames after the target timepoint.
agent.rewind(); // go back one step
agent.rewind(2); // go back to timepoint 2
agent.rewind({ offset: -2 });
Use it for undo-like behavior.
seek(to): Move the Cursor Only
seek() changes the pointer without deleting data.
agent.seek(1);
console.log(agent.read('user-input')?.value);
agent.seek('latest');
Use it for inspection, replay, or debug views.
Reading History
read(ref, '*') returns one entry per timepoint from the first point to the current point.
const frames = agent.read('user-input', '*');
You can also pass a range:
agent.read('user-input', { from: 1, to: 'current' });
agent.read('user-input', { from: 2, to: { offset: -1 } });
Fill Strategies
A timeline does not have to contain an explicit frame at every timepoint. Fill strategies define how empty positions behave.
fill: 'none'
The default. Empty timepoints read as null.
useTimeline<string>({ name: 'scratch' });
fill: 'hold'
The previous frame is carried forward.
const model = useTimeline<string>({ name: 'model', fill: 'hold' });
This is useful for settings such as model name, temperature, current user, or UI state.
Other Fill Options
ChronoAI also supports reaction, lazy, interval, and custom fill strategies. They are covered in later chapters.
collect(): Batch History Into Messages
collect() reads multiple timelines across a range and maps them into a new structure.
const messages = agent.collect({
select: ['user-input', 'content-body'],
from: 1,
to: 'current',
map: {
'user-input': value => ({ role: 'user', content: value }),
'content-body': value => ({ role: 'assistant', content: value }),
},
});
This pattern is common when building chat history for LLM calls.
Complete Example: Multi-turn Chat State
const userInput = createTimelineRef<string>('user-input');
const contentBody = createTimelineRef<string>('content-body');
await agent.write(userInput, 'Hello');
agent.set(contentBody, 'Hi!');
await agent.advance();
await agent.write(userInput, 'What did I say?');
const history = agent.collect({
select: ['user-input', 'content-body'],
from: 1,
to: 'prev',
});
The important idea is that history is not manually copied. It is read from timelines.
Summary
advance()creates a new timepoint.rewind()moves back and deletes future data.seek()moves the cursor without deleting data.read()supports relative and range timepoints.collect()turns timeline history into structured data.
Next
Continue with Chapter 4: Automatic Derivation.