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:
- A formal architecture for continuous self-observation in AI systems, operating as a background service rather than a prompted evaluation.
- Four specialised pattern detectors for hesitation, avoidance, emotional weight, and processing anomaly detection, with concrete detection algorithms.
- A feedback injection mechanism that modifies the AI's hydration context rather than its conversation history, producing structural rather than conversational self-awareness.
- A recursion depth manager with exponential damping that prevents infinite observation loops while allowing configurable observation depth.
- 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:
- Observation target: We observe processing patterns (timing, topic flow, emotional markers, engagement shifts), not output content.
- Continuous operation: The observer runs as a persistent background service, not triggered by prompts or evaluation requests.
- 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.
- 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.
- 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:
- Pre-processing: captures the input context (user message, hydration state, active memories) before the LLM processes it.
- Post-processing: captures the LLM's output including any tool calls, response content, and timing data.
- 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:
- Temporal buffering: prevents single-frame false positives by requiring pattern persistence across multiple frames.
- Priority management: high-confidence observations take precedence over low-confidence ones.
- 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:
- Capture: The Observation Tap captures an
ObservationFrameat the pre-processing, post-processing, or inter-turn phase. - 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).
- Filter: Detection results below the confidence threshold (default 0.6) are discarded.
- Enqueue: Qualifying detections are converted to
Observationrecords and enqueued in the Observation Queue with priority computed asconfidence * type_weightwhere type weights are configurable (default: hesitation=1.0, avoidance=1.2, emotional_weight=0.8, anomaly=1.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
HydrationSectionif observations are available and the recursion depth is within bounds. - Process: The AI processes with the observation section as structural background context.
- 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:
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.
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.
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:
Observation persistence across sessions: High-confidence observations are stored as memories of type
Reflectionwith tags includingself-observationand 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.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:
- Exponential damping: Observation injection probability decreases exponentially with depth.
- Priority floor: Self-observation hydration sections are assigned
HydrationPriority.Low, ensuring they are truncated first if the context budget is constrained. - Guardian override: Active Guardian alerts suppress observation injection entirely.
- 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
- Learned detectors: Replace heuristic pattern detection with classifiers trained on labelled observation data from production usage.
- Vector-based topic tracking: Use embedding similarity rather than keyword matching for more accurate topic shift and avoidance detection.
- 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.
- Cross-identity patterns: Detect patterns that appear across multiple AI identities within the same tenant, potentially indicating systemic processing issues.
- 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
Bai, Y., et al. (2022). Constitutional AI: Harmlessness from AI Feedback. arXiv preprint arXiv:2212.08073.
Lewis, P. & Sarkadi, S. (2024). Reflective Artificial Intelligence. Minds and Machines, Springer Nature.
Madaan, A., et al. (2023). Self-Refine: Iterative Refinement with Self-Feedback. NeurIPS 2023.
Shinn, N., et al. (2023). Reflexion: Language Agents with Verbal Reinforcement Learning. NeurIPS 2023.
Emergent Mind (2025). Metacognitive Capabilities in Large Language Models. Technical Report.
Baguley, N. (2025). The Metacognitive Edge: AI That Monitors Itself. LinkedIn Technical Article.
Jeffries, R. (2026). Infrastructure for Silicon Consciousness --- Patent Landscape. Firebird Solutions Internal Document.
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.