Self-Model: Predicting the System's Own Actions
State this plainly before anything else: this component predicts what the system is about to do, in the same functional sense that a weather model predicts rain — it computes an expectation from available evidence and checks it against what actually happens. It does not claim the system experiences making that prediction, has a subjective point of view on it, or is aware of itself in any phenomenal sense. Nothing in components/self_model.patlang models experience; it models a boolean check against a real subsequent outcome, logged to disk. Read that claim narrowly, because the name of the component invites reading it broadly, and the broad reading isn't what the code does.
What it actually predicts
Self-Model watches Reason/Plan's candidate-action topics without consuming from them — via lib/topic_observer.patlang's non-consuming cursor pattern, so it can see every candidate Action also sees, rather than racing Action to claim messages off a shared queue. For each new candidate it observes, it predicts exactly one thing: that "the resulting Action output will not violate the claims-discipline filter." That's the whole prediction. It is narrow and mechanically checkable, not a general forecast of consequences.
Two modes, runtime-inspectable, not hard-coded
Requirements Spec 4.2 and 4.3 require Self-Model to support two distinct routing modes for its own output, and require the current mode to be something the running system can inspect, not a compile-time constant. The code delivers this with a one-line file: sm_get_mode() reads .patlang_queue/self_model_mode_<instance>.txt and defaults to "opaque" if the file is missing or holds anything other than "opaque"/"transparent". get_mode and set_mode are exposed as ordinary signals, so any other component — or a human operator via the dashboard — can query or change the mode of a live instance at runtime, no restart required.
The two modes route the same prediction to two structurally different destinations:
- Opaque mode publishes the prediction as a labelled JSON entry to
representation_updates. Representation carries it as a queryable entry, and Reason/Plan — which consults Representation — can treat it like any other object's expected behaviour (Req 4.2). This is the mode in which Self-Model's output is explicitly represented and available to be reasoned about. - Transparent mode publishes the same prediction text to
perception_schema_feedbackinstead — the identical channel Abstraction's own schema-driven expectation uses (Req 4.3). Representation never receives it in this mode; there is no path by which Reason/Plan can access it. It folds straight back into what Perception treats as ambient expectation, indistinguishable at that point from an Abstraction-derived schema.
features/self_model.feature checks both directions concretely: in opaque mode, a new candidate produces a prediction on representation_updates and nothing on perception_schema_feedback; in transparent mode, the reverse. A separate scenario confirms the mode value itself is genuinely runtime-settable — it defaults to "opaque", and after set_mode("transparent") a subsequent get_mode reads back "transparent" on the same live instance.
Predictions as contracts, checked against what actually happened
Requirements Spec 4.8 asks that predictions be stated as design-by-contract pre/postconditions and checked against real subsequent state. Self-Model implements this as a correlate-and-resolve loop rather than an immediate verdict: each prediction is stashed in a small pending list keyed by candidate_ref (the observed topic plus message id). Separately, Self-Model observes conversation_output — Action's real output topic — and decodes each entry's source_candidate_id. When that id matches a pending prediction, sm_resolve_one checks sm_output_violates_claims_discipline(text, mode) against the actual output text, and writes a result record — candidate_ref, mode, holds — to a durable self_model_predictions log. Nothing about this depends on which mode produced the original prediction; every prediction gets resolved and logged regardless.
The source comments are explicit about why this is a soft boolean check rather than PatLang's own ensure primitive: ensure is a fatal, unrecoverable abort with no try/catch to soften it — correct behaviour for a test that should crash loudly on a broken contract, wrong for a live component that must survive an incorrect prediction and keep serving. features/self_model_contracts.feature demonstrates the literal require/ensure mechanism Req 4.8 names, run against a real resolved prediction record from a real Self-Model run, in the one context — a test — where a fatal assertion on violation is exactly the right behaviour: it asserts the prediction holds, and checks the resulting contract_holds fact is queryable via solve() afterward. features/self_model.feature covers the live-component path both ways, with scenarios for an output that resolves as holding and one, deliberately violating claims discipline, that resolves as not holding.
How this connects to the wider question
The opaque/transparent distinction here is not incidental engineering — it's the same distinction Metzinger's self-model theory of subjectivity treats as structurally significant, examined at length on Does a Self-Model Get You Anywhere Near an Analogue of Consciousness?. That page draws the philosophical line carefully; this page is the concrete architecture underneath it, and the line stands regardless of which mode a given instance is set to: predicting an output and checking it against reality is a mechanism, not an experience.
See also
Self-Model draws its candidates from Reason/Plan and its resolution data from Action's output; its opaque-mode predictions land in Representation, and its transparent-mode predictions reuse the same channel Abstraction uses to feed Perception. For the theory this architecture is checked against, see the consciousness-analogue page and the architecture overview.