composeLifecycle()
Combine run hooks. Optional context compaction.
Lifecycle hooks prepare executions and model requests, select durable context, and coordinate history rewrites. Compose small parts when an agent needs several policies. For example, context compaction uses resolveModelContext and prepareHistoryRewrite.
import { composeLifecycle, createContextCompaction } from "@pai/core";
defineAgent({
lifecycle: composeLifecycle(
createContextCompaction({
threshold: { maxMessages: 40 },
retain: { maxMessages: 6 },
summarize: async ({ messages }) =>
`Earlier turns: ${messages.length} messages.`,
}),
),
});Import
import { composeLifecycle, createContextCompaction } from "@pai/core";Signature
function composeLifecycle(
...parts: AgentLifecycle[]
): AgentLifecycle;Parameters
parts: AgentLifecycle[]
Hook objects with optional prepareRun, prepareModelStep,
resolveModelContext, and prepareHistoryRewrite callbacks.
Returns
A AgentLifecycle.
Pass as defineAgent({ lifecycle }).
Lifecycle methods
| Method | Responsibility |
|---|---|
prepareRun | Prepare each claimed or resumed execution episode, including owner-fenced run/thread metadata and run defaults. |
prepareModelStep | Prepare a model request before SDK parsing, or deliberately respond/block before the provider call. |
resolveModelContext | Select anchored durable history, including stored compaction summaries, for the next model request. |
prepareHistoryRewrite | Prepare metadata changes that commit atomically with regeneration or visibility changes. |
composeLifecycle combines these methods and merges metadata schemas declared by reusable middleware. The runtime owns its model-step loop, checkpoints, tool execution, cancellation, and suspend/resume handling.
Earlier turns' reasoning
Each model request carries every turn's reasoning as it was stored, the way the AI SDK sends a conversation. What earlier turns' reasoning does is the provider's decision: Anthropic's current models keep it in context, and Claude's prompt cache only reads a history sent back as it was. To drop it anyway, return pruned messages from prepareModelStep. Prune only the turns before the latest user message: a model calling tools needs the current turn's reasoning, which the SDK's reasoning: "before-last-message" would remove. pruneMessages drops reasoning text and keeps reasoning files.
import { defineAgent } from "@pai/core";
import { pruneMessages } from "ai";
export const assistantAgent = defineAgent({
// name, model, instructions, tools…
lifecycle: {
prepareModelStep: ({ input }) => {
const turnStart = input.messages
.map((message) => message.role)
.lastIndexOf("user");
if (turnStart <= 0) return { action: "continue" };
return {
action: "continue",
patch: {
messages: [
...pruneMessages({
messages: input.messages.slice(0, turnStart),
reasoning: "all",
}),
...input.messages.slice(turnStart),
],
},
};
},
},
});createContextCompaction()
Built-in compaction: when the transcript grows past a threshold, retain a tail of messages for the next model step.
function createContextCompaction(options: {
summarize: (input) => string | Promise<string>;
threshold?: { maxMessages?: number; maxEstimatedTokens?: number };
retain?: { maxMessages?: number; maxEstimatedTokens?: number };
estimateTokens?: (messages) => number;
}): AgentLifecycle;options: ContextCompactionOptions
summarize: (input) => string | Promise<string>
Summary text for the compacted prefix. Required.
threshold?: { maxMessages?: number; maxEstimatedTokens?: number }
At least one key is required.
Defaults to { maxMessages: 40 }.
maxMessages?: number
maxEstimatedTokens?: number
retain?: { maxMessages?: number; maxEstimatedTokens?: number }
How much recent history to keep.
Defaults to { maxMessages: 6 }.
maxMessages?: number
maxEstimatedTokens?: number
estimateTokens?: (messages) => number
Count tokens for threshold / retain. Messages only.