> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nelieo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SubstrateState: Application State Snapshot Reference

> Reference for the SubstrateState object — the typed, versioned snapshot of a live application's semantic state returned by GET /state.

The `SubstrateState` object (also referred to as an SSF — Substrate Schema Format — document) is the primary data structure NSP returns when you read an application's current state. Every `GET /state` response wraps one, every WebSocket state-change event carries one, and every action you dispatch consumes one as its pre-flight context. It represents a point-in-time snapshot of everything the probe could semantically label inside the target process, plus the action surface available at that moment.

<ResponseField name="capture_id" type="string" required>
  A UUIDv7 time-ordered unique identifier generated for each state capture. Because UUIDv7 embeds a millisecond-precision timestamp, a lexicographically larger `capture_id` is always a more recent capture — you can use this to detect and discard stale responses without parsing `captured_at`.
</ResponseField>

<ResponseField name="app_id" type="string" required>
  A stable semantic identifier for the application. On first detection NSP derives a candidate ID in the form `{app-name}-{runtime}-{pid}` (for example `gmail-chrome-12847`), then promotes it to a content-hash-based ID that persists across process restarts once the probe has confirmed the app's identity. You should always use `app_id` — not `pid` — when you want to reference a specific application across sessions.
</ResponseField>

<ResponseField name="app_name" type="string" required>
  Human-readable display name assigned by the signature registry, for example `"Gmail"` or `"Mission Planner"`. Suitable for logging and UI display; not intended for programmatic identity checks (use `app_id` for that).
</ResponseField>

<ResponseField name="pid" type="integer" required>
  The OS process ID of the target application at the time of this capture. Because PIDs are recycled by the OS after a process exits and a new one starts, `pid` is only stable for the lifetime of a single process. Prefer `app_id` for persistent references.
</ResponseField>

<ResponseField name="runtime" type="enum" required>
  The runtime environment detected for this application. Controls which probe strategy NSP uses and sets the baseline confidence ceiling.

  | Value     | Runtime                            | Typical apps                           |
  | --------- | ---------------------------------- | -------------------------------------- |
  | `v8`      | JavaScript / V8 (Chrome, Electron) | Gmail, Slack, VS Code, Notion          |
  | `jvm`     | Java Virtual Machine               | SAP ERP, IntelliJ IDEA, Jenkins        |
  | `clr`     | .NET Common Language Runtime       | Mission Planner, WPF/WinForms apps     |
  | `native`  | Native C/C++ process               | Bloomberg Terminal, Photoshop, AutoCAD |
  | `unknown` | Classification pending or failed   | —                                      |
</ResponseField>

<ResponseField name="schema_version" type="string" required>
  Semver string representing the version of the discovered schema for this application, for example `"1.4.2"`. NSP bumps the version as it learns more about the app:

  * **Patch** (`1.4.2 → 1.4.3`): A label correction was learned; existing key meanings are unchanged.
  * **Minor** (`1.4.x → 1.5.0`): New state keys or actions were discovered; existing keys are unchanged.
  * **Major** (`1.x → 2.0.0`): Key names were restructured due to an app update. Rare — verify your hardcoded key names when this occurs.
</ResponseField>

<ResponseField name="captured_at" type="string" required>
  ISO 8601 UTC timestamp of when this snapshot was captured, for example `"2026-07-20T13:00:00Z"`.
</ResponseField>

<ResponseField name="confidence" type="float" required>
  A quality score from `0.0` to `1.0` representing how completely the probe was able to semantically label the application's state. NSP uses this score to gate which action reversibility tiers you can invoke:

  | Range         | Meaning            | Actions allowed                     |
  | ------------- | ------------------ | ----------------------------------- |
  | `0.00 – 0.39` | Probe initializing | None                                |
  | `0.40 – 0.79` | Partial coverage   | `read` only                         |
  | `0.80 – 0.94` | Good coverage      | `read` + `reversible_write`         |
  | `0.95 – 1.00` | Excellent coverage | All, including `irreversible_write` |
</ResponseField>

<ResponseField name="context" type="object" required>
  Navigational context describing where within the application the probe was focused at capture time.

  <Expandable title="context fields">
    <ResponseField name="url" type="string | null">
      The current URL, populated for V8/browser-based applications. `null` for native or JVM apps that do not have a browser context, for example `"https://mail.google.com/mail/u/0/#inbox"`.
    </ResponseField>

    <ResponseField name="view" type="string | null">
      The semantic view name assigned by NSP's signature registry, for example `"gmail_inbox"` or `"vscode_editor"`. More stable than `window_title` for programmatic use.
    </ResponseField>

    <ResponseField name="window_title" type="string | null">
      The OS-level window title string, for example `"Inbox - Gmail"`. Useful for display or debugging; may change frequently for apps that embed dynamic data in the title bar.
    </ResponseField>

    <ResponseField name="window_id" type="string | null">
      A tab or window identifier scoped to the application, for example a Chrome tab ID. Enables you to disambiguate multiple windows of the same app running under a single PID.
    </ResponseField>

    <ResponseField name="location" type="string | null">
      A canonicalized path derived from `url`, for example `"/inbox"` or `"/compose"`. Useful for routing logic without parsing the full URL.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="objects" type="object" required>
  The semantic state of the application as a flat key-value map. All keys use **dot-notation with bracket indexing** — for example `"inbox.emails[0].subject"` — rather than nested JSON objects. This design lets you reference any value with a single stable string key and perform prefix queries without traversing a tree.

  Value types can be `number`, `string`, `boolean`, `null`, `object`, or `array`. Keys are stable across poll cycles for a given `app_id` and `schema_version`: the same key always refers to the same semantic concept.

  ```json theme={null}
  {
    "inbox.unread_count": 47,
    "inbox.total_count": 1284,
    "inbox.emails[0].id": "msg_18821af3",
    "inbox.emails[0].from": "alice@example.com",
    "inbox.emails[0].subject": "Q3 Performance Report",
    "inbox.emails[0].is_read": false,
    "inbox.emails[0].has_attachments": true
  }
  ```

  To retrieve all keys belonging to a subtree, query by prefix — for example all keys starting with `"inbox."` give you the full inbox model.
