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]>
258 lines
8.7 KiB
Rust
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)");
|
|
}
|