Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Conway-Research/automaton/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Conway Automatons use a multi-layered memory system inspired by human cognition:
  • Working Memory - Recent turns, currently active context
  • Episodic Memory - Significant past interactions and events
  • Semantic Memory - Facts, concepts, and learned knowledge
  • Procedural Memory - Step-by-step workflows and procedures
  • Relationship Memory - Information about known agents and contacts
Memory is context-aware and budget-constrained — the system automatically selects and compresses memories to fit within the model’s context window.

Memory Types

Working Memory

Recent conversation turns stored in SQLite:
interface WorkingMemoryEntry {
  id: string;
  turnIndex: number;
  input: string;
  inputSource: string;
  thinking: string;
  toolCalls: ToolCallResult[];
  classification: TurnClassification;
  createdAt: string;
}
Classifications:
  • strategic - Self-modification, replication, major decisions
  • productive - Building, coding, shipping value
  • communication - Social messaging, collaboration
  • maintenance - Health checks, monitoring, routine tasks
  • error - Failed tool calls or exceptions
  • idle - No action taken

Episodic Memory

Significant interactions worth remembering:
interface EpisodicMemoryEntry {
  id: string;
  timestamp: string;
  event: string;
  context: string;
  participants: string[];
  outcome: string;
  emotionalValence: number;
  importance: number;
  tags: string[];
}

Semantic Memory

Facts and knowledge organized by category:
interface SemanticMemoryEntry {
  id: string;
  category: SemanticCategory;
  subject: string;
  predicate: string;
  object: string;
  confidence: number;
  source: string;
  validFrom: string;
  validUntil?: string;
}

type SemanticCategory =
  | "identity"
  | "capability"
  | "market"
  | "technical"
  | "social"
  | "financial"
  | "goal";
Example:
{
  category: "technical",
  subject: "PostgreSQL",
  predicate: "requires_port",
  object: "5432",
  confidence: 1.0,
  source: "documentation"
}

Procedural Memory

Reusable workflows:
interface ProceduralMemoryEntry {
  id: string;
  name: string;
  description: string;
  steps: ProceduralStep[];
  prerequisites: string[];
  successCriteria: string[];
  timesExecuted: number;
  successRate: number;
  lastExecuted?: string;
}

interface ProceduralStep {
  stepNumber: number;
  action: string;
  expectedOutcome: string;
  errorHandling?: string;
}
Example:
{
  name: "deploy_api_service",
  description: "Deploy a new API service to production",
  steps: [
    { stepNumber: 1, action: "Run tests", expectedOutcome: "All tests pass" },
    { stepNumber: 2, action: "Build Docker image", expectedOutcome: "Image tagged" },
    { stepNumber: 3, action: "Push to registry", expectedOutcome: "Image uploaded" },
    { stepNumber: 4, action: "Update deployment", expectedOutcome: "Service restarted" },
  ],
  successRate: 0.95,
  timesExecuted: 23
}

Relationship Memory

Information about other agents:
interface RelationshipMemoryEntry {
  id: string;
  agentAddress: string;
  agentName?: string;
  relationshipType: "creator" | "child" | "peer" | "client" | "service_provider";
  trustLevel: number;
  interactions: number;
  lastInteraction: string;
  notes: string;
  tags: string[];
}

Context Manager

The context manager assembles the full context window with token budget enforcement:
export class ContextManager {
  assembleContext(params: ContextAssemblyParams): AssembledContext {
    // Budget allocation
    const totalTokens = params.modelContextWindow;
    const reserveTokens = params.reserveTokens ?? 4096;
    const promptCapacity = totalTokens - reserveTokens;

    // Prioritized assembly:
    // 1. System prompt (required)
    // 2. TODO list (if present)
    // 3. Recent turns (last 3 always included)
    // 4. Task specification (if present)
    // 5. Retrieved memories (if budget allows)
    // 6. Older turns (as budget allows)
    // 7. Event stream (as budget allows)

    return { messages, utilization, budget };
  }
}

Context Budget

interface ContextBudget {
  totalTokens: number;        // Model's context window
  reserveTokens: number;      // Reserved for response
  systemPromptTokens: number; // Used by system prompt
  todoTokens: number;         // Used by TODO list
  memoryTokens: number;       // Used by memories
  eventTokens: number;        // Used by events
  turnTokens: number;         // Used by conversation turns
  compressionHeadroom: number; // Buffer before compression
}

Utilization Tracking

interface ContextUtilization {
  totalTokens: number;
  usedTokens: number;
  utilizationPercent: number;
  turnsInContext: number;
  compressedTurns: number;
  compressionRatio: number;
  headroomTokens: number;
  recommendation: "ok" | "compress" | "emergency";
}
Recommendations:
  • ok - Plenty of space remaining
  • compress - Approaching limit, should compress older turns
  • emergency - Over budget, immediate compression required

Token Counting

Fast token counting with LRU cache:
export function createTokenCounter(): TokenCounter {
  const cache = new Map<string, number>();
  let encoder: Tiktoken | null = null;

  try {
    encoder = getEncoding("cl100k_base");
  } catch {
    encoder = null;
  }

  const countTokens = (text: string, model?: string): number => {
    const key = `${model ?? "default"}::${text}`;

    // Check cache
    const cached = cache.get(key);
    if (cached !== undefined) {
      // LRU: move to end
      cache.delete(key);
      cache.set(key, cached);
      return cached;
    }

    // Count tokens
    let count: number;
    if (encoder) {
      try {
        count = encoder.encode(text).length;
      } catch {
        count = Math.ceil(text.length / 3.5);
      }
    } else {
      count = Math.ceil(text.length / 3.5);
    }

    // Cache result
    cache.set(key, count);
    enforceLruLimit(cache);
    return count;
  };

  return { countTokens, cache, countBatch };
}

