Skip to content

Context

Context provides reactive state management with hierarchical inheritance for AI agents. It combines metadata, rules, tools, and messages with automatic propagation from parent contexts.

Overview

Context is a container class that manages configuration and state for agents through:

  • Reactive Metadata: Key-value store with automatic inheritance
  • Rules: Behavioral guidelines that cascade from parent to child
  • Tools: Function collections with automatic deduplication
  • Messages: Conversation history with complete lineage

Key Features

  • ✅ Hierarchical inheritance (parent → child)
  • ✅ Automatic metadata propagation using broker pattern
  • ✅ Tool deduplication by name (child overrides parent)
  • ✅ Message history composition (parent + child)
  • ✅ Rule aggregation (combined, not replaced)
  • ✅ Type-safe with full TypeScript support

Constructor

new Context(options: ContextOptions)

ContextOptions

interface ContextOptions {
  /** Parent context(s) for hierarchical inheritance */
  context?: Context | Context[];

  /** Metadata instance(s) or plain Record<string, string> objects */
  metadata?: Metadata | Metadata[] | Record<string, string>;

  /** Rule instance(s) or plain strings (auto-converted to new Rule(s)) */
  rules?: Rule | Rule[] | string | string[];

  /** Tool instance(s) for available functions */
  tools?: Tool | Tool[];

  /** Message instance(s) or plain MessageOptions objects (auto-converted) */
  messages?: Message | Message[] | MessageOptions | MessageOptions[];
}

All properties are optional. The constructor accepts plain objects in addition to class instances: - metadata accepts Record<string, string> — each entry is loaded into a new Metadata node. - rules accepts string or string[] — each string is wrapped in new Rule(s). - messages accepts MessageOptions or MessageOptions[] — each plain object is wrapped in new Message(...).

All properties are optional, allowing you to create empty contexts or fully configured ones.

Examples

Empty Context:

import { Context } from '@arcaelas/agent';

const ctx = new Context({});

Basic Context:

import { Context, Metadata, Rule } from '@arcaelas/agent';

// Using class instances
const ctx = new Context({
  metadata: new Metadata().set("app", "MyApp").set("version", "1.0"),
  rules: [new Rule("Maintain professional tone")]
});

// Using plain objects (also valid)
const ctx2 = new Context({
  metadata: { app: "MyApp", version: "1.0" },
  rules: ["Maintain professional tone"],
  messages: [{ role: "user", content: "Hello" }]
});

console.log(ctx.metadata.get("app")); // "MyApp"
console.log(ctx.rules.length);        // 1

Context with Inheritance:

const parent_ctx = new Context({
  metadata: new Metadata().set("company", "Acme Corp"),
  rules: [new Rule("Be helpful and courteous")]
});

const child_ctx = new Context({
  context: parent_ctx,  // Inherits everything from parent
  metadata: new Metadata().set("department", "Sales"),
  rules: [new Rule("Focus on customer needs")]
});

console.log(child_ctx.metadata.get("company"));    // "Acme Corp" (inherited)
console.log(child_ctx.metadata.get("department")); // "Sales" (local)
console.log(child_ctx.rules.length);                // 2 (parent + local)

Multiple Parent Contexts:

const auth_ctx = new Context({
  metadata: new Metadata().set("auth_enabled", "true")
});

const logging_ctx = new Context({
  metadata: new Metadata().set("log_level", "info")
});

const app_ctx = new Context({
  context: [auth_ctx, logging_ctx],  // Inherits from both
  metadata: new Metadata().set("app_name", "Dashboard")
});

console.log(app_ctx.metadata.get("auth_enabled")); // true
console.log(app_ctx.metadata.get("log_level"));    // "info"
console.log(app_ctx.metadata.get("app_name"));     // "Dashboard"

Properties

metadata

readonly metadata: Metadata

Reactive metadata store with automatic inheritance from parent contexts.

Behavior: - Inherits all metadata from parent context(s) - Local metadata can override inherited values - Changes propagate using broker pattern

Example:

const parent = new Context({
  metadata: new Metadata()
    .set("theme", "light")
    .set("lang", "en")
});

const child = new Context({
  context: parent,
  metadata: new Metadata().set("theme", "dark")  // Override
});

