DICSTAMACH — Dynamic Interactive Claude State Machine. The introspection layer complementing ai.claude.* (intention) and ai.gryph.* (execution): what knowledge the agent had loaded, when it garbage-collected (with user consent), and when context pressure crossed thresholds. 8 primary events · 2 reusable verify sub-types · matching verify correlation convention.
These events capture an AI coding agent's context-state transitions — what knowledge domains were loaded into its working context, when, why, when garbage-collected (with user consent), and when context pressure crossed operational thresholds. They complete the three-layer audit triad alongside existing families:
| Layer | Family | Observer | Answers |
|---|---|---|---|
| Intention | ai.claude.* | Claude bridge on Anthropic API | What did the agent say it was going to do? |
| Execution | ai.gryph.* | Gryph hook on Claude Code runtime | What did the system actually see happen? |
| Introspection | ai.ias.claude.* | Claude bridge parsing DICSTAMACH markers | What did the agent's working memory look like while it was thinking? |
All three share a single session_id. A DICSTAMACH event explains the state of agent memory between any two ai.claude.* or ai.gryph.* events. Per the project-wide verification convention (see CORRELATION), each primary event MAY have one or more verify events from independent observers — most commonly a gryph-based hook, which confirms the OS-level writes to STATEOFCLAUDE.md and reads of the domain source file.
Canonical source: the authoritative markdown spec lives at codeberg.org/foundationprotocols/foundation-protocols-spec/spec/ai/ias.md — 504 lines, full field tables, all JSON examples, emission rules.
All foundation.protocols.ai.ias.claude.* primary events include:
| Field | Type | Required | Description |
|---|---|---|---|
| session_id | string (UUID) | yes | Same ID as ai.claude.session.start and ai.gryph.session.start |
| sequence | integer | yes | Monotonic counter within the DICSTAMACH stream |
| timestamp | string (ISO 8601) | yes | UTC timestamp of the state transition |
| turn_count | integer | yes | Turn number in the session when the event fires |
| schema | string | yes | DICSTAMACH schema version, e.g. "DICSTAMACH v1.0" |
2 events — session.start, session.end.
Fires once per session at Gate 0 pre-flight, after the agent reads STATEOFCLAUDE.md and resets all domain states to INDEXED.
| Field | Type | Description |
|---|---|---|
| domains_total | integer | Number of non-kernel domains in the hook table |
| domains_initial_state | string | Always "INDEXED" at session start |
| context_budget_chars | integer | Max target size for CLAUDE.md + loaded reference content |
| kernel_chars | integer | Measured size of the kernel |
| gc_threshold | number | Pressure ratio at which GC fires. Default 0.80 |
| previous_session_id | string (UUID) or null | Immediate previous session for continuity. Null on first-ever session |
{
"type": "foundation.protocols.ai.ias.claude.session.start",
"content": {
"session_id": "2026-04-14T09:00Z",
"sequence": 1,
"timestamp": "2026-04-14T09:00:00Z",
"turn_count": 0,
"schema": "DICSTAMACH v1.0",
"domains_total": 12,
"domains_initial_state": "INDEXED",
"context_budget_chars": 40000,
"kernel_chars": 26000,
"gc_threshold": 0.80,
"previous_session_id": "2026-04-13T16:35Z"
}
}
Fires at Gate 9 clean session close, or as recovered state at next-session Gate 0 for unclean close. Summarises the session's DICSTAMACH activity.
| Field | Type | Description |
|---|---|---|
| reason | string | Values: session_end, session_end_unclean, commit, pressure_exhausted |
| final_turn_count | integer | Total turns in the session |
| domains_loaded_final | integer | Count of LOADED domains at close |
| domains_active_final | integer | Count of ACTIVE domains at close |
| context_pressure_final | number | (kernel_chars + loaded_chars) / context_budget_chars |
| gc_proposals_total | integer | Count of gc.proposed events this session |
| gc_acceptances_total | integer | Count of gc.accepted events this session |
2 events — domain.loaded, domain.unloaded.
Fires when a domain transitions INDEXED → LOADED — the agent reads the domain's source file into context for the first time this session (or re-reads after a prior GC).
| Field | Type | Description |
|---|---|---|
| domain | string | Domain key from the hook table, e.g. "daicas_arch" |
| source | string | File path that was read, relative to project root |
| chars | integer | Character count of the file content loaded |
| trigger_type | string | Values: hook_word, user_request, task_plan |
| trigger_matched | string | The trigger word or short description of why this domain loaded |
| loaded_at_turn | integer | Turn when this happened |
{
"type": "foundation.protocols.ai.ias.claude.domain.loaded",
"content": {
"session_id": "2026-04-14T09:00Z",
"sequence": 4,
"timestamp": "2026-04-14T09:12:00Z",
"turn_count": 3,
"schema": "DICSTAMACH v1.0",
"domain": "daicas_arch",
"source": "config/dicstamach/ref-daicas-architecture.md",
"chars": 3770,
"trigger_type": "hook_word",
"trigger_matched": "DAICAS",
"loaded_at_turn": 3
}
}
Fires when a domain transitions LOADED → INDEXED via consent-gated GC. Always paired with a preceding gc.accepted.
| Field | Type | Description |
|---|---|---|
| domain | string | Domain key being released |
| chars_released | integer | Character count freed |
| unloaded_reason | string | Values: gc_accepted_all, gc_accepted_selective, session_end |
| loaded_for_turns | integer | Number of turns the domain was LOADED |
| last_referenced_at_turn | integer or null | Most recent turn where content was actively used |
| gc_acceptance_event_id | string or null | Matrix event_id of the paired gc.accepted. Null for session-end cleanup |
3 events — gc.proposed, gc.accepted, gc.declined. Consent-gated: the agent NEVER unloads without explicit user approval.
Fires when the agent proposes garbage collection — either pressure crossed gc_threshold, or a LOADED domain went 5+ turns unreferenced.
| Field | Type | Description |
|---|---|---|
| trigger_reason | string | Values: pressure_threshold, stale_domain |
| pressure_ratio | number | Current (kernel_chars + loaded_chars) / context_budget_chars |
| pressure_threshold | number | Threshold value triggering GC |
| candidates | array of objects | Each: {domain, loaded_at_turn, last_used_at_turn, chars}. Sorted by staleness. |
| candidates_total_chars | integer | Sum of chars across all candidates |
| pinned_domains | array of strings | Domain keys that would have been candidates but are pinned |
Fires when the user consents to GC. A single event covers whole or partial acceptance; domains_released is authoritative about what transitioned.
| Field | Type | Description |
|---|---|---|
| proposal_event_id | string | Matrix event_id of the gc.proposed this responds to |
| acceptance_mode | string | Values: all, selective, pin_only |
| domains_released | array of strings | Domain keys that transitioned LOADED → INDEXED |
| domains_kept | array of strings | Domain keys proposed but kept loaded |
| chars_released | integer | Sum of chars freed |
| post_gc_pressure_ratio | number | Pressure after release |
Fires when the user refuses GC — explicit n, all candidates pinned, or no-response timeout.
| Field | Type | Description |
|---|---|---|
| proposal_event_id | string | Matrix event_id of the gc.proposed being declined |
| decline_reason | string | Values: user_n, all_pinned, no_response |
| domains_kept_loaded | array of strings | Every candidate that stayed LOADED |
| pressure_ratio_unchanged | number | Pressure remains at pre-proposal value |
1 event — pressure.threshold.
Fires when the context-pressure ratio crosses a defined threshold — upward (rising) or downward (after GC). Decoupled from gc.proposed because pressure signals are useful even when GC is not proposed.
| Field | Type | Description |
|---|---|---|
| pressure_ratio | number | New pressure ratio after crossing |
| threshold_crossed | number | Specific threshold value (0.50, 0.80, 0.95, etc.) |
| direction | string | Values: upward, downward |
| kernel_chars | integer | Current kernel size |
| loaded_chars | integer | Current chars across all LOADED domains |
| active_domains | integer | Count of ACTIVE domains |
Typical sequence: pressure.threshold (upward, 0.80) → gc.proposed → gc.accepted or gc.declined → zero or more domain.unloaded → pressure.threshold (downward, 0.80).
This family uses two reusable verify sub-types per the correlation convention. Each uses Matrix's native m.relates_to with rel_type: "foundation.protocols.verify.v1" pointing at the primary event.
Fires when a gryph-based hook observes a Write tool-call targeting STATEOFCLAUDE.md consistent with the primary DICSTAMACH event.
| Field | Type | Description |
|---|---|---|
| statefile_path | string | Path to the observed STATEOFCLAUDE.md |
| bytes_written | integer | Total bytes in the Write payload |
| content_hash | string | SHA-256 of the post-write content |
| diff_summary | object | {domains_changed: [...], fields_changed: [...]} |
| tool_use_id | string | Gryph's tool_use_id for cross-reference with ai.gryph.* |
{
"type": "foundation.protocols.ai.ias.claude.domain.loaded.verify.gryph.statefile_write",
"content": {
"m.relates_to": {
"event_id": "$primaryDomainLoaded:matrix.org",
"rel_type": "foundation.protocols.verify.v1"
},
"session_id": "2026-04-14T09:00Z",
"observed_at": "2026-04-14T09:12:01.523Z",
"verifier": "gryph-hook",
"verifier_version": "v0.6.0",
"primary_event_id": "$primaryDomainLoaded:matrix.org",
"primary_event_type": "foundation.protocols.ai.ias.claude.domain.loaded",
"statefile_path": "/workspace/STATEOFCLAUDE.md",
"bytes_written": 7754,
"content_hash": "sha256:abcdef...",
"diff_summary": {
"domains_changed": ["daicas_arch"],
"fields_changed": ["domains.daicas_arch.state", "domains.daicas_arch.loaded_at_turn"]
},
"tool_use_id": "toolu_01ABC..."
}
}
Fires when a gryph-based hook observes a Read tool-call targeting the domain's source file close in time to a primary domain.loaded.
| Field | Type | Description |
|---|---|---|
| source_path | string | Path of the file that was read |
| bytes_read | integer | Total bytes returned |
| content_hash | string | SHA-256 of the content read |
| tool_use_id | string | Gryph's tool_use_id for the observed Read |
Which verify sub-type applies to which primary event:
| Primary event | .verify.gryph.statefile_write | .verify.gryph.file_read |
|---|---|---|
session.start | ✓ | — |
session.end | ✓ | — |
domain.loaded | ✓ | ✓ |
domain.unloaded | ✓ | — |
gc.proposed | ✓ | — |
gc.accepted | ✓ | — |
gc.declined | ✓ | — |
pressure.threshold | N/A — pure introspection | — |
pressure.threshold is marked N/A because pressure computation is pure agent introspection — the agent computes (kernel_chars + loaded_chars) / budget from its own knowledge of what it loaded. A gryph-based hook has no independent view of "chars in context".
An illustrative sub-session showing the primary + verify pattern during a load and a GC cycle:
| 1 | ai.ias.claude.session.start | 0 | 12 domains INDEXED |
| 1v | ….session.start.verify.gryph.statefile_write | 0 | gryph confirms state init |
| 2 | ai.claude.session.start | 0 | bridge self-reports |
| 3 | ai.gryph.session.start | 0 | hook self-reports |
| … | |||
| 18 | ai.ias.claude.domain.loaded (daicas_arch, "DAICAS") | 3 | |
| 18a | ….domain.loaded.verify.gryph.file_read | 3 | Read of ref file confirmed |
| 18b | ….domain.loaded.verify.gryph.statefile_write | 3 | STATEOFCLAUDE.md updated |
| … | |||
| 37 | ai.ias.claude.pressure.threshold (upward, 0.80) | 28 | no verify — pure introspection |
| 38 | ai.ias.claude.gc.proposed | 28 | |
| 38v | ….gc.proposed.verify.gryph.statefile_write | 28 | proposal metadata persisted |
| 41 | ai.ias.claude.gc.accepted (selective) | 29 | |
| 41v | ….gc.accepted.verify.gryph.statefile_write | 29 | |
| 42 | ai.ias.claude.domain.unloaded | 30 | |
| 42v | ….domain.unloaded.verify.gryph.statefile_write | 30 | state transition persisted |
| 43 | ai.ias.claude.pressure.threshold (downward, 0.80) | 30 | no verify |
| … | |||
| 87 | ai.ias.claude.session.end | 45 | clean exit via Gate 9 |
| 87v | ….session.end.verify.gryph.statefile_write | 45 | Gate 9 finalisation confirmed |
Missing a v line for any row where one is expected is an audit signal.
sequence MUST be strictly monotonic within a session across primary events.turn_count MUST be non-decreasing across a session.gc.proposed → gc.accepted or gc.declined MUST be paired (exactly one response per proposal).domain.unloaded events that follow a gc.accepted MUST reference it via gc_acceptance_event_id.session.start MUST be the first primary event; session.end MUST be the last.m.relates_to with rel_type: "foundation.protocols.verify.v1" per CORRELATION.session_id.Full emission-paths implementation notes — including the bridge-side marker protocol and the gryph-side Check observing Writes to STATEOFCLAUDE.md — are in the canonical spec at Codeberg.