Cogitator
Advanced

Self-Modifying Agents

Build agents that generate new tools at runtime, adapt reasoning strategies, and evolve their architecture through multi-armed bandits.

Overview

The @cogitator-ai/self-modifying package provides agents that evolve at runtime. They detect capability gaps and generate new tools, monitor their own reasoning to switch strategies, and optimize architecture parameters using bandit algorithms.

pnpm add @cogitator-ai/self-modifying

Quick Start

import { Agent, createLLMBackend } from '@cogitator-ai/core';
import { SelfModifyingAgent } from '@cogitator-ai/self-modifying';

const llm = createLLMBackend('openai', {
  providers: { openai: { apiKey: process.env.OPENAI_API_KEY! } },
});

const agent = new Agent({
  name: 'adaptive-assistant',
  model: 'openai/gpt-6.1-sol',
  instructions: 'Solve problems adaptively.',
});

const selfModifying = new SelfModifyingAgent({
  agent,
  llm,
  config: {
    toolGeneration: { enabled: true, autoGenerate: true },
    metaReasoning: { enabled: true },
    architectureEvolution: { enabled: true },
    constraints: { enabled: true, autoRollback: true },
  },
});

const result = await selfModifying.run('Analyze this CSV and visualize trends');

console.log('Output:', result.output);
console.log('Tools generated:', result.toolsGenerated.length);
console.log('Adaptations:', result.adaptationsMade.length);

The backend's provider prefix (openai/ here) is stripped from the agent model, the agent's temperature and maxTokens form the baseline configuration, and concurrent run() calls on one instance are serialized. Set config.enabled: false to run as a plain tool-calling agent. Pass modificationConstraints to add constraints and availableModels to let architecture evolution switch models.

config.maxInternalTokens bounds the output tokens of every internal LLM call (gap analysis, tool generation and review, architecture evolution), which keeps reasoning models from spending minutes on those steps. Unset leaves the backend default. Meta-reasoning has its own bound, metaReasoning.maxMetaTokens. The components used on their own (GapAnalyzer, ToolGenerator, ToolValidator, CapabilityAnalyzer, ParameterOptimizer) take it as maxTokens.

Tool Self-Generation

When the agent encounters a task requiring capabilities it doesn't have, it generates new tools at runtime.

How It Works

  1. Gap Analysis - LLM compares user intent and the agent's instructions with available tools (including tools generated earlier), identifies missing capabilities
  2. Code Synthesis - Generates a plain JavaScript execute(params) implementation, a description of what the tool does, and a few example inputs
  3. Validation - Static security scanning, test cases in the sandbox, optional LLM review
  4. Constraint Check - The tool must satisfy the modification constraints (sandboxing, size, depth)
  5. Registration - Accepted tools are stored as active, used immediately and loaded into later runs

A generated tool's description is what the model wrote about the tool. When it is empty or only repeats the gap text, the gap's requiredCapability is used, so the next gap analysis sees what the tool does and reuses it instead of generating a duplicate.

Validation

  • Static checks are authoritative: a security rule hit (eval, require, process., prototype tricks, shell or file system access) makes the tool invalid with score 0, whatever a review says.
  • Sandbox tests are authoritative too: any failed case makes the tool invalid. Without explicit test cases, the tool runs on the example inputs it was generated with (they must succeed) and on inputs synthesized from its parameters schema. Synthesized inputs only prove the tool runs, so a descriptive error is accepted for them, while leaving out a required parameter must throw.
  • LLM review (requireLLMValidation) follows the reviewer's verdict. An approving review keeps the tool valid and its security and logic notes become suggestions. A review that recommends revise or reject makes the tool invalid, and its findings are what the next iteration fixes.

Generated code runs in a worker thread inside a fresh vm context without host objects (process, require, timers, host constructors) and with string code generation disabled. Parameters and results cross the boundary as JSON.

Configuration

const selfModifying = new SelfModifyingAgent({
  agent,
  llm,
  config: {
    toolGeneration: {
      enabled: true,
      autoGenerate: true,
      maxToolsPerSession: 3,
      minConfidenceForGeneration: 0.7,
      maxIterationsPerTool: 3,
      requireLLMValidation: true,
      sandboxConfig: {
        enabled: true,
        maxExecutionTime: 5000,
        maxMemory: 50 * 1024 * 1024,
        allowedModules: [],
        isolationLevel: 'strict',
      },
    },
  },
});

