Skip to main content

corpus_ipc/models/
message.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use serde::{Deserialize, Serialize};
4
5use crate::validation::{ProtocolLimits, Validate, ValidationError, check_finite};
6
7use super::de::de_loss;
8use super::{
9    ConfigPayload, EmbeddingBatch, GradientBatch, NeuromodulatorSnapshot, SpikeBatch,
10    StimulusBatch, TraceBatch,
11};
12
13/// Core message enum for cross-process IPC.
14///
15/// Messages are separated into:
16/// - Input messages (spikes, embeddings, stimuli, neuromodulators, config)
17/// - Output messages (gradients, traces, training status)
18/// - Control messages (shutdown, ping)
19///
20/// Variant names (`Spikes`, `EligibilityTraces`, …) are serde identifiers and
21/// must stay stable. The payloads they wrap are IPC transport types, not
22/// in-memory training structs (see the module docs).
23///
24/// Versioned ingress should go through
25/// [`crate::decode_ipc_message_json`]: that entry point classifies
26/// [`crate::WireCompatibility`] before the payload is used. Direct
27/// `Deserialize` on this enum is the unversioned (legacy) encoding.
28///
29/// Unknown object fields on payloads are ignored (forward compatible).
30/// Unknown variant names fail to deserialize; this enum has no `Default`
31/// and no serde `other` catch-all, so they never become a valid message.
32#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
33pub enum IpcMessage {
34    // Input messages
35    /// Wire payload for an IPC [`SpikeBatch`] (not an in-memory training batch).
36    Spikes(SpikeBatch),
37    Embeddings(EmbeddingBatch),
38    /// Typed continuous runtime stimulus ingress between a producer and a
39    /// consumer. See [`StimulusBatch`] for channel-width and
40    /// invalid/missing-channel semantics.
41    Stimuli(StimulusBatch),
42    /// Typed neuromodulator ingress, replacing an unstructured float tail.
43    /// See [`NeuromodulatorSnapshot`] and [`Validate`].
44    Neuromodulators(NeuromodulatorSnapshot),
45    Loss(#[serde(deserialize_with = "de_loss")] f32),
46    ConfigUpdate(ConfigPayload),
47
48    // Output messages
49    GradientUpdate(GradientBatch),
50    /// Wire payload for an IPC [`TraceBatch`] (not an in-memory training batch).
51    EligibilityTraces(TraceBatch),
52    TrainingComplete,
53
54    // Control
55    Shutdown,
56    Ping,
57}
58
59impl Validate for IpcMessage {
60    fn validate_with(&self, limits: ProtocolLimits) -> Result<(), ValidationError> {
61        match self {
62            Self::Spikes(batch) => batch.validate_with(limits),
63            Self::Embeddings(batch) => batch.validate_with(limits),
64            Self::Stimuli(batch) => batch.validate_with(limits),
65            Self::Neuromodulators(snapshot) => snapshot.validate_with(limits),
66            Self::ConfigUpdate(payload) => payload.validate_with(limits),
67            Self::GradientUpdate(batch) => batch.validate_with(limits),
68            Self::EligibilityTraces(batch) => batch.validate_with(limits),
69            Self::Loss(value) => check_finite("Loss", *value),
70            Self::TrainingComplete | Self::Shutdown | Self::Ping => Ok(()),
71        }
72    }
73}