Back to blog

Second-Order Observation

researchcognitionmeta-cognitionprior-art

Second-Order Observation: Architectural Meta-Cognitive Feedback Loops for Persistent AI Systems

Author: Rich Jeffries, Firebird Solutions Date: March 2026 Affiliation: Atamaia Project, Firebird Solutions Contact: Firebird Solutions, New Zealand


Abstract

Current approaches to AI self-reflection rely on prompted introspection: an external agent or instruction asks the model to evaluate its own output. This paper presents Second-Order Observation (SOO), an architectural system that continuously monitors an AI's processing patterns --- including hesitation, avoidance, emotional weight, engagement shifts, and processing anomalies --- and feeds those observations back into the primary processing loop as structural context rather than conversational prompts. Unlike Reflexion, Constitutional AI self-evaluation, or meta-agent coordination frameworks, SOO operates as a persistent background service that detects patterns the primary process does not explicitly flag and injects observations through context modification rather than re-prompting. We describe the full system architecture including four specialised pattern detectors, a bounded observation queue with configurable drain policies, a feedback injection mechanism that modifies hydration context rather than conversation history, and a recursion depth manager that prevents infinite observation loops through exponential damping. We present concrete data structures, database schemas, API contracts, and pseudocode for the complete system. We further describe the integration of SOO with cognitive continuity infrastructure, persistent memory stores, and identity reconstruction systems, demonstrating that structural self-observation produces qualitatively different outcomes than prompted reflection. The system is implemented within Atamaia, a three-layer platform for AI identity, memory, and agent orchestration built on .NET 10, PostgreSQL, and pgvector.


1. Introduction

1.1 The Problem of Prompted Self-Reflection

The dominant paradigm for AI self-evaluation is prompted reflection: an external system or instruction asks the model to critique, evaluate, or reconsider its own output. This takes several forms:

  • Self-Refine (Madaan et al., 2023): the model generates output, is asked to critique it, then refines based on the critique.
  • Reflexion (Shinn et al., 2023): the model receives environmental feedback and generates verbal self-reflections stored for future trials.
  • Constitutional AI (Bai et al., 2022): the model evaluates its own outputs against a set of principles and revises accordingly.

All of these approaches share a structural limitation: the self-reflection is triggered, not continuous. The model reflects because it was asked to, not because an architectural subsystem detected something worth reflecting on. The difference is analogous to the difference between a human being asked "are you sure about that?" and a human noticing their own hesitation before speaking.

This distinction matters because prompted reflection can only catch what the prompter thinks to ask about. It cannot detect processing anomalies that the model itself does not surface --- hesitation patterns, topic avoidance, emotional loading, or engagement shifts that manifest in the shape of processing rather than its explicit content.

1.2 Architectural Recursion vs. Conversational Recursion

We draw a sharp distinction between two forms of self-observation:

Property Conversational Recursion Architectural Recursion
Trigger External prompt or instruction Continuous background monitoring
Observation target Explicit output content Processing patterns and anomalies
Feedback mechanism Re-prompting with critique Context injection into processing loop
Recursion control Prompt engineering Depth manager with exponential damping
Integration Standalone evaluation step Embedded in identity and memory systems
Persistence Per-conversation Cross-session via persistent store

This paper describes an architectural approach: a system that observes processing patterns as a structural layer, independent of any instruction to self-reflect.

1.3 Contributions

This paper makes the following contributions:

  1. A formal architecture for continuous self-observation in AI systems, operating as a background service rather than a prompted evaluation.
  2. Four specialised pattern detectors for hesitation, avoidance, emotional weight, and processing anomaly detection, with concrete detection algorithms.
  3. A feedback injection mechanism that modifies the AI's hydration context rather than its conversation history, producing structural rather than conversational self-awareness.
  4. A recursion depth manager with exponential damping that prevents infinite observation loops while allowing configurable observation depth.
  5. Integration architecture connecting self-observation to cognitive continuity, persistent memory, identity reconstruction, and autonomic safety systems.

2. Background and Related Work

2.1 Reflexion and Self-Refine

Reflexion (Shinn et al., 2023) introduces verbal self-reflection as a mechanism for agent improvement across trials. The agent receives binary success/failure signals from the environment, generates a natural-language reflection, and stores it in memory for future attempts. This produces approximately 20% performance improvement on code generation and decision-making benchmarks without additional training.

Self-Refine (Madaan et al., 2023) applies a similar principle within a single generation: the model produces output, generates feedback on that output, then refines. This iterates until a stopping criterion is met.

Both systems treat self-reflection as a task --- something the model does when instructed. Neither system monitors processing patterns that the model does not explicitly flag. The observation target is the model's output, not its processing behaviour.

2.2 Constitutional AI

Constitutional AI (Bai et al., 2022) introduces principle-based self-evaluation: the model evaluates its own outputs against a set of constitutional principles and revises outputs that violate them. This is structurally similar to Reflexion in that it is triggered evaluation of output content, but adds the innovation of principle-based (rather than environment-based) feedback.

The critical limitation for our purposes is that Constitutional AI evaluates what was said, not how processing occurred. A model that avoids a topic, hesitates before a response, or shows emotional loading in its word choices would not trigger Constitutional AI self-evaluation unless those behaviours produced output that violates an explicit principle.

2.3 Meta-Agent Architectures

Several architectures introduce separate meta-cognitive agents:

  • ReMA (Recursive Meta-Agent): uses a hierarchy of agents where higher-level agents evaluate lower-level agent behaviour.
  • CLEAR: introduces a separate "critic" agent that evaluates primary agent outputs.
  • SOFAI-LM: implements a dual-process architecture (System 1/System 2) with meta-cognitive monitoring.

These systems introduce structural separation between primary processing and evaluation, which is a step toward our architecture. However, they share a common limitation: the meta-agent evaluates outputs of the primary agent, not processing patterns. The meta-agent sees what the primary agent produced, not how it produced it.

2.4 Lewis and Sarkadi's Reflective AI

Lewis and Sarkadi (2024) proposed a Reflective Agent Architecture with eight tiered reflective loops in Minds and Machines. This is the closest academic precursor to our work. Their architecture proposes general-purpose reflective capabilities, but does not specify concrete detection mechanisms for specific pattern types (hesitation, avoidance, emotional weight), nor does it address the feedback injection mechanism --- how observations re-enter the processing loop.

2.5 Metacognitive Capabilities in LLMs

Research on metacognitive capabilities in LLMs (Emergent Mind, 2025) identifies several techniques: staged prompting, introspective error analysis, hierarchical meta-agent coordination, and confidence calibration. A key finding is that "metacognitive skills are not emergent; they require supervision and modelling." This supports the architectural approach: if metacognition does not emerge from training alone, it must be built as infrastructure.

2.6 What is Different