Manual Tool Generation

import { GapAnalyzer, ToolGenerator } from '@cogitator-ai/self-modifying';

const gapAnalyzer = new GapAnalyzer({ llm, config: toolGenConfig, model: 'gpt-6.1-sol' });
const toolGenerator = new ToolGenerator({ llm, config: toolGenConfig, model: 'gpt-6.1-sol' });

const analysis = await gapAnalyzer.analyze(
  'Calculate compound interest over 10 years',
  existingTools,
  { instructions: agent.instructions }
);

for (const gap of analysis.gaps) {
  const result = await toolGenerator.generate(gap, existingTools);
  if (result.success && result.tool) {
    console.log('Generated:', result.tool.name);
  }
}

Generated Tool Store

import { InMemoryGeneratedToolStore } from '@cogitator-ai/self-modifying';

const store = new InMemoryGeneratedToolStore();

await store.save(generatedTool);

await store.recordUsage({
  toolId: tool.id,
  timestamp: new Date(),
  success: true,
  executionTime: 150,
});

const tools = await store.list({ status: 'active' });
const similar = await store.findSimilar('calculate interest');

Meta-Reasoning

The meta-reasoning layer monitors the agent's reasoning process and makes strategic adjustments — switching between analytical, creative, systematic, and other modes.

Reasoning Modes

ModeTemperatureUse Case
analytical0.3Logical analysis, debugging
creative0.9Brainstorming, ideation
systematic0.2Step-by-step procedures
intuitive0.6Quick decisions, heuristics
reflective0.4Self-assessment, learning
exploratory0.7Open-ended exploration

Configuration

const selfModifying = new SelfModifyingAgent({
  agent,
  llm,
  config: {
    metaReasoning: {
      enabled: true,
      defaultMode: 'analytical',
      allowedModes: ['analytical', 'creative', 'systematic'],
      modeProfiles: {
        analytical: { temperature: 0.2, depth: 4 },
        creative: { temperature: 1.0 },
      },
      maxMetaAssessments: 5,
      maxAdaptations: 3,
      triggers: ['on_failure', 'on_low_confidence', 'periodic'],
      triggerAfterIterations: 3,
      triggerOnConfidenceDrop: 0.3,
      enableRollback: true,
    },
  },
});

modeProfiles overrides the temperature, depth or parameters of individual modes; each given field is merged over the mode's default in DEFAULT_MODE_PROFILES, so list only what you change. mergeModeProfiles(base, overrides) applies the same merge to profiles of your own.

Inside SelfModifyingAgent, meta-reasoning only intervenes when a step is incomplete (empty or truncated answer). Only triggers listed in triggers fire, and the next attempt uses the adapted mode temperature, parameters or injected context. LLM assessments are validated; recommendations without a valid confidence are ignored.

Direct MetaReasoner Usage

import { MetaReasoner } from '@cogitator-ai/self-modifying';

const metaReasoner = new MetaReasoner({ llm, model: 'gpt-6.1-sol', config: metaReasoningConfig });

const modeConfig = metaReasoner.initializeRun(runId);

const observation = metaReasoner.observe(
  {
    runId,
    iteration: 3,
    goal: 'Analyze data',
    currentMode: 'analytical',
    tokensUsed: 1500,
    timeElapsed: 5000,
    iterationsRemaining: 2,
    budgetRemaining: 0.6,
  },
  insights
);

const assessment = await metaReasoner.assess(observation);

if (assessment.requiresAdaptation) {
  const adaptation = await metaReasoner.adapt(runId, assessment);
  console.log('Switched to:', adaptation?.after?.mode);
}

Architecture Evolution

Optimizes agent parameters (temperature, max tokens, tool strategy, reflection depth and — with availableModels — the model) using multi-armed bandit algorithms. Every run records its outcome for the configuration that was actually used, LLM-proposed candidates are sanitized, and high-risk candidates are discarded.

Strategies

StrategyDescription
ucbUpper Confidence Bound — balanced exploration
thompson_samplingThompson Sampling — probabilistic selection
epsilon_greedyEpsilon-Greedy — random exploration

Parameter Optimizer

import { ParameterOptimizer } from '@cogitator-ai/self-modifying';

