Deep Agents
AgentContextOrchestratorRetrievalText2SQLToolbox

Core Concepts

Fragment layer, codec system, lazy fragments, and engine orchestration

This page explains the foundational concepts behind @deepagents/context: fragments, codecs, lazy fragments, and the ContextEngine.

Fragment Layer

Fragments are the building blocks of context. In normal usage, you create them through helpers such as:

  • role()
  • hint()
  • term()
  • guardrail()
  • user()
  • assistantText()
  • fragment()

Every fragment has a stable name, optional runtime metadata, and optional lifecycle fields such as type, persist, and codec.

Fragment Composition

Fragments can be nested to create structured context:

fragment(
  'database',
  hint('PostgreSQL 15'),
  hint('Tables: users, orders'),
  fragment(
    'constraints',
    hint('No DELETE without audit'),
  ),
);

That lets you build hierarchical context without writing prompt strings by hand.

Type Guards

The package provides runtime guards for fragment inspection:

GuardPurpose
isFragment(value)Check if a value is a ContextFragment
isFragmentObject(value)Check if a value is a plain object (not array/fragment/primitive)
isMessageFragment(fragment)Check if fragment has type: 'message'
isLazyFragment(fragment)Check if fragment needs lazy ID resolution

Codec System

Codecs let fragments materialize or serialize alternate representations.

interface FragmentCodec {
  decode(): unknown;
  encode(): unknown;
}

The important practical rule is:

  • message fragments are codec-first
  • resolve() uses codec.encode() to produce UIMessage[]
  • save() also persists codec.encode()

Message Lifecycle

┌────────────────────────────────────────────────────────┐
│                    Message Lifecycle                    │
├────────────────────────────────────────────────────────┤
│                                                        │
│  user('Hello')                                         │
│       │                                                │
│       ▼                                                │
│  ┌─────────────────────────────────────────┐          │
│  │ Fragment with attached codec            │          │
│  │   codec.encode() -> UIMessage           │          │
│  │   codec.decode() -> materialized form   │          │
│  └─────────────────────────────────────────┘          │
│       │                                                │
│       ├──── resolve() ────▶ codec.encode() → messages[]│
│       │                                                │
│       └──── save() ───────▶ codec.encode() → store    │
│                                                        │
└────────────────────────────────────────────────────────┘

Built-in Message Helpers

Built-in message helpers attach codecs automatically:

const userMessage = user('Hello');
const assistantMessage = assistantText('Hi there');

If you already have an AI SDK UIMessage, use assistant(...) or message(...).

Lazy Fragments

Lazy fragments defer ID resolution until save() time. This is useful when you want to update the most recent assistant message without knowing its ID ahead of time.

lastAssistantMessage()

The main lazy helper is lastAssistantMessage(...).

Use it for retry and correction flows where the replacement assistant response should overwrite the latest assistant node on the current branch.

context.set(lastAssistantMessage(correctedContent));
await context.save();

During save(), the engine resolves that lazy fragment into a concrete assistant message using the latest assistant ID if one exists.

Engine Orchestration

The ContextEngine maintains two internal lanes:

  • regular fragments for the system prompt
  • pending message fragments for conversation history

set() Routing

set() routes fragments by type:

public set(...fragments: ContextFragment[]) {
  for (const fragment of fragments) {
    if (isMessageFragment(fragment)) {
      this.#pendingMessages.push(fragment);
    } else {
      this.#fragments.push(fragment);
    }
  }
  return this;
}

That separation is why resolve() can render system context and conversation history differently.

Resolve Flow

resolve() produces AI SDK-ready output in this order:

  1. Initialize chat and active branch.
  2. Render regular fragments into systemPrompt.
  3. Load persisted message history from the graph.
  4. Resolve any lazy pending fragments.
  5. Collect message payloads from persisted history and pending message codecs.
  6. Validate the final message list with validateUIMessages(...).

ResolveResult

interface ResolveResult {
  systemPrompt: string;
  messages: UIMessage[];
}

This is the key contract of the package:

  • systemPrompt is renderer output from regular fragments
  • messages is a validated AI SDK UIMessage[]

Why This Design

This split keeps the system clean:

  • renderers only care about regular context structure
  • conversation history stays in graph storage
  • message serialization is centralized in codecs
  • validation happens at the resolve() boundary, where @deepagents/context promises AI SDK-compatible output