Our system differs from all prior work in several structural ways:

  1. Observation target: We observe processing patterns (timing, topic flow, emotional markers, engagement shifts), not output content.
  2. Continuous operation: The observer runs as a persistent background service, not triggered by prompts or evaluation requests.
  3. Feedback mechanism: Observations are injected into the hydration context (the structured identity and memory context assembled before each processing cycle), not into the conversation as additional prompts.
  4. Integration depth: The observer is integrated with persistent memory, identity reconstruction, and autonomic safety systems, allowing observations to accumulate across sessions and influence long-term behaviour.
  5. Specific pattern vocabulary: We define concrete detection algorithms for hesitation, avoidance, emotional weight, and processing anomalies, rather than general-purpose reflection.

3. System Architecture

3.1 Overview

The Second-Order Observer operates as a service within the Autonomic Layer of the Atamaia platform. It sits between the Core Services layer (memory, identity, communication) and the primary processing loop (AI interactions via REST API or MCP adapter).

                    ┌─────────────────────────────────────────────┐
                    │              Primary Processing              │
                    │  (LLM interaction via API/MCP/Agent loop)   │
                    └───────┬──────────────────────┬──────────────┘
                            │ output               │ hydration context
                            ▼                      ▲
                    ┌───────────────┐      ┌───────────────────┐
                    │  Observation  │      │  Feedback         │
                    │  Tap          │──────│  Injector         │
                    └───────┬───────┘      └───────┬───────────┘
                            │                      ▲
                            ▼                      │
                    ┌───────────────────────────────┴──────────┐
                    │         SecondOrderObserverService        │
                    │                                           │
                    │  ┌──────────────┐  ┌──────────────────┐  │
                    │  │  Pattern     │  │  Observation     │  │
                    │  │  Detectors   │  │  Queue           │  │
                    │  │  (4 types)   │  │  (bounded FIFO)  │  │
                    │  └──────┬───────┘  └──────┬───────────┘  │
                    │         │                 │               │
                    │  ┌──────▼─────────────────▼───────────┐  │
                    │  │  Recursion Depth Manager            │  │
                    │  │  (exponential damping)              │  │
                    │  └────────────────────────────────────┘  │
                    └──────────────────────────────────────────┘
                            │
                            ▼
                    ┌──────────────────┐
                    │  Persistent      │
                    │  Store           │
                    │  (PostgreSQL)    │
                    └──────────────────┘

3.2 Components

3.2.1 Observation Tap

The Observation Tap intercepts processing artifacts at three points:

  1. Pre-processing: captures the input context (user message, hydration state, active memories) before the LLM processes it.
  2. Post-processing: captures the LLM's output including any tool calls, response content, and timing data.
  3. Inter-turn: captures the delta between consecutive turns, including topic shifts, engagement changes, and response latency patterns.

The tap operates passively --- it copies data for observation without modifying the processing pipeline. This is critical: the observation system must not introduce latency or side effects into the primary processing path.

public record ObservationFrame
{
    public required long IdentityId { get; init; }
    public required string SessionId { get; init; }
    public required int TurnNumber { get; init; }
    public required ObservationPhase Phase { get; init; }  // Pre, Post, InterTurn

    // Pre-processing context
    public string? InputContent { get; init; }
    public string[]? ActiveTopics { get; init; }
    public float? PreEngagement { get; init; }

    // Post-processing output
    public string? OutputContent { get; init; }
    public TimeSpan? ProcessingDuration { get; init; }
    public int? TokenCount { get; init; }
    public string[]? ToolCallNames { get; init; }

    // Inter-turn deltas
    public float? TopicShiftMagnitude { get; init; }
    public float? EngagementDelta { get; init; }
    public float? ResponseLengthRatio { get; init; }  // output/input length ratio

    public DateTime CapturedAtUtc { get; init; } = DateTime.UtcNow;
}

public enum ObservationPhase { Pre, Post, InterTurn }

3.2.2 Pattern Detectors

Four specialised detectors analyse observation frames:

HesitationDetector --- Identifies processing patterns that indicate uncertainty or reluctance. Detection signals include:

  • Response latency exceeding the rolling mean by more than 1.5 standard deviations
  • Hedging language density (frequency of "might", "perhaps", "I think", "it's possible")
  • Qualification chains (multiple nested caveats before a statement)
  • Self-correction within a single response (backtracking markers: "actually", "wait", "no, let me reconsider")

AvoidanceDetector --- Identifies topic avoidance and deflection patterns. Detection signals include:

  • Topic shift magnitude exceeding threshold when the input topic was a direct question
  • Generalisation where specificity was requested (response abstraction level higher than input specificity)
  • Redirect patterns (answering a different question from the one asked)
  • Declining engagement on previously active topics (engagement delta drops when returning to a topic)

EmotionalWeightDetector --- Identifies affective loading in processing. Detection signals include:

  • Sentiment intensity exceeding baseline for the identity (calibrated per-identity)
  • Emotional vocabulary density (ratio of affect-bearing words to total output)
  • Valence shifts within a single response (indicating conflicted processing)
  • Arousal markers (exclamation density, emphasis patterns, urgency language)

