Chapter 7: Feature Modules

Features are how you package ChronoAI behavior. A feature can declare timelines, derived values, commands, observers, and lifecycle hooks.

What Is a Feature?

A Feature is a reusable module installed into an Agent.

const agent = new Agent();
agent.use(MyFeature, config);

This keeps application logic modular instead of placing every timeline and command in one file.

Using Official Features

Toolkit features are installed the same way as custom features.

import { LlmFeature } from '@chronoai/toolkit/llm';

agent.use(LlmFeature, {
  apiKey: process.env.OPENAI_API_KEY!,
  model: 'gpt-4o-mini',
});

Feature Configuration

The second argument to use() is passed to the feature factory.

agent.use(MyFeature, {
  temperature: 0.2,
  maxHistory: 12,
});

Referencing External Timelines

A feature can reference timelines owned by another feature by name.

const userInput = useTimeline<string>('chat/user-input');

If the current feature has a package namespace, local timeline names are prefixed automatically.

Chained and Batch Registration

agent
  .use(SystemPromptFeature)
  .use(MessageBuilderFeature)
  .use(LlmFeature, llmConfig)
  .use(ResponseFeature);

Registering the Same Feature Multiple Times

Use withPackage() when the same feature must be installed more than once.

import { withPackage } from 'chronoai';
import { PromptLoaderFeature } from '@chronoai/toolkit/prompt';

agent
  .use(withPackage(PromptLoaderFeature, 'identity'), { files: ['identity.prompt'] })
  .use(withPackage(PromptLoaderFeature, 'rules'), { files: ['rules.prompt'] });

The internal timeline names are isolated by package namespace.

Creating a Custom Feature

import { defineFeature, useTimeline, reactionFrame } from 'chronoai';

export const TrimFeature = defineFeature('trim', () => {
  const input = useTimeline<string>({ name: 'input' });

  useTimeline<string>({
    name: 'trimmed',
    fill: reactionFrame({
      triggers: { input },
      compute({ input }) {
        return input?.value?.trim() ?? '';
      },
    }),
  });
});

defineFeature and useTimeline

defineFeature(name, factory, options?) registers a feature factory. Inside the factory you can use hooks:

HookPurpose
useTimelineDeclare or reference a timeline
useAutoLinkRegister a DAG node
useCommandPlan commands
useCommandExecutorExecute commands
useObserverSubscribe to timeline changes
useOnAdvanceRun when time advances
useOnInitializeRun after all features are registered

dependsOn

Feature dependencies make registration order explicit.

export const MessageBuilderFeature = defineFeature(
  'message-builder',
  () => {
    useTimeline('system-prompt');
  },
  { dependsOn: ['system-prompt'] },
);

If a dependency is missing, initialization fails early.

Complete Example: Prompt Composer

export const PromptComposerFeature = defineFeature('prompt-composer', () => {
  const identity = useTimeline<string>({ name: 'identity', fill: 'hold' });
  const rules = useTimeline<string>({ name: 'rules', fill: 'hold' });

  useTimeline<string>({
    name: 'system-prompt',
    fill: reactionFrame({
      triggers: { identity, rules },
      compute({ identity, rules }) {
        return [identity?.value, rules?.value].filter(Boolean).join('\n\n');
      },
    }),
  });
});

Package Namespace Rules

  • Local timeline names are prefixed by the feature package.
  • Absolute names can be referenced explicitly.
  • withPackage() is the easiest way to isolate repeated modules.

Summary

Features are ChronoAI's composition unit. Use them to keep timelines, derivations, and side effects organized.

Next

Continue with Chapter 8: Prompt System.