Chapter 5: Lazy Frames and Advanced Fill Strategies

A lazy frame is evaluated only when it is read. This is useful for expensive formatting, late-bound context, and derived views that do not need to be stored eagerly.

Basic Usage

import { lazyFrame } from 'chronoai';

agent.set('display-name', lazyFrame({
  compute({ read }) {
    const first = read('first-name')?.value ?? '';
    const last = read('last-name')?.value ?? '';
    return `${first} ${last}`.trim();
  },
}));

console.log(agent.read('display-name')?.value);

The frame is placed on the timeline, but its value is computed when read() resolves it.

More Examples

Formatting

agent.set('price-label', lazyFrame({
  compute({ read }) {
    const price = read('price')?.value ?? 0;
    return `$${price.toFixed(2)}`;
  },
}));

Cross-timeline Aggregation

agent.set('profile-card', lazyFrame({
  compute({ read }) {
    return {
      name: read('name')?.value,
      role: read('role')?.value,
      status: read('status')?.value,
    };
  },
}));

Range Collection

Lazy frames can also collect history at read time.

agent.set('conversation-preview', lazyFrame({
  compute({ collect }) {
    return collect({
      select: ['user-input', 'content-body'],
      from: 1,
      to: 'current',
    });
  },
}));

Lazy Frame vs reactionFrame

UseChoose
Value should update when dependencies changereactionFrame
Value should be computed only when readlazyFrame
Value should participate in DAG propagationreactionFrame
Value is a view or formatterlazyFrame

Recursive Read Protection

ChronoAI protects against recursive lazy reads. A lazy frame should not directly or indirectly read itself.

If you hit recursion, split the value into two timelines: one source timeline and one view timeline.

intervalFill

intervalFill produces values based on time intervals.

useTimeline<number>({
  name: 'clock',
  fill: intervalFill({
    interval: 1000,
    fn: () => Date.now(),
  }),
});

It is useful for clocks, polling state, or time-based UI.

customFill

customFill gives full control over how an empty timepoint is resolved.

useTimeline<string>({
  name: 'label',
  fill: customFill({
    fn(ctx) {
      return ctx.read('source')?.value ?? 'empty';
    },
  }),
});

Use it when built-in strategies do not fit.

Summary

  • Lazy frames compute on read.
  • They are good for views, formatting, and late aggregation.
  • Reaction frames are better for graph propagation.
  • Interval and custom fills cover advanced resolution behavior.

Next

Continue with Chapter 6: Command System.