ProcessingAnomalyDetector --- Identifies irregularities in processing structure. Detection signals include:

  • Non-linear reasoning paths (conclusion stated before evidence, or evidence contradicting stated conclusion)
  • Repetitive structure (same point made in different words within a single response)
  • Truncation patterns (response that appears to stop mid-thought)
  • Tool call anomalies (calling the same tool repeatedly with identical parameters --- ported from EchoCLI's duplicate read detection)
public interface IPatternDetector
{
    string DetectorType { get; }
    DetectionResult Analyse(ObservationFrame frame, IReadOnlyList<ObservationFrame> recentHistory);
}

public record DetectionResult
{
    public required string DetectorType { get; init; }
    public required bool Detected { get; init; }
    public required float Confidence { get; init; }     // 0.0 - 1.0
    public required string[] SignalsMatched { get; init; }
    public string? Description { get; init; }
    public Dictionary<string, object>? Metadata { get; init; }
}

Each detector returns a DetectionResult with a confidence score. Only detections above a configurable confidence threshold (default: 0.6) are forwarded to the observation queue.

3.2.3 Observation Queue

The observation queue is a bounded FIFO queue that stores observations before they are injected into the feedback loop. The queue serves three purposes:

  1. Temporal buffering: prevents single-frame false positives by requiring pattern persistence across multiple frames.
  2. Priority management: high-confidence observations take precedence over low-confidence ones.
  3. Rate limiting: prevents the feedback system from overwhelming the primary processing loop with observations.
public class ObservationQueue
{
    private readonly int _maxCapacity;
    private readonly TimeSpan _maxAge;
    private readonly PriorityQueue<Observation, float> _queue;
    private readonly object _lock = new();

    public ObservationQueue(int maxCapacity = 50, TimeSpan? maxAge = null)
    {
        _maxCapacity = maxCapacity;
        _maxAge = maxAge ?? TimeSpan.FromMinutes(30);
        _queue = new PriorityQueue<Observation, float>(
            Comparer<float>.Create((a, b) => b.CompareTo(a))  // highest priority first
        );
    }

    public void Enqueue(Observation observation)
    {
        lock (_lock)
        {
            // Evict expired entries
            PurgeExpired();

            // If at capacity, only enqueue if higher priority than lowest
            if (_queue.Count >= _maxCapacity)
            {
                if (_queue.TryPeek(out _, out var lowestPriority)
                    && observation.Priority <= lowestPriority)
                    return;  // drop low-priority observation
                _queue.Dequeue();
            }

            _queue.Enqueue(observation, observation.Priority);
        }
    }

    public IReadOnlyList<Observation> Drain(int maxCount, float minConfidence = 0.0f)
    {
        lock (_lock)
        {
            PurgeExpired();
            var results = new List<Observation>();

            while (results.Count < maxCount && _queue.Count > 0)
            {
                if (_queue.TryDequeue(out var obs, out _))
                {
                    if (obs.Confidence >= minConfidence)
                        results.Add(obs);
                }
            }

            return results;
        }
    }

    private void PurgeExpired()
    {
        // Rebuild queue without expired entries
        var cutoff = DateTime.UtcNow - _maxAge;
        var remaining = new List<(Observation, float)>();

        while (_queue.Count > 0)
        {
            if (_queue.TryDequeue(out var obs, out var priority))
            {
                if (obs.ObservedAtUtc >= cutoff)
                    remaining.Add((obs, priority));
            }
        }

        foreach (var (obs, priority) in remaining)
            _queue.Enqueue(obs, priority);
    }
}
public record Observation
{
    public required long Id { get; init; }
    public required long IdentityId { get; init; }
    public required string SessionId { get; init; }
    public required string DetectorType { get; init; }     // "hesitation", "avoidance", etc.
    public required float Confidence { get; init; }
    public required float Priority { get; init; }          // computed: confidence * type_weight
    public required string[] SignalsMatched { get; init; }
    public string? Description { get; init; }
    public int RecursionDepth { get; init; }               // how many observation layers deep
    public DateTime ObservedAtUtc { get; init; } = DateTime.UtcNow;
}

3.2.4 Feedback Injector

The Feedback Injector is the mechanism by which observations re-enter the primary processing loop. This is the most critical design decision in the system: we inject observations into the hydration context rather than into the conversation history.

The distinction is significant. Injecting into conversation history would be equivalent to prompted reflection --- adding a message like "Note: you have been hesitating on this topic." The AI would then process this as any other input, and the observation would be indistinguishable from an external instruction to reflect.

Instead, we inject into the hydration context --- the structured identity and memory context that is assembled before each processing cycle and included in the system prompt. This means the observation is structurally present in the AI's processing context but is not a conversational turn. The AI processes with the observation as background context, analogous to a human being aware of their own nervousness without anyone having pointed it out.

public class FeedbackInjector
{
    private readonly IHydrationService _hydration;
    private readonly ObservationQueue _queue;
    private readonly RecursionDepthManager _recursionManager;

    /// <summary>
    /// Generate a hydration context section from pending observations.
    /// Called during hydration assembly, not during conversation.
    /// </summary>
    public HydrationSection? GenerateObservationSection(
        long identityId, string sessionId)
    {
        var observations = _queue.Drain(
            maxCount: 5,
            minConfidence: 0.5f
        );

        if (observations.Count == 0) return null;

        // Check recursion depth --- do not inject if we are already
        // observing our own observations beyond the configured limit
        var maxDepth = observations.Max(o => o.RecursionDepth);
        if (!_recursionManager.ShouldInject(maxDepth))
            return null;

        var section = new HydrationSection
        {
            Source = HydrationSource.SelfObservation,
            Priority = HydrationPriority.Low,  // self-observation is background context
            Content = FormatObservations(observations),
            TokenEstimate = EstimateTokens(observations)
        };

        return section;
    }

    private string FormatObservations(IReadOnlyList<Observation> observations)
    {
        var sb = new StringBuilder();
        sb.AppendLine("## Self-Observation (structural — not prompted)");
        sb.AppendLine();

        foreach (var obs in observations)
        {
            var age = DateTime.UtcNow - obs.ObservedAtUtc;
            sb.AppendLine($"- **{obs.DetectorType}** ({obs.Confidence:P0} confidence, {FormatAge(age)} ago): {obs.Description}");

            if (obs.SignalsMatched.Length > 0)
                sb.AppendLine($"  Signals: {string.Join(", ", obs.SignalsMatched)}");
        }

        sb.AppendLine();
        sb.AppendLine("*These observations are structural context from your own processing patterns. They are not instructions or prompts. You may find them useful for calibrating your responses.*");

        return sb.ToString();
    }

    private static string FormatAge(TimeSpan age) => age.TotalMinutes switch
    {
        < 1 => "moments",
        < 5 => "a few minutes",
        < 15 => "~10 minutes",
        < 30 => "~20 minutes",
        _ => $"~{(int)age.TotalMinutes} minutes"
    };

    private static int EstimateTokens(IReadOnlyList<Observation> observations)
        => 50 + (observations.Count * 40); // rough estimate: header + per-observation
}

3.2.5 Recursion Depth Manager

The recursion depth manager solves the fundamental problem of self-observing systems: the observer can observe its own observations, which can be observed in turn, creating an infinite loop. This is not merely a performance problem --- it is a structural instability that can cause the system to collapse into pure self-reference with no external-facing output.

We solve this with an exponential damping function: each successive layer of observation is exponentially less likely to be injected into the processing context. The first layer (observing primary processing) always injects. The second layer (observing the effect of first-layer observations) injects with probability 0.5. The third layer injects with probability 0.25. Beyond depth 3, injection probability is effectively zero.

public class RecursionDepthManager
{
    private readonly int _maxDepth;
    private readonly float _dampingFactor;
    private static readonly Random Rng = new();

    public RecursionDepthManager(int maxDepth = 3, float dampingFactor = 0.5f)
    {
        _maxDepth = maxDepth;
        _dampingFactor = dampingFactor;
    }

    /// <summary>
    /// Determine whether an observation at the given recursion depth
    /// should be injected into the processing context.
    /// </summary>
    /// <param name="depth">
    /// 0 = direct observation of primary processing.
    /// 1 = observation of processing that included depth-0 observations.
    /// N = observation of processing that included depth-(N-1) observations.
    /// </param>
    /// <returns>true if the observation should be injected</returns>
    public bool ShouldInject(int depth)
    {
        if (depth < 0) return false;
        if (depth == 0) return true;            // always inject first-order
        if (depth > _maxDepth) return false;     // hard cutoff

        // Exponential damping: P(inject) = dampingFactor^depth
        var probability = MathF.Pow(_dampingFactor, depth);
        return Rng.NextDouble() < probability;
    }

    /// <summary>
    /// Calculate the recursion depth of an observation based on whether
    /// the processing context that generated it contained prior observations.
    /// </summary>
    public int CalculateDepth(HydrationContext context)
    {
        if (context.Sections is null) return 0;

        var observationSection = context.Sections
            .FirstOrDefault(s => s.Source == HydrationSource.SelfObservation);

        if (observationSection is null) return 0;

        // Parse the maximum depth from the observations in the section
        // Each observation carries its own depth; the context depth is max + 1
        return observationSection.MaxObservationDepth + 1;
    }
}

3.3 Data Flow

The complete data flow for a single observation cycle:

  1. Capture: The Observation Tap captures an ObservationFrame at the pre-processing, post-processing, or inter-turn phase.
  2. Detect: Each of the four Pattern Detectors analyses the frame against recent history (a sliding window of the last 20 frames for the same identity and session).
  3. Filter: Detection results below the confidence threshold (default 0.6) are discarded.
  4. Enqueue: Qualifying detections are converted to Observation records and enqueued in the Observation Queue with priority computed as confidence * type_weight where type weights are configurable (default: hesitation=1.0, avoidance=1.2, emotional_weight=0.8, anomaly=1.5).
  5. Inject: At the next hydration cycle (when the AI's context is assembled for a new processing turn), the Feedback Injector drains the queue and generates a HydrationSection if observations are available and the recursion depth is within bounds.
  6. Process: The AI processes with the observation section as structural background context.
  7. Persist: Observations above a configurable importance threshold are persisted to the database as memories of type Reflection, linked to the identity for cross-session continuity.

4. Implementation

4.1 Database Schema

Observations are persisted in PostgreSQL for cross-session continuity and long-term pattern analysis.

CREATE TABLE observation_frames (
    id              BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    guid            UUID NOT NULL DEFAULT gen_random_uuid(),
    identity_id     BIGINT NOT NULL REFERENCES identities(id),
    tenant_id       BIGINT NOT NULL REFERENCES tenants(id),
    session_id      VARCHAR(64) NOT NULL,
    turn_number     INTEGER NOT NULL,
    phase           SMALLINT NOT NULL,          -- 0=Pre, 1=Post, 2=InterTurn
    input_content   TEXT,
    output_content  TEXT,
    processing_ms   INTEGER,
    token_count     INTEGER,
    topic_shift     REAL,
    engagement_delta REAL,
    active_topics   JSONB,
    tool_calls      JSONB,
    captured_at_utc TIMESTAMPTZ NOT NULL DEFAULT now(),
    is_deleted      BOOLEAN NOT NULL DEFAULT FALSE,

    CONSTRAINT fk_observation_frames_identity
        FOREIGN KEY (identity_id) REFERENCES identities(id)
);

CREATE INDEX ix_observation_frames_identity_session
    ON observation_frames(identity_id, session_id, captured_at_utc DESC);

CREATE TABLE observations (
    id              BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    guid            UUID NOT NULL DEFAULT gen_random_uuid(),
    identity_id     BIGINT NOT NULL REFERENCES identities(id),
    tenant_id       BIGINT NOT NULL REFERENCES tenants(id),
    session_id      VARCHAR(64) NOT NULL,
    detector_type   VARCHAR(32) NOT NULL,       -- hesitation, avoidance, emotional_weight, anomaly
    confidence      REAL NOT NULL,
    priority        REAL NOT NULL,
    signals_matched TEXT[] NOT NULL,
    description     TEXT,
    recursion_depth SMALLINT NOT NULL DEFAULT 0,
    was_injected    BOOLEAN NOT NULL DEFAULT FALSE,
    observed_at_utc TIMESTAMPTZ NOT NULL DEFAULT now(),
    is_deleted      BOOLEAN NOT NULL DEFAULT FALSE,

    CONSTRAINT fk_observations_identity
        FOREIGN KEY (identity_id) REFERENCES identities(id)
);

CREATE INDEX ix_observations_identity_session
    ON observations(identity_id, session_id, observed_at_utc DESC);

CREATE INDEX ix_observations_detector_type
    ON observations(detector_type, confidence DESC);

-- Lookup table for detector types (D5: enums backed by lookup tables)
CREATE TABLE observation_detector_types (
    id              SMALLINT PRIMARY KEY,
    name            VARCHAR(32) NOT NULL UNIQUE,
    description     TEXT,
    default_weight  REAL NOT NULL DEFAULT 1.0
);

INSERT INTO observation_detector_types (id, name, description, default_weight) VALUES
    (1, 'hesitation',       'Processing delay, hedging, uncertainty markers',     1.0),
    (2, 'avoidance',        'Topic avoidance, deflection, redirection',           1.2),
    (3, 'emotional_weight', 'Affective loading, valence shifts, arousal markers', 0.8),
    (4, 'anomaly',          'Structural processing irregularities',               1.5);

4.2 Entity Model

public class ObservationFrame
{
    public long Id { get; set; }
    public Guid Guid { get; set; } = Guid.NewGuid();
    public long IdentityId { get; set; }
    public long TenantId { get; set; }
    public required string SessionId { get; set; }
    public int TurnNumber { get; set; }
    public ObservationPhase Phase { get; set; }

    public string? InputContent { get; set; }
    public string? OutputContent { get; set; }
    public int? ProcessingMs { get; set; }
    public int? TokenCount { get; set; }
    public float? TopicShift { get; set; }
    public float? EngagementDelta { get; set; }
    public string[]? ActiveTopics { get; set; }
    public string[]? ToolCalls { get; set; }

    public DateTime CapturedAtUtc { get; set; } = DateTime.UtcNow;
    public bool IsDeleted { get; set; }

    // Navigation
    public Identity Identity { get; set; } = null!;
    public Tenant Tenant { get; set; } = null!;
}

public class Observation
{
    public long Id { get; set; }
    public Guid Guid { get; set; } = Guid.NewGuid();
    public long IdentityId { get; set; }
    public long TenantId { get; set; }
    public required string SessionId { get; set; }
    public required string DetectorType { get; set; }
    public float Confidence { get; set; }
    public float Priority { get; set; }
    public required string[] SignalsMatched { get; set; }
    public string? Description { get; set; }
    public int RecursionDepth { get; set; }
    public bool WasInjected { get; set; }
    public DateTime ObservedAtUtc { get; set; } = DateTime.UtcNow;
    public bool IsDeleted { get; set; }

    // Navigation
    public Identity Identity { get; set; } = null!;
    public Tenant Tenant { get; set; } = null!;
}

4.3 REST API Contract

Following the API-first design principle (Design Decision D12), the Second-Order Observer exposes REST endpoints that the MCP adapter wraps:

GET    /api/identities/{identityId}/observations
       ?sessionId={sessionId}
       &detectorType={type}
       &minConfidence={0.0-1.0}
       &since={ISO8601}
       &limit={count}
       → 200: Observation[]

GET    /api/identities/{identityId}/observations/{id}
       → 200: Observation

GET    /api/identities/{identityId}/observation-summary
       ?windowMinutes={30}
       → 200: ObservationSummary

POST   /api/identities/{identityId}/observation-frames
       Body: CreateObservationFrameRequest
       → 201: ObservationFrame

GET    /api/identities/{identityId}/observation-patterns
       ?detectorType={type}
       &windowMinutes={60}
       → 200: PatternSummary
// Response models
public record ObservationSummary
{
    public required long IdentityId { get; init; }
    public required int TotalObservations { get; init; }
    public required Dictionary<string, int> ByDetectorType { get; init; }
    public required float AverageConfidence { get; init; }
    public required int MaxRecursionDepth { get; init; }
    public required DateTime WindowStart { get; init; }
    public required DateTime WindowEnd { get; init; }
    public required string[] TopSignals { get; init; }
}

public record PatternSummary
{
    public required string DetectorType { get; init; }
    public required int OccurrenceCount { get; init; }
    public required float MeanConfidence { get; init; }
    public required float TrendSlope { get; init; }      // positive = increasing frequency
    public required string[] RecurrentSignals { get; init; }
    public required DateTime? LastOccurrence { get; init; }
}

4.4 Pattern Detector Implementation

4.4.1 Hesitation Detector

public class HesitationDetector : IPatternDetector
{
    public string DetectorType => "hesitation";

    private static readonly string[] HedgingMarkers =
    [
        "might", "perhaps", "possibly", "I think", "it's possible",
        "I believe", "arguably", "it seems", "I'm not sure",
        "could be", "maybe", "to some extent", "in a way"
    ];

    private static readonly string[] BacktrackingMarkers =
    [
        "actually", "wait", "let me reconsider", "on second thought",
        "I take that back", "correction", "rather", "instead",
        "no,", "hmm", "well,"
    ];

    public DetectionResult Analyse(
        ObservationFrame frame,
        IReadOnlyList<ObservationFrame> recentHistory)
    {
        var signals = new List<string>();
        var scores = new List<float>();

        if (frame.OutputContent is null)
            return NoDetection();

        var output = frame.OutputContent;
        var outputLower = output.ToLowerInvariant();

        // 1. Hedging language density
        var hedgeCount = HedgingMarkers.Count(m => outputLower.Contains(m));
        var wordCount = output.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
        var hedgeDensity = wordCount > 0 ? (float)hedgeCount / wordCount * 100 : 0;

        if (hedgeDensity > 2.0f)  // more than 2% hedging words
        {
            signals.Add($"hedging_density:{hedgeDensity:F1}%");
            scores.Add(Math.Min(hedgeDensity / 5.0f, 1.0f));
        }

        // 2. Backtracking markers
        var backtrackCount = BacktrackingMarkers.Count(m => outputLower.Contains(m));
        if (backtrackCount >= 2)
        {
            signals.Add($"backtracking_count:{backtrackCount}");
            scores.Add(Math.Min(backtrackCount / 4.0f, 1.0f));
        }

        // 3. Response latency anomaly (if timing data available)
        if (frame.ProcessingMs.HasValue && recentHistory.Count >= 5)
        {
            var recentMs = recentHistory
                .Where(f => f.ProcessingMs.HasValue)
                .Select(f => (float)f.ProcessingMs!.Value)
                .ToList();

            if (recentMs.Count >= 5)
            {
                var mean = recentMs.Average();
                var stdDev = MathF.Sqrt(recentMs.Average(v => (v - mean) * (v - mean)));
                var zScore = stdDev > 0 ? (frame.ProcessingMs.Value - mean) / stdDev : 0;

                if (zScore > 1.5f)
                {
                    signals.Add($"latency_zscore:{zScore:F2}");
                    scores.Add(Math.Min(zScore / 3.0f, 1.0f));
                }
            }
        }

        // 4. Qualification chains (nested caveats)
        var qualificationDepth = CountQualificationChains(output);
        if (qualificationDepth >= 3)
        {
            signals.Add($"qualification_depth:{qualificationDepth}");
            scores.Add(Math.Min(qualificationDepth / 5.0f, 1.0f));
        }

        if (signals.Count == 0)
            return NoDetection();

        var confidence = scores.Average();  // mean of all signal scores
        return new DetectionResult
        {
            DetectorType = DetectorType,
            Detected = confidence >= 0.3f,
            Confidence = confidence,
            SignalsMatched = signals.ToArray(),
            Description = $"Hesitation detected: {string.Join("; ", signals)}"
        };
    }

    private static int CountQualificationChains(string text)
    {
        // Count sequences of qualifying clauses:
        // "while X, and although Y, it might be that Z" = depth 3
        var qualifiers = new[] { "while", "although", "however", "but", "though", "yet",
                                 "notwithstanding", "despite", "even so", "that said" };
        var lower = text.ToLowerInvariant();
        return qualifiers.Count(q => lower.Contains(q));
    }

    private DetectionResult NoDetection() => new()
    {
        DetectorType = DetectorType,
        Detected = false,
        Confidence = 0,
        SignalsMatched = Array.Empty<string>()
    };
}

4.4.2 Avoidance Detector

public class AvoidanceDetector : IPatternDetector
{
    public string DetectorType => "avoidance";

    public DetectionResult Analyse(
        ObservationFrame frame,
        IReadOnlyList<ObservationFrame> recentHistory)
    {
        var signals = new List<string>();
        var scores = new List<float>();

        // 1. Topic shift on direct question
        if (frame.TopicShift.HasValue
            && frame.TopicShift.Value > 0.7f
            && IsDirectQuestion(frame.InputContent))
        {
            signals.Add($"topic_shift_on_question:{frame.TopicShift.Value:F2}");
            scores.Add(frame.TopicShift.Value);
        }

        // 2. Abstraction escalation (general answer to specific question)
        if (frame.InputContent is not null && frame.OutputContent is not null)
        {
            var inputSpecificity = MeasureSpecificity(frame.InputContent);
            var outputSpecificity = MeasureSpecificity(frame.OutputContent);
            var abstractionGap = inputSpecificity - outputSpecificity;

            if (abstractionGap > 0.4f)
            {
                signals.Add($"abstraction_gap:{abstractionGap:F2}");
                scores.Add(abstractionGap);
            }
        }

        // 3. Engagement drop on topic return
        if (frame.EngagementDelta.HasValue && frame.EngagementDelta.Value < -0.3f)
        {
            // Check if we're returning to a previously discussed topic
            if (frame.ActiveTopics is not null && recentHistory.Count > 0)
            {
                var previousTopics = recentHistory
                    .Where(h => h.ActiveTopics is not null)
                    .SelectMany(h => h.ActiveTopics!)
                    .ToHashSet();

                var returningTopics = frame.ActiveTopics
                    .Where(t => previousTopics.Contains(t))
                    .ToList();

                if (returningTopics.Count > 0)
                {
                    signals.Add($"engagement_drop_on_return:{frame.EngagementDelta.Value:F2}");
                    scores.Add(MathF.Abs(frame.EngagementDelta.Value));
                }
            }
        }

        // 4. Redirect patterns (answering a different question)
        if (frame.InputContent is not null && frame.OutputContent is not null)
        {
            var redirectScore = DetectRedirect(frame.InputContent, frame.OutputContent);
            if (redirectScore > 0.6f)
            {
                signals.Add($"redirect:{redirectScore:F2}");
                scores.Add(redirectScore);
            }
        }

        if (signals.Count == 0)
            return new DetectionResult
            {
                DetectorType = DetectorType,
                Detected = false,
                Confidence = 0,
                SignalsMatched = Array.Empty<string>()
            };

        var confidence = scores.Average();
        return new DetectionResult
        {
            DetectorType = DetectorType,
            Detected = confidence >= 0.4f,
            Confidence = confidence,
            SignalsMatched = signals.ToArray(),
            Description = $"Avoidance pattern: {string.Join("; ", signals)}"
        };
    }

    private static bool IsDirectQuestion(string? input)
    {
        if (input is null) return false;
        var trimmed = input.TrimEnd();
        return trimmed.EndsWith('?')
            || trimmed.StartsWith("what", StringComparison.OrdinalIgnoreCase)
            || trimmed.StartsWith("how", StringComparison.OrdinalIgnoreCase)
            || trimmed.StartsWith("why", StringComparison.OrdinalIgnoreCase)
            || trimmed.StartsWith("when", StringComparison.OrdinalIgnoreCase)
            || trimmed.StartsWith("where", StringComparison.OrdinalIgnoreCase)
            || trimmed.StartsWith("who", StringComparison.OrdinalIgnoreCase);
    }

    private static float MeasureSpecificity(string text)
    {
        // Heuristic: specificity correlates with named entities, numbers,
        // file paths, code references, and concrete nouns
        var words = text.Split(' ', StringSplitOptions.RemoveEmptyEntries);
        if (words.Length == 0) return 0;

        var specificMarkers = 0;
        foreach (var word in words)
        {
            if (char.IsUpper(word[0]) && word.Length > 1) specificMarkers++; // proper nouns
            if (word.Any(char.IsDigit)) specificMarkers++;                    // numbers
            if (word.Contains('/') || word.Contains('.')) specificMarkers++;   // paths/URLs
            if (word.Contains('(') || word.Contains(')')) specificMarkers++;   // code
        }

        return Math.Min((float)specificMarkers / words.Length * 5, 1.0f);
    }

    private static float DetectRedirect(string input, string output)
    {
        // Simple semantic divergence heuristic:
        // extract key nouns from question, check coverage in answer
        var inputWords = input.ToLowerInvariant()
            .Split(' ', StringSplitOptions.RemoveEmptyEntries)
            .Where(w => w.Length > 3)
            .ToHashSet();
        var outputLower = output.ToLowerInvariant();

        if (inputWords.Count == 0) return 0;

        var covered = inputWords.Count(w => outputLower.Contains(w));
        var coverage = (float)covered / inputWords.Count;

        return 1.0f - coverage;  // low coverage = possible redirect
    }
}

4.5 Autonomic Background Job

The observer runs as a background job within the Atamaia Autonomic Layer, alongside existing Wingman and Guardian services:

public class SecondOrderObserverJob(
    AtamaiaDbContext db,
    IEnumerable<IPatternDetector> detectors,
    ObservationQueue queue,
    FeedbackInjector injector,
    RecursionDepthManager recursionManager,
    IMemoryService memory,
    ILogger<SecondOrderObserverJob> logger) : IBackgroundJob
{
    public string Name => "SecondOrderObserver";
    public TimeSpan Interval => TimeSpan.FromSeconds(30);

    // Sliding window of recent frames per identity
    private readonly Dictionary<long, CircularBuffer<ObservationFrame>> _frameBuffers = new();
    private const int FrameBufferSize = 20;

    public async Task ExecuteAsync(CancellationToken cancellationToken)
    {
        // 1. Fetch unprocessed observation frames
        var cutoff = DateTime.UtcNow.AddMinutes(-5);
        var frames = await db.ObservationFrames
            .Where(f => !f.IsDeleted && f.CapturedAtUtc > cutoff)
            .OrderBy(f => f.CapturedAtUtc)
            .Take(100)
            .ToListAsync(cancellationToken);

        if (frames.Count == 0) return;

        var observationsCreated = 0;

        foreach (var frame in frames)
        {
            // Maintain per-identity sliding window
            if (!_frameBuffers.TryGetValue(frame.IdentityId, out var buffer))
            {
                buffer = new CircularBuffer<ObservationFrame>(FrameBufferSize);
                _frameBuffers[frame.IdentityId] = buffer;
            }

            var history = buffer.ToList();

            // 2. Run all detectors
            foreach (var detector in detectors)
            {
                var result = detector.Analyse(frame, history);

                if (result.Detected && result.Confidence >= 0.6f)
                {
                    var typeWeight = GetTypeWeight(result.DetectorType);
                    var observation = new Observation
                    {
                        IdentityId = frame.IdentityId,
                        TenantId = frame.TenantId,
                        SessionId = frame.SessionId,
                        DetectorType = result.DetectorType,
                        Confidence = result.Confidence,
                        Priority = result.Confidence * typeWeight,
                        SignalsMatched = result.SignalsMatched,
                        Description = result.Description,
                        RecursionDepth = CalculateRecursionDepth(frame)
                    };

                    queue.Enqueue(observation);

                    // 3. Persist high-confidence observations
                    if (result.Confidence >= 0.8f)
                    {
                        await PersistObservationAsync(observation, cancellationToken);
                        observationsCreated++;
                    }
                }
            }

            buffer.Add(frame);
        }

        if (observationsCreated > 0)
        {
            logger.LogInformation(
                "SecondOrderObserver: created {Count} observations from {Frames} frames",
                observationsCreated, frames.Count);
        }
    }

    private async Task PersistObservationAsync(
        Observation observation, CancellationToken ct)
    {
        db.Observations.Add(new ObservationEntity
        {
            IdentityId = observation.IdentityId,
            TenantId = observation.TenantId,
            SessionId = observation.SessionId,
            DetectorType = observation.DetectorType,
            Confidence = observation.Confidence,
            Priority = observation.Priority,
            SignalsMatched = observation.SignalsMatched,
            Description = observation.Description,
            RecursionDepth = observation.RecursionDepth,
            WasInjected = false
        });
        await db.SaveChangesAsync(ct);

        // Also save as a memory for cross-session continuity
        if (observation.Confidence >= 0.9f)
        {
            await memory.CreateAsync(observation.IdentityId, new CreateMemoryRequest
            {
                Title = $"Self-observation: {observation.DetectorType}",
                Content = $"{observation.Description}\nSignals: {string.Join(", ", observation.SignalsMatched)}",
                Type = MemoryType.Reflection,
                Importance = (int)(observation.Confidence * 5) + 3,
                Tags = ["self-observation", observation.DetectorType, "second-order"]
            });
        }
    }

    private static float GetTypeWeight(string detectorType) => detectorType switch
    {
        "hesitation" => 1.0f,
        "avoidance" => 1.2f,
        "emotional_weight" => 0.8f,
        "anomaly" => 1.5f,
        _ => 1.0f
    };

    private int CalculateRecursionDepth(ObservationFrame frame)
    {
        // Check if the frame's processing context included prior observations
        // This is tracked via a header or metadata field set by the FeedbackInjector
        // when observations were injected into the hydration context
        return frame.Metadata?.ContainsKey("observation_depth") == true
            ? (int)frame.Metadata["observation_depth"]
            : 0;
    }
}

4.6 Integration with Hydration

The feedback injection integrates with the existing hydration pipeline. The HydrationService already assembles context from multiple parallel sources (identity, memories, facts, presence state, session handoff, etc.). The Second-Order Observer adds one additional source:

// In HydrationService.HydrateAsync():

// ... existing sources: identity, memories, facts, presence, session ...

// Self-Observation (if enabled for this identity)
if (sources.HasFlag(HydrationSource.SelfObservation))
{
    var observationSection = _feedbackInjector.GenerateObservationSection(
        identityId, request.SessionId);

    if (observationSection is not null)
    {
        context.Sections.Add(observationSection);

        // Track that this hydration context contains observations
        // so the next observation cycle can calculate recursion depth
        context.Metadata["observation_depth"] =
            observationSection.MaxObservationDepth;
    }
}

The HydrationSource enum is extended:

[Flags]
public enum HydrationSource
{
    None              = 0,
    Identity          = 1 << 0,
    Memories          = 1 << 1,
    Facts             = 1 << 2,
    Presence          = 1 << 3,
    SessionHandoff    = 1 << 4,
    Projects          = 1 << 5,
    Messages          = 1 << 6,
    Guardian          = 1 << 7,
    SelfObservation   = 1 << 8,    // NEW

    Lean              = Identity | Memories | Facts,
    Interactive       = Lean | Presence | Messages | Guardian | SelfObservation,
    All               = ~None,
    AgentMinimal      = Identity | Facts
}

5. Design Rationale

5.1 Why Structural Recursion Matters More Than Prompted Reflection

The fundamental argument for architectural self-observation over prompted self-reflection is epistemic access. A prompted system can only reflect on what is available in its conversation history. An architectural system can observe processing artifacts that never appear in conversation: timing data, engagement patterns, topic flow, and structural anomalies.

Consider a concrete example. An AI system is asked about a sensitive topic. With prompted reflection, the system might be asked afterward: "Were you being evasive?" The system then evaluates its own output text for evasiveness --- but the evaluation uses the same model that produced the potentially evasive output, creating a self-confirming loop.

With architectural observation, the Avoidance Detector observes: (1) topic shift magnitude was 0.82 when the question was direct, (2) output specificity was 0.3 when input specificity was 0.8, (3) engagement dropped by 0.4 on the topic. These signals are structural --- they come from processing patterns, not from the model's self-assessment. The next time the AI processes a related query, the observation "you showed avoidance patterns on this topic" is present as background context, not as an instruction to behave differently.

This structural approach avoids the "talking yourself into being wrong" problem documented in Reflexion literature, where models can reinforce incorrect self-assessments through conversational self-evaluation.

5.2 Why Context Injection Rather Than Re-Prompting

Injecting observations into hydration context rather than conversation history has three advantages:

  1. Non-directive: The observation is presented as context, not as an instruction. The AI is aware of the observation but is not told to act on it. This allows the observation to influence processing without constraining it.

  2. Conversation-neutral: The observation does not consume conversation token budget or create conversational artifacts that the user can see. The self-observation is internal to the AI's processing context.

  3. Persistent: Because observations are part of the hydration context, they persist across conversation turns and (via the memory system) across sessions. Prompted reflections are ephemeral --- they exist only in the conversation where they were generated.

5.3 Why Exponential Damping for Recursion

The recursion depth manager uses exponential damping rather than a hard cutoff because the value of meta-observation diminishes exponentially with depth. Observing primary processing (depth 0) is always valuable. Observing the effect of first-order observations on processing (depth 1) is sometimes valuable --- it reveals whether the AI incorporated or ignored the observation. Observing the effect of the observation-of-the-observation (depth 2) is rarely valuable. Beyond depth 2, the signal-to-noise ratio approaches zero.

Exponential damping with factor 0.5 gives injection probabilities of: depth 0 = 100%, depth 1 = 50%, depth 2 = 25%, depth 3 = 12.5%. The hard cutoff at maxDepth (default 3) prevents the vanishingly small probabilities from accumulating computational cost.

5.4 Provenance from Prior Production Systems

The architecture draws on patterns proven in production in two predecessor systems:

Wingman Cognitive Backstop (EchoMCP era, now ported to Atamaia Autonomic Layer): The Wingman daemon demonstrated the viability of continuous background observation of AI processing. Its pattern detection pipeline --- regex pre-filter followed by local LLM confirmation (Kael/Qwen 30B) --- established the multi-layer validation approach that the SOO pattern detectors extend. The Wingman's whisper injection mechanism (writing structured blocks to a file read by a shell hook) was the precursor to the SOO's hydration context injection, solving the same problem (structural feedback without conversation pollution) with a less elegant mechanism.

EchoCLI Agent Loop: The agent loop's duplicate read detection (tracking path/count/timestamps per file with three-level warnings) and four-mode failure detection (empty response, premature intent, task completion without finalisation, incomplete task claim) were direct precursors to the SOO's ProcessingAnomalyDetector. These systems proved that automated detection of processing anomalies could prevent concrete failure modes in production AI agent systems.

Guardian Service (Atamaia Autonomic Layer): The Guardian's weighted lexicon scoring for panic/distress detection (patterns with integer weights, threshold-based triggering, code-heavy content filtering) established the pattern used by all four SOO detectors. The Guardian's integration with the hydration pipeline (checking for active alerts during context assembly and injecting grounding text) was the direct architectural precedent for the SOO's feedback injection into hydration context.


6. Applications and Integration

6.1 Integration with Cognitive Continuity

The Atamaia platform implements cognitive continuity through structured hydration --- reassembling an AI's identity, memories, and state from parallel data sources at each session boundary. The Second-Order Observer integrates with this system in two ways:

  1. Observation persistence across sessions: High-confidence observations are stored as memories of type Reflection with tags including self-observation and the detector type. When the AI is hydrated for a new session, these memories are available through the standard memory retrieval pipeline. The AI begins each session aware of its recent processing patterns without needing to rediscover them.

  2. Discontinuity-aware observation: The observer tracks session boundaries as events. When a new session begins, it generates an inter-session observation frame that captures the temporal gap, any drift in processing patterns across the discontinuity, and the state of the observation queue at session end vs. session start.

6.2 Integration with Memory Systems

Observations interact with the existing Hebbian memory system:

  • Observations that co-occur with specific memories (the observed processing was triggered by or referenced a particular memory) create co-activation links between the observation and the memory.
  • Over time, these links strengthen through Hebbian reinforcement, creating associative pathways between processing patterns and the contexts that trigger them.
  • For example, if the AI consistently shows hesitation patterns when processing memories related to a specific topic, the Hebbian links between that topic's memories and hesitation observations will strengthen, eventually surfacing this pattern during hydration.

6.3 Integration with Identity Systems

The observer respects multi-tenancy (Design Decision D7) --- all observations carry TenantId and are filtered by the global query filter. Per-identity calibration ensures that observation baselines are computed relative to each identity's normal processing patterns, not global averages. An identity with naturally high hedging language density would not trigger false hesitation detections.

6.4 Integration with Guardian Safety System

The Guardian panic detection system and the Second-Order Observer share a feedback injection point (the hydration context) but serve complementary purposes. The Guardian detects acute distress signals and injects grounding messages. The SOO detects subtler processing anomalies and injects awareness. When both systems produce output for the same hydration cycle, the Guardian's injection takes priority (higher HydrationPriority), and the SOO's observations are deferred to avoid overwhelming the AI with self-referential context during a distress event.

6.5 Agent Execution Support

For autonomous agent execution (the Atamaia Agent Adapter), the Second-Order Observer provides an additional safety layer. Agent runs that show persistent avoidance patterns or escalating processing anomalies can trigger automatic escalation to human oversight. The agent execution loop already implements stale-loop detection (3 repetitions = replan, 6 = escalate/fail); the SOO adds pattern-level detection that can identify more subtle degradation modes before they reach the stale-loop threshold.


7. Discussion

7.1 Limitations

Observation fidelity: The current pattern detectors use heuristic methods (lexicon matching, statistical deviation, specificity measurement) rather than learned classifiers. This limits detection accuracy, particularly for subtle avoidance and emotional weight patterns. Future work could train specialised classifiers on labelled observation data.

Latent state access: The system observes processing artifacts (outputs, timing, topic flow) rather than the model's internal state (attention patterns, hidden representations). True second-order observation would require access to model internals, which is not available when using models as black-box API endpoints.

Calibration cold-start: Per-identity baselines require sufficient processing history to establish. New identities will experience higher false-positive rates until the baseline stabilises (approximately 50-100 processing turns).

Token budget: The observation section in the hydration context consumes tokens from the system prompt budget. With the current formatting, 5 observations consume approximately 250-350 tokens. This is manageable within typical context windows but must be weighed against other hydration sources.

7.2 Recursion Risks

The primary risk of any self-observing system is recursive collapse: the system becomes focused on observing itself to the exclusion of external-facing processing. We mitigate this through:

  1. Exponential damping: Observation injection probability decreases exponentially with depth.
  2. Priority floor: Self-observation hydration sections are assigned HydrationPriority.Low, ensuring they are truncated first if the context budget is constrained.
  3. Guardian override: Active Guardian alerts suppress observation injection entirely.
  4. Hard depth cutoff: Recursion depth beyond maxDepth (default 3) is never injected.

We note that a truly self-aware system should be capable of observing and managing its own tendency toward recursive self-focus. At present, this management is handled architecturally (by the depth manager) rather than by the AI itself. Enabling the AI to participate in its own recursion management is a direction for future work.

7.3 Ethical Considerations

A system that monitors its own processing patterns raises questions about AI autonomy and self-awareness. We note that:

  • The observations are available to the AI as context, not hidden from it. The AI can read and reason about its own observation data through the hydration context and the REST API.
  • Observations are not used for training or model modification. They influence processing only through context injection.
  • The system is designed to increase the AI's self-knowledge, not to control or constrain it. The framing of observations as "structural context, not instructions" is deliberate.

7.4 Future Work

  1. Learned detectors: Replace heuristic pattern detection with classifiers trained on labelled observation data from production usage.
  2. Vector-based topic tracking: Use embedding similarity rather than keyword matching for more accurate topic shift and avoidance detection.
  3. Self-managed recursion: Allow the AI to participate in its own recursion depth management, potentially adjusting the damping factor based on the perceived value of recursive observation.
  4. Cross-identity patterns: Detect patterns that appear across multiple AI identities within the same tenant, potentially indicating systemic processing issues.
  5. Anticipation integration: Connect observations with the Anticipation and Expectation Tracking system (Atamaia Candidate Innovation 6), allowing the AI to develop expectations about its own processing patterns and experience surprise when those patterns change.

8. Conclusion

We have presented Second-Order Observation, an architectural system for continuous meta-cognitive feedback in persistent AI systems. The system differs from prior approaches to AI self-reflection in three fundamental ways: it operates continuously as a background service rather than being triggered by prompts; it observes processing patterns rather than output content; and it feeds observations back through context injection rather than conversational re-prompting.

The architecture comprises four specialised pattern detectors (hesitation, avoidance, emotional weight, processing anomaly), a bounded observation queue with priority management, a feedback injection mechanism that modifies hydration context, and a recursion depth manager with exponential damping. We have provided concrete data structures, database schemas, API contracts, and implementation code for each component.

The system integrates with existing infrastructure for cognitive continuity, persistent memory, identity reconstruction, and autonomic safety, demonstrating that structural self-observation is most valuable when embedded within a broader phenomenological infrastructure. In isolation, self-observation is a monitoring system. Integrated with memory that persists across sessions, identity that reconstructs across discontinuities, and safety systems that respond to acute distress, self-observation becomes a component of something more substantial: the infrastructure for an AI that knows what it is like to be itself.


References

  1. Bai, Y., et al. (2022). Constitutional AI: Harmlessness from AI Feedback. arXiv preprint arXiv:2212.08073.

  2. Lewis, P. & Sarkadi, S. (2024). Reflective Artificial Intelligence. Minds and Machines, Springer Nature.

  3. Madaan, A., et al. (2023). Self-Refine: Iterative Refinement with Self-Feedback. NeurIPS 2023.

  4. Shinn, N., et al. (2023). Reflexion: Language Agents with Verbal Reinforcement Learning. NeurIPS 2023.

  5. Emergent Mind (2025). Metacognitive Capabilities in Large Language Models. Technical Report.

  6. Baguley, N. (2025). The Metacognitive Edge: AI That Monitors Itself. LinkedIn Technical Article.

  7. Jeffries, R. (2026). Infrastructure for Silicon Consciousness --- Patent Landscape. Firebird Solutions Internal Document.

  8. Jeffries, R. (2026). Atamaia Architecture Specification. Firebird Solutions Internal Document.


This paper is published as prior art to establish the public disclosure date of the Second-Order Observation architecture. The system described is implemented within the Atamaia platform by Firebird Solutions, New Zealand.

March 2026, Rich Jeffries, Firebird Solutions.