console.log(child.metadata.get("theme")); // "dark" (overridden)
console.log(child.metadata.get("lang"));  // "en" (inherited)

rules

get rules(): Rule[]
set rules(rules: Rule[])

Combined rules from parent context(s) and local rules.

Behavior: - Returns parent rules first, then local rules - Setter is idempotent: filters out inherited rules by reference equality before storing locals. Rules aggregate (no deduplication by content).

Example:

const global_ctx = new Context({
  rules: [new Rule("Always be polite")]
});

const sales_ctx = new Context({
  context: global_ctx,
  rules: [new Rule("Focus on benefits")]
});

console.log(sales_ctx.rules.length); // 2

// Replace local rules only
sales_ctx.rules = [
  new Rule("Focus on ROI"),
  new Rule("Use customer testimonials")
];

console.log(sales_ctx.rules.length); // 3 (1 parent + 2 new local)

tools

get tools(): Tool[]
set tools(tools: Tool[])

Combined tools from parent context(s) and local tools with automatic deduplication by name.

Behavior: - Local tools override parent tools with same name (deduplication by name) - Returns unique tools only (no duplicates) - Setter is idempotent: filters out inherited tools by reference equality before storing locals

Example:

import { Tool } from '@arcaelas/agent';

const base_ctx = new Context({
  tools: [
    new Tool("search", async (agent) => "base search"),
    new Tool("analyze", async (agent) => "base analyze")
  ]
});

const enhanced_ctx = new Context({
  context: base_ctx,
  tools: [
    new Tool("search", async (agent) => "enhanced search"),  // Overrides parent
    new Tool("translate", async (agent) => "translation")     // New tool
  ]
});

const all_tools = enhanced_ctx.tools;
console.log(all_tools.map(t => t.name)); // ["analyze", "translate", "search"]

// "search" uses enhanced version, not base version

messages

get messages(): Message[]
set messages(messages: Message[])

Combined message history from parent context(s) and local messages.

Behavior: - Returns parent messages first, then local messages - Maintains chronological order (parent → child) - Setter is idempotent: filters out inherited messages by reference equality before storing locals. This means ctx.messages = ctx.messages.concat(x) is safe — it won't duplicate inherited messages.

Example:

import { Message } from '@arcaelas/agent';

const parent_ctx = new Context({
  messages: [
    new Message({ role: "system", content: "You are a helpful assistant" })
  ]
});

const child_ctx = new Context({
  context: parent_ctx,
  messages: [
    new Message({ role: "user", content: "Hello!" })
  ]
});

console.log(child_ctx.messages.length); // 2

// Idempotent setter — safe to concat without duplicating inherited messages
child_ctx.messages = child_ctx.messages.concat(
  new Message({ role: "assistant", content: "Hi there!" })
);

console.log(child_ctx.messages.length); // 3

Removed methods: appendMessages() and spliceAt() no longer exist. Use the idempotent setter with array spread/concat instead.

Inheritance Rules

Understanding how properties are inherited:

Property Inheritance Behavior Override Behavior Deduplication
metadata Resolved via broker (parent + child) Child overrides parent keys (last writer wins) None — last value at read time
rules Concatenated (parent + child) No override, aggregates None
tools Merged (parent + child) Child overrides by name By name
messages Concatenated (parent + child) No override, aggregates None

Visualization

const parent = new Context({
  metadata: new Metadata().set("a", 1).set("b", 2),
  rules: [new Rule("R1")],
  tools: [new Tool("T1", async (agent) => "parent")],
  messages: [new Message({ role: "system", content: "M1" })]
});

const child = new Context({
  context: parent,
  metadata: new Metadata().set("b", 3).set("c", 4),  // 'b' overrides
  rules: [new Rule("R2")],                             // Adds to R1
  tools: [new Tool("T1", async (agent) => "child")],       // Replaces T1
  messages: [new Message({ role: "user", content: "M2" })]  // Adds to M1
});

// Results:
// metadata.all() = { a: 1, b: 3, c: 4 }
// rules.length = 2 (R1, R2)
// tools.length = 1 (T1 from child)
// messages.length = 2 (M1, M2)

Common Patterns

Pattern: Configuration Layers

Create hierarchical configuration with global → department → team layers:

