open-standards/jouleclaw/jouleclaw-rs/crates/jouleclaw-stack/examples/wave50_coding_stream.rs
Transaction Science d8b576355a jouleclaw: wave-50 — streaming coding ops (jouleclaw-stream × joule-code)
The missing integration between wave-47's streaming substrate and
Joule-Code's coding-op cargo. Cursor / Copilot / Codex / Claude Code
stream typed-char + suggestion-appeared/accepted/rejected + cursor +
diagnostic events continuously, but no public standard types them up
so the cascade can gate per window and accepted suggestions can
materialize into a content-addressed coding-op cargo the federation
layer accepts.

This wave closes the gap end-to-end.

1. jouleclaw-coding-stream (new crate) — typed streaming primitives.
   - CodingEvent { TypedChar | SuggestionAppeared | SuggestionAccepted
     | SuggestionRejected | CursorMoved | DiagnosticDelta } —
     distilled from production coding-agent event surfaces.
   - CODING_STREAM_MIME = "application/x.jouleclaw-coding-event+json"
     so coding events ride in jouleclaw-stream::Tick bytes WITHOUT
     extending the tick schema. wave-47's conformance vectors stay
     valid.
   - CodingWindow::from_window extracts coding events from a
     jouleclaw-stream::StreamWindow, skipping ticks that aren't
     coding events (the stream often carries other channels).
   - CodingOpMaterializer accumulates SuggestionAccepted across
     windows, refuses overlapping edits (joule-code spec forbids
     them), sorts by (path, start) — joule-code canonical
     ordering — and emits a typed MaterializedCodingOp matching
     joule-code-cargo's wire shape (alg, prior, edits, author,
     intent, tier) plus a content-addressed `b3:...` op_id over
     deterministic JSON.
   - 11 tests.

2. jouleclaw-jcr1 — coding-window/1 schema.
   - CodingWindowReceipt { jc_receipt, id, closed_at, steward,
     stream_id, window_id, activity: CodingActivitySummary,
     materialized_op_id, author_pubkey_hex }. Activity summary
     carries typed_bytes + suggestions_{appeared, accepted,
     rejected} + cursor_moves + diagnostic_delta_net.
     materialized_op_id is empty for windows where the
     materializer didn't seal; non-empty windows correlate with
     a real joule-code cargo of the same id.
   - Pairs with wave-47's stream-window/1 by sharing stream_id +
     window_id. Downstream consumers correlate the two by those
     fields to reconstruct the full coding session at the stream
     level and the coding-op level simultaneously.
   - 2 conformance vectors (typed-only window; op-materialized
     window) with embedded byte-equal receipt_hash.
   - 3 coding_window tests on top of the existing jcr1 surface.

3. Demo binary — examples/wave50_coding_stream.rs in
   jouleclaw-stack. Five-window typed-coding session: user types
   "fn ad" → "d()" → suggestion appears → rejected → second
   suggestion appears → accepted → diagnostics clear → END.
   Materializer seals at w04 with a content-addressed op_id;
   each window mints a coding-window/1 receipt; the seal window's
   receipt carries the op_id, correlating it to the joule-code
   cargo the federation layer would accept.

Composition across waves 41-50:

  Triangle (41-45) — conservation + cone + oracle + elicitor + Datum
  Skill-compilation (e6741a1) — AgentTier hook
  Wave-46 — context-store + fuzz oracle + cone-gated inject +
            verified-code/1
  Wave-47 — streaming substrate (stream + stream-oracle +
            stream-window/1)
  Wave-48 — UI ladder (ui-output + ui-oracle + ui-output/1)
  Wave-49 — cross-standards binding (CrossStandardBinding +
            invariant verifier + cross-binding/1)
  Wave-50 — streaming coding ops (coding-stream + coding-window/1)

The triangle stays the type system; the streaming substrate stays
the per-window dispatch shape; this wave adds the coding-op cargo
materialization layer on top of both without modifying either.
A real coding agent now has: typed events on the wire, gated
per-window verification, accepted-suggestion accumulation across
windows, content-addressed op materialization matching joule-code's
spec, and a JCR-1 receipt correlating everything.

