Back to feed

sledtools/pika branch #90

pika-cloud-lifecycle-events

Add lifecycle event helpers

Target branch: master

Merge Commit: d79a3e61326900f1a1079be8821c2649850306ee

branch: merged tutorial: ready ci: success
Open CI Details

Continuous Integration

CI: success

Compact status on the review page, with full logs on the CI page.

Open CI Details

Latest run #116 success

6 passed

head 552ca2df2cd3c0192828928963ba12866dd51304 · queued 2026-03-26 01:14:32 · 6 lane(s)

queued 6s · ran 1m 58s

check-pika-followup · success check-notifications · success check-agent-contracts · success check-pikachat · success check-apple-host-sanity · success check-fixture · success

Summary

This branch adds compact JSON-line encode/decode helpers for LifecycleEvent in the pika-cloud crate, complementing the existing pretty-print and byte-oriented serialization used for status snapshots and terminal results. The new encode_runtime_event_line and decode_runtime_event_line functions produce single-line (no trailing newline) JSON strings suitable for structured logging or streaming event protocols. The branch also updates the orchestration skill guide with a journaling practice for capturing simplification ideas during implementation work.

Tutorial Steps

Add journaling guidance to the orchestration skill

Intent: Introduce a lightweight practice of keeping a local, untracked JOURNAL.md during multi-chunk implementation programs so that cleanup ideas are captured without widening the active chunk of work.

Affected files: .agents/skills/orchestrate/SKILL.md

Evidence
@@ -19,6 +19,10 @@ Use this skill when running a multi-chunk implementation program in `pika` where
+- Keep a local untracked `JOURNAL.md` in the worktree for simplification opportunities, abstraction ideas, and cleanup candidates noticed in the weeds.
+  - Capture the note quickly and keep moving.
+  - Do not widen the active chunk just because the journal grew.
+  - Triage the journal at the end of the broader project or migration once the primary seams are landed.

A new bullet block is added to the orchestration skill's workflow rules. The guidance addresses a common problem during large migrations: developers notice refactoring opportunities while deep in implementation but risk scope creep if they act on them immediately.

The rule prescribes four behaviors:

  1. Maintain a local, untracked JOURNAL.md per worktree.
  2. Capture simplification ideas, abstraction candidates, and cleanup notes quickly.
  3. Do not expand the current chunk based on journal entries.
  4. Triage the journal only after the primary seams of the broader project have landed.

This keeps the working branch focused while preserving institutional knowledge about tech-debt opportunities.

Implement encode_runtime_event_line and decode_runtime_event_line

Intent: Provide a pair of helpers that serialize a `LifecycleEvent` to a compact, single-line JSON string and deserialize it back, suitable for newline-delimited event streams or structured log lines.

Affected files: crates/pika-cloud/src/lifecycle.rs

Evidence
@@ -129,6 +129,14 @@ pub fn decode_runtime_terminal_result(bytes: &[u8]) -> serde_json::Result<Runtim
+pub fn encode_runtime_event_line(event: &LifecycleEvent) -> serde_json::Result<String> {
+    serde_json::to_string(event)
+}
+
+pub fn decode_runtime_event_line(line: &str) -> serde_json::Result<LifecycleEvent> {
+    serde_json::from_str(line)
+}

Two new public functions are added to crates/pika-cloud/src/lifecycle.rs:

  • encode_runtime_event_line(&LifecycleEvent) -> Result<String> — delegates to serde_json::to_string, producing compact (non-pretty) JSON with no trailing newline. This is the key difference from the existing encode_runtime_status_pretty which targets file persistence.
  • decode_runtime_event_line(&str) -> Result<LifecycleEvent> — delegates to serde_json::from_str, the string-slice counterpart to the existing decode_runtime_status which operates on &[u8].

The naming convention (*_event_line) signals that these are intended for line-oriented protocols (e.g., one JSON object per line in a stream), distinguishing them from the byte-oriented status snapshot helpers.

Export the new helpers from the crate root

Intent: Make `encode_runtime_event_line` and `decode_runtime_event_line` accessible to downstream crates through the `pika-cloud` public API.

Affected files: crates/pika-cloud/src/lib.rs

Evidence
@@ -11,8 +11,8 @@ pub use incus::{
 pub use lifecycle::{
     LIFECYCLE_SCHEMA_VERSION, LifecycleEvent, LifecycleState, RuntimeResultStatus,
-    RuntimeStatusSnapshot, RuntimeTerminalResult, decode_runtime_status,
-    decode_runtime_terminal_result, encode_runtime_status_pretty,
+    RuntimeStatusSnapshot, RuntimeTerminalResult, decode_runtime_event_line, decode_runtime_status,
+    decode_runtime_terminal_result, encode_runtime_event_line, encode_runtime_status_pretty,

The pub use lifecycle::{…} block in crates/pika-cloud/src/lib.rs is updated to include both decode_runtime_event_line and encode_runtime_event_line. The items remain in alphabetical order, consistent with the existing style of the re-export list.

Add round-trip and rejection tests for event line helpers

Intent: Verify that the new helpers correctly round-trip a fully populated `LifecycleEvent` and that deserialization rejects unknown `LifecycleEventKind` variants, ensuring forward-compatibility safety.

Affected files: crates/pika-cloud/src/lifecycle.rs

Evidence
@@ -162,6 +170,41 @@ mod tests {
+    #[test]
+    fn runtime_event_line_helpers_round_trip() {
+        let event = LifecycleEvent {
+            schema_version: LIFECYCLE_SCHEMA_VERSION,
+            seq: 7,
+            timestamp: "2026-03-25T20:00:00Z".to_string(),
+            kind: LifecycleEventKind::Ready,
+            message: "guest declared readiness".to_string(),
+            boot_id: Some("boot-123".to_string()),
+            details: Some(serde_json::json!({ "service": "openclaw" })),
+        };
+
+        let encoded = encode_runtime_event_line(&event).expect("encode");
+        let decoded = decode_runtime_event_line(&encoded).expect("decode");
+
+        assert_eq!(decoded, event);
+        assert!(!encoded.ends_with('\n'));
+    }
+
+    #[test]
+    fn runtime_event_line_rejects_unknown_kind() {

Two unit tests are added to the existing mod tests block:

runtime_event_line_helpers_round_trip

Constructs a LifecycleEvent with all optional fields populated (boot_id, details with a nested JSON object). It encodes the event, decodes it, and asserts structural equality. A secondary assertion confirms the encoded string does not end with '\n', codifying the contract that callers are responsible for adding their own line terminator.

runtime_event_line_rejects_unknown_kind

Feeds a JSON payload containing "kind": "passed" (a value not in the LifecycleEventKind enum) into decode_runtime_event_line and asserts that:

  1. Deserialization returns an Err.
  2. The error message contains "unknown variant".

This test documents that the enum uses serde's default deny-unknown-variants behavior, which is important for safely evolving the lifecycle protocol — producers must not emit kinds that consumers cannot understand.

Diff