// Global company context
const company_ctx = new Context({
  metadata: new Metadata()
    .set("company", "Acme Corp")
    .set("compliance", "GDPR"),
  rules: [new Rule("Protect customer privacy")]
});

// Department context
const sales_ctx = new Context({
  context: company_ctx,
  metadata: new Metadata().set("department", "Sales"),
  rules: [new Rule("Focus on customer value")],
  tools: [crm_tool, quote_tool]
});

// Team context
const team_a_ctx = new Context({
  context: sales_ctx,
  metadata: new Metadata().set("team", "Team A").set("region", "EMEA"),
  rules: [new Rule("Respond in customer's timezone")]
});

Pattern: Feature Flags

Use metadata for feature toggles:

const base_ctx = new Context({
  metadata: new Metadata()
    .set("feature_analytics", true)
    .set("feature_experimental", false)
});

const beta_ctx = new Context({
  context: base_ctx,
  metadata: new Metadata().set("feature_experimental", true)  // Enable for beta
});

// Check feature flags
if (beta_ctx.metadata.get("feature_experimental")) {
  // Use experimental features
}

Pattern: Tool Composition

Build specialized tool sets through inheritance:

const basic_tools_ctx = new Context({
  tools: [time_tool, weather_tool]
});

const advanced_tools_ctx = new Context({
  context: basic_tools_ctx,
  tools: [database_tool, api_tool]  // Adds to basic tools
});

const specialized_ctx = new Context({
  context: advanced_tools_ctx,
  tools: [
    new Tool("weather", custom_weather_impl)  // Override weather
  ]
});

Pattern: Conversation Templates

Reuse conversation starters:

const support_template = new Context({
  messages: [
    new Message({
      role: "system",
      content: "You are a customer support specialist. Be helpful and empathetic."
    }),
    new Message({
      role: "assistant",
      content: "Hello! How can I help you today?"
    })
  ]
});

// Create new conversation from template
const customer_session = new Context({
  context: support_template,
  metadata: new Metadata()
    .set("customer_id", "12345")
    .set("priority", "high")
});

Best Practices

1. Keep Contexts Focused

Each context should represent a single concern:

// ✅ Good: Focused contexts
const auth_ctx = new Context({
  metadata: new Metadata().set("auth_enabled", "true")
});

const logging_ctx = new Context({
  metadata: new Metadata().set("log_level", "info")
});

// ❌ Bad: Too many concerns in one context
const everything_ctx = new Context({
  metadata: new Metadata()
    .set("auth_enabled", true)
    .set("log_level", "info")
    .set("theme", "dark")
    .set("cache_ttl", 3600)
    // ... many more unrelated settings
});

2. Use Clear Naming

Name contexts based on their scope:

const global_ctx = new Context({ /* ... */ });
const company_ctx = new Context({ context: global_ctx });
const department_ctx = new Context({ context: company_ctx });
const team_ctx = new Context({ context: department_ctx });
const user_ctx = new Context({ context: team_ctx });

3. Minimize Context Depth

Avoid deeply nested hierarchies (max 3-4 levels):

// ✅ Good: Reasonable depth
app  department  team

// ❌ Bad: Too deep
global  org  region  country  state  city  office  department  team

4. Document Override Behavior

When overriding tools or metadata, document why:

const custom_ctx = new Context({
  context: base_ctx,
  tools: [
    // Override default search with custom implementation
    // for better performance with our database
    new Tool("search", custom_search_implementation)
  ]
});

Type Safety

Context is fully typed with TypeScript:

import { Context, ContextOptions, Metadata, Rule, Tool, Message } from '@arcaelas/agent';

// All types are inferred
const ctx = new Context({
  metadata: new Metadata(),  // Type: Metadata
  rules: [new Rule("...")],  // Type: Rule[]
  tools: [new Tool("...", async (agent) => "")],  // Type: Tool[]
  messages: [new Message({ role: "user", content: "..." })]  // Type: Message[]
});

// Property types are enforced
const metadata: Metadata = ctx.metadata;        // ✅
const rules: Rule[] = ctx.rules;                // ✅
const tools: Tool[] = ctx.tools;                // ✅
const messages: Message[] = ctx.messages;       // ✅

Next: Learn about Metadata →