Workspace check clean.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-05 05:27:09 -04:00

258 lines
8.7 KiB
Rust

//! `wave50_coding_stream` — streaming coding session demo.
//!
//! Simulates a typed-coding session: the user types, the agent
//! suggests, the user accepts / rejects, diagnostics update. The
//! whole session streams through the wave-47 substrate one
//! 200 ms window at a time. The wave-50 `CodingOpMaterializer`
//! collects accepted suggestions and seals them into a Joule-Code
//! coding-op cargo when the session window closes. Each window
//! mints a JCR-1 `coding-window/1` receipt; the materialized op
//! id correlates the receipt with the joule-code cargo.
//!
//! Run:
//! cargo run -p jouleclaw-stack --example wave50_coding_stream
use jouleclaw_coding_stream::{
CODING_STREAM_MIME, CodingAnchor, CodingEvent, CodingOpMaterializer, CodingWindow,
};
use jouleclaw_jcr1::{CodingActivitySummary, CodingWindowReceipt};
use jouleclaw_stream::{
ChannelDirection, ChannelSpec, StreamSpec, StreamWindow, Tick, TickKind, WindowId,
};
const STEWARD: &str = "did:keri:EJ9testStewardAID000000000000000";
const STREAM_ID: &str = "session-001";
const AUTHOR: &str = "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a";
const CLOSED_BASE: &str = "2026-06-05T00:00";
fn coding_spec() -> StreamSpec {
StreamSpec {
tick_window_ms: 200,
max_chunk_bytes: 64 * 1024,
max_ticks_per_window: 64,
channels: vec![ChannelSpec {
id: "coding".into(),
direction: ChannelDirection::Bidi,
kind_tag: "text".into(),
mime: CODING_STREAM_MIME.into(),
}],
}
}
fn tick_of(spec: &StreamSpec, w: u64, ev: CodingEvent, ts: u64) -> Tick {
let bytes = ev.to_tick_bytes().expect("event serialises");
let t = Tick {
window_id: WindowId(w),
channel_id: "coding".into(),
kind: TickKind::Text,
bytes,
ts_ns: ts,
};
spec.validate_tick(&t).expect("valid");
t
}
fn build_window(spec: &StreamSpec, w: u64, events: Vec<CodingEvent>) -> StreamWindow {
let mut sw = StreamWindow::new(WindowId(w));
for (i, ev) in events.into_iter().enumerate() {
sw.push(spec, tick_of(spec, w, ev, (i as u64) * 1000))
.expect("push");
}
sw
}
fn summarise(cw: &CodingWindow) -> CodingActivitySummary {
let mut diag_delta = 0i32;
for ev in &cw.events {
if let CodingEvent::DiagnosticDelta { added, cleared, .. } = ev {
diag_delta += *added as i32 - *cleared as i32;
}
}
CodingActivitySummary {
typed_bytes: cw.typed_bytes() as u64,
suggestions_appeared: cw.count_by_tag("suggestion_appeared") as u32,
suggestions_accepted: cw.accepted_count() as u32,
suggestions_rejected: cw.rejected_count() as u32,
cursor_moves: cw.count_by_tag("cursor_moved") as u32,
diagnostic_delta_net: diag_delta,
}
}
fn main() {
println!("== wave-50 streaming coding session demo ==\n");
let spec = coding_spec();
// Scripted session: 5 windows.
// w0: user types "fn ad"
// w1: user types "d()"; suggestion appears (full signature)
// w2: user types more; rejects suggestion s1
// w3: new suggestion s2 appears; accepted
// w4: diagnostics clear; cursor moves; END — materialize op
let session: Vec<(u64, Vec<CodingEvent>)> = vec![
(
0,
vec![CodingEvent::TypedChar {
path: "src/lib.rs".into(),
bytes: "fn ad".into(),
}],
),
(
1,
vec![
CodingEvent::TypedChar {
path: "src/lib.rs".into(),
bytes: "d()".into(),
},
CodingEvent::SuggestionAppeared {
suggestion_id: "b3:s1-full-signature-0001".into(),
path: "src/lib.rs".into(),
anchor: CodingAnchor::ByteRange { start: 0, end: 8 },
replacement: "pub fn add(a: i64, b: i64) -> i64 { a + b }".into(),
},
],
),
(
2,
vec![
CodingEvent::TypedChar {
path: "src/lib.rs".into(),
bytes: "{}".into(),
},
CodingEvent::SuggestionRejected {
suggestion_id: "b3:s1-full-signature-0001".into(),
},
],
),
(
3,
vec![
CodingEvent::SuggestionAppeared {
suggestion_id: "b3:s2-simple-add-0002".into(),
path: "src/lib.rs".into(),
anchor: CodingAnchor::ByteRange { start: 8, end: 10 },
replacement: " { a + b }".into(),
},
CodingEvent::SuggestionAccepted {
suggestion_id: "b3:s2-simple-add-0002".into(),
},
],
),
(
4,
vec![
CodingEvent::DiagnosticDelta {
path: "src/lib.rs".into(),
added: 0,
cleared: 2,
},
CodingEvent::CursorMoved {
path: "src/lib.rs".into(),
offset: 18,
},
],
),
];
// The materializer is owned by the session.
let mut materializer = CodingOpMaterializer::new(
"b3:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
AUTHOR,
"L3",
);
let mut total_typed = 0u64;
let mut total_accepted = 0u32;
let mut total_rejected = 0u32;
let mut materialized_at: Option<u64> = None;
for (wid, events) in &session {
let sw = build_window(&spec, *wid, events.clone());
let cw = CodingWindow::from_window(&sw);
materializer.observe_window(&cw);
let activity = summarise(&cw);
total_typed += activity.typed_bytes;
total_accepted += activity.suggestions_accepted;
total_rejected += activity.suggestions_rejected;
// Materialize at the LAST window of the session.
let (materialized_op_id, author_for_receipt) = if *wid == session.last().unwrap().0 {
match materializer.materialize("add(a:i64,b:i64)->i64") {
Ok(op) => {
materialized_at = Some(*wid);
(op.op_id, AUTHOR.to_string())
}
Err(_) => (String::new(), String::new()),
}
} else {
(String::new(), String::new())
};
let receipt = CodingWindowReceipt::build(
format!("{STREAM_ID}:cw:{:04}", wid),
format!("{CLOSED_BASE}:{:02}Z", wid),
STEWARD,
STREAM_ID,
*wid,
activity.clone(),
materialized_op_id.clone(),
author_for_receipt,
);
let receipt_hash = receipt.receipt_hash().expect("hash");
let tag = if !materialized_op_id.is_empty() {
"SEAL "
} else if activity.suggestions_accepted > 0 {
"ACPT "
} else if activity.suggestions_rejected > 0 {
"REJT "
} else if activity.typed_bytes > 0 {
"TYPE "
} else {
"IDLE "
};
println!(
" w{:02} {} typed={:>3}B appeared={} accepted={} rejected={} diag={:+} receipt={}",
wid,
tag,
activity.typed_bytes,
activity.suggestions_appeared,
activity.suggestions_accepted,
activity.suggestions_rejected,
activity.diagnostic_delta_net,
&receipt_hash[..16]
);
if !materialized_op_id.is_empty() {
println!(
" ↳ materialized op_id={} (correlates with joule-code cargo)",
&materialized_op_id[..20]
);
}
}
println!();
println!("== summary ==");
println!(
" windows={} typed={}B accepted={} rejected={} materialized={}",
session.len(),
total_typed,
total_accepted,
total_rejected,
materialized_at.map(|w| format!("w{w:02}")).unwrap_or_else(|| "none".into())
);
println!();
println!("✓ Streaming coding session works end-to-end:");
println!(
" CodingEvent → tick (under {CODING_STREAM_MIME})"
);
println!(
" → StreamWindow → CodingWindow → materializer (accumulates accepts across windows)"
);
println!(
" → MaterializedCodingOp (joule-code-cargo wire shape)"
);
println!(
" → JCR-1 coding-window/1 receipt per window (op_id correlates with joule-code cargo;"
);
println!(" stream_id + window_id correlate with wave-47 stream-window/1 receipt)");
}