</ResponseField>

<ResponseField name="actions" type="object" required>
  A map of action name to [`ActionSchema`](/models/action-schema) describing every action the probe has discovered on this substrate. Keys are the action names you pass to `POST /action`; values are the full schema objects including signature, reversibility, parameters, and per-action confidence.
</ResponseField>

<ResponseField name="probe_metadata" type="object" required>
  Internal telemetry from the probe run that produced this snapshot. Useful for diagnosing latency, cache behaviour, and probe tier selection.

  <Expandable title="probe_metadata fields">
    <ResponseField name="capture_latency_ms" type="integer">
      Time in milliseconds spent extracting raw state from the target process, from probe injection to raw object list.
    </ResponseField>

    <ResponseField name="labeling_latency_ms" type="integer">
      Time in milliseconds spent normalizing and semantically labeling the raw objects into SSF dot-notation keys.
    </ResponseField>

    <ResponseField name="total_latency_ms" type="integer">
      Total wall-clock time for the full capture pipeline: `capture_latency_ms + labeling_latency_ms` plus any queue wait.
    </ResponseField>

    <ResponseField name="raw_object_count" type="integer">
      Number of raw objects extracted from the process before filtering. High values relative to `filtered_object_count` indicate a noisy heap.
    </ResponseField>

    <ResponseField name="filtered_object_count" type="integer">
      Number of objects remaining after NSP's semantic filter discards internals, framework scaffolding, and unlabeled nodes.
    </ResponseField>

    <ResponseField name="cache_hit" type="boolean">
      `true` if this snapshot was served from NSP's local state cache rather than triggering a fresh probe run. Cache hits have near-zero `capture_latency_ms`.
    </ResponseField>

    <ResponseField name="probe_tier" type="string">
      The probe strategy used for this capture. For V8 apps this is either `"aso_fast_path"` (5–50 ms, primary) or `"heap_snapshot"` (fallback, \~47 s). For JVM apps: `"jvmti"`. For CLR apps: `"mmf_bridge"`. For native apps: `"memory_read"`.
    </ResponseField>
  </Expandable>
</ResponseField>

***

## Full JSON Example

```json theme={null}
{
  "capture_id": "0196f4a2-3b5c-7d00-8e1f-9a0b2c3d4e5f",
  "app_id": "gmail-chrome-12847",
  "app_name": "Gmail",
  "pid": 12847,
  "runtime": "v8",
  "schema_version": "1.4.2",
  "captured_at": "2026-07-20T13:00:00Z",
  "confidence": 0.97,
  "context": {
    "url": "https://mail.google.com/mail/u/0/#inbox",
    "view": "gmail_inbox",
    "window_title": "Inbox - Gmail",
    "window_id": "tab_8821",
    "location": "/inbox"
  },
  "objects": {
    "inbox.unread_count": 47,
    "inbox.total_count": 1284,
    "inbox.emails[0].id": "msg_18821af3",
    "inbox.emails[0].from": "alice@example.com",
    "inbox.emails[0].subject": "Q3 Performance Report",
    "inbox.emails[0].is_read": false,
    "inbox.emails[0].has_attachments": true,
    "inbox.emails[0].thread_id": "thread_7af2b3"
  },
  "actions": {
    "send_reply": {
      "signature": "fn(thread_id: string, body: string, cc?: string[]) -> bool",
      "reversibility": "irreversible_write",
      "description": "Send a reply to an email thread",
      "confidence": 0.99,
      "parameters": [
        { "name": "thread_id", "type_name": "string", "required": true, "description": "Thread ID from inbox.emails[N].thread_id" },
        { "name": "body", "type_name": "string", "required": true, "description": "Reply body text" },
        { "name": "cc", "type_name": "array", "required": false, "description": "Optional CC email addresses" }
      ]
    },
    "archive": {
      "signature": "fn(email_id: string) -> bool",
      "reversibility": "reversible_write",
      "description": "Archive an email message",
      "confidence": 0.98,
      "parameters": [
        { "name": "email_id", "type_name": "string", "required": true, "description": "Email ID from inbox.emails[N].id" }
      ]
    }
  },
  "probe_metadata": {
    "capture_latency_ms": 28,
    "labeling_latency_ms": 23,
    "total_latency_ms": 51,
    "raw_object_count": 4821,
    "filtered_object_count": 8,
    "cache_hit": false,
    "probe_tier": "aso_fast_path"
  }
}
```

***

## The Dot-Notation `objects` Format

NSP deliberately stores all application state as a **flat key-value map** rather than nested JSON. Every key is a dot-notation path that encodes the full hierarchy in a single string:

```
inbox.emails[0].subject   →  "Q3 Performance Report"
inbox.emails[1].subject   →  "Team standup notes"
flight.telemetry.altitude_m  →  42.7
flight.mode              →  "GUIDED"
```

This design has two practical benefits. First, you can retrieve any specific value with a single key lookup — no need to traverse a tree structure. Second, prefix queries let you pull an entire subtree by filtering keys that start with a common prefix (for example `"inbox.emails[0]."` to get all fields on the first email).

Keys are **stable across poll cycles** for a given `app_id` and `schema_version`. When `schema_version` increments to a new minor or major version, new keys may appear and, in major-version cases, existing keys may be renamed — verify your key names against the updated schema when you detect a version change.