const optimizer = new ParameterOptimizer({
  llm,
  model: 'gpt-6.1-sol',
  config: evolutionConfig,
  baseConfig: {
    model: 'gpt-6.1-sol',
    temperature: 0.7,
    maxTokens: 4096,
    toolStrategy: 'sequential',
    reflectionDepth: 0,
  },
  availableModels: ['gpt-6.1-sol', 'gpt-6-luna'],
});

const result = await optimizer.optimize('Complex reasoning task');

console.log('Should adopt:', result.shouldAdopt);
console.log('Recommended config:', result.recommendedConfig);

await optimizer.recordOutcome(result.candidate!.id, result.taskProfile, {
  successRate: 1,
  latency: 1200,
  tokenUsage: 850,
  qualityScore: 0.9,
});

Capability Analyzer

import { CapabilityAnalyzer } from '@cogitator-ai/self-modifying';

const analyzer = new CapabilityAnalyzer({ llm, enableLLMAnalysis: true, model: 'gpt-6.1-sol' });

const profile = await analyzer.analyzeTask('Build a REST API with authentication');

console.log('Complexity:', profile.complexity);
console.log('Domain:', profile.domain);
console.log('Tool intensity:', profile.toolIntensity);

Constraints & Safety

All self-modifications are validated against safety constraints before being applied.

import {
  ModificationValidator,
  DEFAULT_SAFETY_CONSTRAINTS,
  DEFAULT_CAPABILITY_CONSTRAINTS,
  DEFAULT_RESOURCE_CONSTRAINTS,
} from '@cogitator-ai/self-modifying';

const validator = new ModificationValidator({
  constraints: {
    safety: DEFAULT_SAFETY_CONSTRAINTS,
    capability: DEFAULT_CAPABILITY_CONSTRAINTS,
    resource: DEFAULT_RESOURCE_CONSTRAINTS,
    custom: [
      {
        id: 'no-external-apis',
        name: 'No External APIs',
        description: 'External API calls not allowed',
        predicate: (request) => !JSON.stringify(request.changes).includes('fetch'),
      },
    ],
  },
});

const result = await validator.validate({
  type: 'tool_creation',
  target: 'tools',
  changes: { name: 'new-tool', code: '...' },
  reason: 'User requested capability',
});

The default safety constraints carry appliesTo: ['tool_generation', 'tool_creation'], so configuration changes are not checked against tool-only rules. Rules support =, !=, <, <=, >, >=, AND, OR, numbers, booleans, null and quoted strings.

Rollback Manager

import { RollbackManager } from '@cogitator-ai/self-modifying';

const rollbackManager = new RollbackManager({ maxCheckpoints: 10 });

const checkpoint = await rollbackManager.createCheckpoint(
  agentName,
  agentConfig,
  currentTools,
  modifications
);

const restored = await rollbackManager.rollbackTo(checkpoint.id);

Events

Subscribe to self-modification events for observability. Handlers receive typed event data and on() returns an unsubscribe function.

selfModifying.on('tool_generation_started', (e) => {
  console.log('Generating:', e.data.gap.suggestedToolName);
});

selfModifying.on('strategy_changed', (e) => {
  console.log(`Mode: ${e.data.previousMode} → ${e.data.newMode}`);
});

selfModifying.on('run_completed', (e) => {
  console.log('Success:', e.data.success);
});
EventDescription
run_startedSelf-modifying run started
run_completedRun completed (success/failure)
tool_generation_startedStarted generating a new tool
tool_generation_completedTool generation finished
meta_assessmentMeta-reasoning assessment made
strategy_changedReasoning mode switched
architecture_evolvedArchitecture config changed
checkpoint_createdRollback checkpoint created
rollback_performedRolled back to checkpoint

Utilities

extractJson

Extracts the first valid JSON object from a string using balanced-brace matching.

import { extractJson } from '@cogitator-ai/self-modifying';

const raw = 'Here is my analysis: {"onTrack": true} end.';
const json = extractJson(raw); // '{"onTrack": true}'

llmChat

Normalizes LLM backend calls — uses complete() if available, falls back to chat().

import { llmChat } from '@cogitator-ai/self-modifying';

const response = await llmChat(llm, [{ role: 'user', content: 'Analyze this data' }], {
  model: 'gpt-6.1-sol',
});

On this page