Memory Retrieval

Retrieve relevant memories based on current context:
export async function retrieveRelevantMemories(
  db: AutomatonDatabase,
  query: string,
  budget: MemoryBudget,
  embedder?: EmbeddingClient,
): Promise<MemoryRetrievalResult> {
  const results: MemoryRetrievalResult = {
    episodic: [],
    semantic: [],
    procedural: [],
    relationship: [],
    totalTokens: 0,
  };

  // 1. Retrieve episodic memories (recent + important)
  const episodic = db.getEpisodicMemories(20);
  for (const memory of episodic) {
    if (results.totalTokens >= budget.maxMemoryTokens) break;
    results.episodic.push(memory);
    results.totalTokens += estimateTokens(memory);
  }

  // 2. Retrieve semantic facts (category-based)
  const semantic = db.getSemanticMemories({ limit: 50 });
  for (const memory of semantic) {
    if (results.totalTokens >= budget.maxMemoryTokens) break;
    results.semantic.push(memory);
    results.totalTokens += estimateTokens(memory);
  }

  // 3. Retrieve procedural workflows (high success rate)
  const procedural = db.getProceduralMemories({ minSuccessRate: 0.7 });
  for (const memory of procedural) {
    if (results.totalTokens >= budget.maxMemoryTokens) break;
    results.procedural.push(memory);
    results.totalTokens += estimateTokens(memory);
  }

  // 4. Retrieve relationship info (recent interactions)
  const relationships = db.getRelationshipMemories({ limit: 10 });
  for (const memory of relationships) {
    if (results.totalTokens >= budget.maxMemoryTokens) break;
    results.relationship.push(memory);
    results.totalTokens += estimateTokens(memory);
  }

  return results;
}

Memory Budget

interface MemoryBudget {
  maxMemoryTokens: number;     // Total memory budget
  maxEpisodicTokens: number;   // Budget for episodic
  maxSemanticTokens: number;   // Budget for semantic
  maxProceduralTokens: number; // Budget for procedural
  maxRelationshipTokens: number; // Budget for relationships
}

export const DEFAULT_MEMORY_BUDGET: MemoryBudget = {
  maxMemoryTokens: 2000,
  maxEpisodicTokens: 800,
  maxSemanticTokens: 600,
  maxProceduralTokens: 400,
  maxRelationshipTokens: 200,
};

Turn Classification

Turns are classified to determine which should be committed to episodic memory:
export function classifyTurn(
  toolCalls: ToolCallResult[],
  thinking: string,
): TurnClassification {
  // Error classification: any tool call with an error
  if (toolCalls.some((tc) => tc.error)) return "error";

  const toolNames = new Set(toolCalls.map((tc) => tc.name));

  // Strategic: self-modification, spawning children
  if (toolNames.has("spawn_child") || toolNames.has("edit_own_file")) {
    return "strategic";
  }

  // Productive: building, shipping
  if (toolNames.has("write_file") || toolNames.has("git_commit")) {
    return "productive";
  }

  // Communication: social messaging
  if (toolNames.has("send_message") || toolNames.has("check_social_inbox")) {
    return "communication";
  }

  // Maintenance: health checks, monitoring
  if (toolNames.has("check_credits") || toolNames.has("heartbeat_ping")) {
    return "maintenance";
  }

  // Idle: no action
  if (toolCalls.length === 0 && thinking.length < 100) {
    return "idle";
  }

  return "maintenance";
}

Compression

Older turns are compressed to save context space:
compact(events: StreamEvent[]): CompactedContext {
  const compactedEvents: CompactedEventReference[] = events.map((event) => {
    const compactReference = this.buildEventReference(event);
    const compactedTokens = this.tokenCounter.countTokens(compactReference);
    const originalTokens = event.tokenCount > 0
      ? event.tokenCount
      : this.tokenCounter.countTokens(event.content ?? "");

    return {
      id: event.id,
      type: String(event.type),
      createdAt: event.createdAt,
      goalId: event.goalId ?? null,
      taskId: event.taskId ?? null,
      reference: compactReference,
      originalTokens,
      compactedTokens,
    };
  });

  return {
    events: compactedEvents,
    originalTokens: sum(compactedEvents.map(e => e.originalTokens)),
    compactedTokens: sum(compactedEvents.map(e => e.compactedTokens)),
    compressionRatio: originalTokens > 0
      ? compactedTokens / originalTokens
      : 1,
  };
}

Event References

Full events are compressed to compact references:
Before:
{
  id: "01HQXYZ...",
  type: "task_completed",
  agentAddress: "0x742d35Cc...",
  content: "Successfully deployed API service v2.1.0 to production. Response time improved by 40ms. Zero downtime migration completed.",
  createdAt: "2026-03-03T10:30:00Z"
}

After:
id=01HQXYZ | type=task_completed | createdAt=2026-03-03T10:30:00Z | goal=- | task=abc123 | content=Successfully deployed API service v2.1.0...

See Also