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:
| Hook | Purpose |
|---|---|
useTimeline | Declare or reference a timeline |
useAutoLink | Register a DAG node |
useCommand | Plan commands |
useCommandExecutor | Execute commands |
useObserver | Subscribe to timeline changes |
useOnAdvance | Run when time advances |
useOnInitialize | Run 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.