Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
75f20b1
feat: leg_corpus disk export + PEFT train/eval scripts
staticroostermedia-arch Jul 10, 2026
e30e328
docs: Glass-Box RSI v1 design spec (hybrid goals + LEG home)
staticroostermedia-arch Jul 10, 2026
fd420f1
docs: Glass-Box RSI v1 implementation plan
staticroostermedia-arch Jul 10, 2026
d298211
feat(glassbox): dual_loop + fire verify schemas and validators
staticroostermedia-arch Jul 10, 2026
5fde642
docs(skills): engram-glassbox-rsi fire lifecycle skill
staticroostermedia-arch Jul 10, 2026
3c2d4f0
docs: glassbox loop prompts v2 with fire goals and typed verify
staticroostermedia-arch Jul 10, 2026
9110e46
docs: runbook to mint glassbox parent goals via MCP
staticroostermedia-arch Jul 10, 2026
300c70f
docs: AGENT_MEMORY_CONTRACT glassbox fire-goal pointer
staticroostermedia-arch Jul 10, 2026
120af46
feat(leg-browser): glassbox sample fixture for offline smoke
staticroostermedia-arch Jul 10, 2026
eae0a83
docs: how to reschedule loops onto glassbox v2 prompts
staticroostermedia-arch Jul 10, 2026
2c5b0ad
feat(leg-browser): glassbox split-home view (?view=glassbox)
staticroostermedia-arch Jul 10, 2026
d4b9c4c
docs: LEG glassbox view usage
staticroostermedia-arch Jul 10, 2026
ccb45fd
fix(leg-browser): glassbox local inspector + full health chips
staticroostermedia-arch Jul 10, 2026
9acda1a
feat(bench): continuity_bench_v0 offline process-contract gates
staticroostermedia-arch Jul 10, 2026
e7ef954
feat(leg): --glassbox opens RSI process view (?view=glassbox)
staticroostermedia-arch Jul 10, 2026
06b142b
fix(ci): serialize turn_record LLM extract test against env races
staticroostermedia-arch Jul 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions SKILLS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Load the ritual skills in `docs/skills/` for full protocol detail (all aligned t
- [docs/skills/engram-session-end.md](docs/skills/engram-session-end.md) — Structured handoff packet (`session_end` JSON, COMPRESS, anchors).
- [docs/skills/engram-thought-tiles.md](docs/skills/engram-thought-tiles.md) — Structured offload (mandatory for meta, promote_hot for re-hydration).
- [docs/skills/engram-leg-wiki-starter.md](docs/skills/engram-leg-wiki-starter.md) — Bootstrap a personal knowledge wiki (LEG Browser + tiles).
- [docs/skills/engram-glassbox-rsi.md](docs/skills/engram-glassbox-rsi.md) — Hybrid fire goals + typed verify for scheduled loops + LEG glass box.

## Declarative Process Sheaf

Expand Down
79 changes: 78 additions & 1 deletion crates/engram-server/src/leg_corpus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ pub struct CorpusBuildResult {
pub candidates: usize,
pub export: ScrubExportResult,
pub homotopy: HomotopyReport,
/// Absolute path of full pack dump written for PEFT export (if any).
pub disk_export_path: Option<String>,
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -124,6 +126,10 @@ pub fn build_training_corpus(
);
let homotopy = verify_pack_homotopy(&export.packs, config.coherence_min);

// Full pack dump for PEFT (chat MCP truncates large packs arrays).
// ENGRAM_LORA_EXPORT_DIR overrides; else data/lora-export under cwd if present.
let disk_export_path = write_full_pack_export(corpus_concept, &export.packs, &homotopy);

if persist_manifest {
let manifest = json!({
"format": "leg_corpus_manifest_v1",
Expand All @@ -132,6 +138,7 @@ pub fn build_training_corpus(
"candidate_count": candidates.len(),
"pack_count": export.packs.len(),
"denied_count": export.denied.len(),
"disk_export_path": disk_export_path,
"homotopy": {
"checked": homotopy.checked,
"passed": homotopy.passed,
Expand Down Expand Up @@ -162,6 +169,67 @@ pub fn build_training_corpus(
candidates: candidates.len(),
export,
homotopy,
disk_export_path,
}
}

/// Write full `leg_corpus_batch_v1` JSON to disk for PEFT JSONL export.
/// Returns absolute path string when successful.
fn write_full_pack_export(
corpus_concept: &str,
packs: &[Value],
homotopy: &HomotopyReport,
) -> Option<String> {
let dir = std::env::var("ENGRAM_LORA_EXPORT_DIR").unwrap_or_else(|_| {
let cwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
let candidate = cwd.join("data/lora-export");
if candidate.is_dir() || cwd.join("data").is_dir() {
candidate.to_string_lossy().into_owned()
} else {
// Fall back under store-adjacent default in home
dirs_fallback_lora_dir()
}
});
let dir_path = std::path::PathBuf::from(&dir);
if let Err(e) = std::fs::create_dir_all(&dir_path) {
eprintln!("[leg_corpus] mkdir {dir}: {e}");
return None;
}
let safe_name = corpus_concept.replace([':', '/', '\\'], "_");
let file = dir_path.join(format!("{safe_name}_batch.json"));
let batch = json!({
"format": "leg_corpus_batch_v1",
"corpus_concept": corpus_concept,
"pack_format": PACK_FORMAT,
"pack_count": packs.len(),
"homotopy": {
"checked": homotopy.checked,
"passed": homotopy.passed,
"mean_coherence": homotopy.mean_coherence,
"min_coherence": homotopy.min_coherence,
},
"packs": packs,
});
match serde_json::to_vec_pretty(&batch) {
Ok(bytes) => {
if let Err(e) = std::fs::write(&file, bytes) {
eprintln!("[leg_corpus] write {}: {e}", file.display());
return None;
}
Some(file.to_string_lossy().into_owned())
}
Err(e) => {
eprintln!("[leg_corpus] serialize packs: {e}");
None
}
}
}

fn dirs_fallback_lora_dir() -> String {
if let Ok(home) = std::env::var("HOME") {
format!("{home}/.engram/lora-export")
} else {
"/tmp/engram-lora-export".into()
}
}

Expand All @@ -175,14 +243,23 @@ pub fn corpus_response(result: &CorpusBuildResult) -> Value {
"denied_count": result.export.denied.len(),
"failed_coherence_count": result.export.failed_coherence.len(),
"minted_derivatives": result.export.minted,
"disk_export_path": result.disk_export_path,
"homotopy": {
"checked": result.homotopy.checked,
"passed": result.homotopy.passed,
"mean_coherence": result.homotopy.mean_coherence,
"min_coherence": result.homotopy.min_coherence,
"failed": result.homotopy.failed,
},
"packs": result.export.packs,
// Omit full packs from MCP chat path when disk dump exists (token economy).
// Clients that need packs: read disk_export_path or set ENGRAM_LORA_EXPORT_INLINE=1.
"packs": if result.disk_export_path.is_some()
&& std::env::var("ENGRAM_LORA_EXPORT_INLINE").ok().as_deref() != Some("1")
{
Value::Array(vec![])
} else {
Value::Array(result.export.packs.clone())
},
"denied": result.export.denied,
"failed_coherence": result.export.failed_coherence,
})
Expand Down
81 changes: 61 additions & 20 deletions crates/engram-server/src/mcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9870,26 +9870,42 @@ mod tests {

fn spawn_mock_llm_server(facts: &str) -> (String, std::thread::JoinHandle<()>) {
let listener = TcpListener::bind("127.0.0.1:0").expect("bind mock llm");
listener
.set_nonblocking(false)
.expect("mock llm blocking accept");
let addr = listener.local_addr().unwrap();
let facts_json = facts.replace('\n', "\\n");
// Escape for JSON string content (not a full JSON encoder — facts are plain ASCII prose).
let facts_json = facts
.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\n', "\\n")
.replace('\r', "\\r");
let handle = std::thread::spawn(move || {
for _ in 0..2 {
if let Ok((mut stream, _)) = listener.accept() {
let mut buf = vec![0u8; 1 << 16];
let _ = stream.read(&mut buf);
let body = format!(
r#"{{"choices":[{{"message":{{"content":"{facts_json}"}}}}]}}"#
);
let resp = format!(
"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
body.len(),
body
);
let _ = stream.write_all(resp.as_bytes());
// Serve a few requests (turn_record may call twice + stray probes).
// Blocking accept is fine — test drops JoinHandle and does not join.
for _ in 0..8 {
match listener.accept() {
Ok((mut stream, _)) => {
let mut buf = vec![0u8; 1 << 16];
let _ = stream.read(&mut buf);
let body = format!(
r#"{{"choices":[{{"message":{{"content":"{facts_json}"}}}}]}}"#
);
let resp = format!(
"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
body.len(),
body
);
let _ = stream.write_all(resp.as_bytes());
let _ = stream.flush();
}
Err(_) => break,
}
}
});
(format!("http://{}", addr), handle)
// Brief settle so accept() is parked before client dials (CI scheduler noise).
std::thread::sleep(std::time::Duration::from_millis(20));
(format!("http://{addr}"), handle)
}

fn setup_post_clear_goals(store: &SharedStore) {
Expand All @@ -9914,6 +9930,16 @@ mod tests {

#[test]
fn verify_turn_record_llm_mcp_entrypoint() {
// Serialize against parallel tests that clobber ENGRAM_LLM_URL / TURN_LLM_EXTRACT
// (CI flake: heuristic fallback when env is stolen mid-test).
let _guard = CONTINUITY_TEST_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());

let prev_llm = std::env::var("ENGRAM_LLM_URL").ok();
let prev_extract = std::env::var("ENGRAM_TURN_EXTRACT").ok();
let prev_llm_extract = std::env::var("ENGRAM_TURN_LLM_EXTRACT").ok();

let tmp = unique_tmp("turn-llm");
let store = prep_store(&tmp);

Expand Down Expand Up @@ -9973,7 +9999,7 @@ mod tests {
block_bodies.push_str(&format!("\n--- {concept} ---\n{body}\n"));
assert!(
body.contains("**extraction:** llm"),
"expected LLM extract marker: {body}"
"expected LLM extract marker (CI flake if env race): {body}"
);
assert!(
body.contains("Relational recall") || body.contains("Auto-relate"),
Expand All @@ -9986,18 +10012,33 @@ mod tests {
&format!("LLM-extracted normalized statements in minted blocks:{block_bodies}"),
);

// Re-assert env before second call (defense against any async cleanup).
std::env::set_var("ENGRAM_LLM_URL", &base_url);
std::env::set_var("ENGRAM_TURN_LLM_EXTRACT", "1");
let resp2 = handle_tool_on_big_stack("mcp_engram_turn_record", &turn_args, &store);
append_evidence(
"turn_extract_llm.txt",
&format!("=== run 2 response ===\n{}", mcp_text(&resp2)),
);
assert!(!mcp_text(&resp2).is_empty());

let _ = mock_handle.join();
// Mock thread may still be in accept timeout — don't block test exit.
let _ = mock_handle;
let _ = std::fs::remove_dir_all(&tmp);
std::env::remove_var("ENGRAM_LLM_URL");
std::env::remove_var("ENGRAM_TURN_EXTRACT");
std::env::remove_var("ENGRAM_TURN_LLM_EXTRACT");

// Restore env (do not clobber neighboring tests after unlock).
match prev_llm {
Some(v) => std::env::set_var("ENGRAM_LLM_URL", v),
None => std::env::remove_var("ENGRAM_LLM_URL"),
}
match prev_extract {
Some(v) => std::env::set_var("ENGRAM_TURN_EXTRACT", v),
None => std::env::remove_var("ENGRAM_TURN_EXTRACT"),
}
match prev_llm_extract {
Some(v) => std::env::set_var("ENGRAM_TURN_LLM_EXTRACT", v),
None => std::env::remove_var("ENGRAM_TURN_LLM_EXTRACT"),
}
}

#[test]
Expand Down
8 changes: 8 additions & 0 deletions docs/AGENT_MEMORY_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ Wake lean-avoid (no `watch_workspace` at wake) is separate and still applies. Th

---

## Glass-Box RSI (scheduled fires)

Scheduled Dual RSI / Ship / PR / Stale / Aliveness fires **mint a child `goal:fire_*`**, run a **typed verify**, then update `helper:rsi_dual_loop_state.last_verify`. Do not flip stages or claim ship/PR ready without `verify_status=pass`.

See: [docs/skills/engram-glassbox-rsi.md](skills/engram-glassbox-rsi.md), [docs/superpowers/specs/2026-07-10-glassbox-rsi-design.md](superpowers/specs/2026-07-10-glassbox-rsi-design.md).

---

## Manage resume (TUI / MCP restart)

After **TUI restart**, **MCP transport death**, or **`cargo build`** on `engram-server`, the live MCP may run a stale binary until restart. Resume without re-briefing:
Expand Down
16 changes: 16 additions & 0 deletions docs/LEG_BROWSER.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,21 @@ Override the router tile explicitly: `?geo_lens=tile:formal_spec_geo-lens-router

---

## Glass-Box RSI view

```bash
./scripts/leg --live
# open http://127.0.0.1:8765/?view=glassbox
```

Shows health strip (`dual_loop` + aliveness), parent program goals, last fire verify, and activity. Read-only. Requires process contract (fire goals + `dual_loop` fields) for full fidelity; otherwise chips show unknown.

Offline fixture: serve `tools/leg-browser` and open `?view=glassbox&fixture=1` (loads `tools/leg-browser/fixtures/glassbox-sample.json`).

**Optional API decision:** Live mode multi-fetches existing endpoints (`/health`, anchors/goals, activity, etc.). If that multi-fetch is >3s on large stalks (~80k blocks) in practice, file a follow-up to implement `GET /api/glassbox` in `serve.rs` returning `dual_loop` + parents + last_fire only. **Not required for B1 acceptance.**

---

## Modes

| Mode | Command | Backend | What you see |
Expand All @@ -68,6 +83,7 @@ Live mode uses the same `ENGRAM_STORE` as `scripts/engram-grok` (MCP). TUI, Curs
- **Ariel cockpit** — `?cockpit=ariel` or `LEG_DEFAULT_GEO_LENS` boots Property Lens; ops strip (camera, Pi stream, YouTube) with labels from `/api/block/` or placeholders
- **Hygiene strip** — demote sprawl, condensation hints, wake/edit-arc debt (beta)
- **Code atlas + evolution timeline** — file-scoped loci, `__arc` segments, trace chain via `GET /api/code-atlas?evolution=1`
- **Glass-Box RSI view** — `?view=glassbox` (health / dual_loop, parent goals, last fire, activity); offline via `?fixture=1`

---

Expand Down
73 changes: 73 additions & 0 deletions docs/schemas/dual_loop_state_v1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://engram.dev/schemas/dual_loop_state_v1.json",
"title": "dual_loop_state_v1",
"description": "Control register for helper:rsi_dual_loop_state (Glass-Box RSI). Python validator in scripts/validate_dual_loop_schema.py is source of truth for tests.",
"type": "object",
"required": ["version", "track_next", "mcp_restart_required", "parents"],
"properties": {
"version": {
"const": 1,
"description": "Schema version; must be integer 1"
},
"track_next": {
"type": "string",
"enum": ["S", "G", "M"],
"description": "Next Dual RSI track to fire"
},
"track_last": {
"type": "string",
"enum": ["S", "G", "M"],
"description": "Last Dual RSI track that completed a fire"
},
"open_pr": {
"type": ["string", "null"],
"description": "Open ship PR URL, or null if none"
},
"mcp_restart_required": {
"type": "boolean",
"description": "True when binary_vs_proc verify found STALE binary vs process"
},
"last_fire_goal": {
"type": ["string", "null"],
"description": "Most recent child goal:fire_* id"
},
"last_verify": {
"type": "object",
"description": "Summary of last typed verify packet",
"required": ["type", "status"],
"properties": {
"type": {
"type": "string",
"description": "verify_type from fire packet (e.g. substrate_local)"
},
"status": {
"type": "string",
"enum": ["pending", "pass", "fail"]
},
"at": {
"type": "string",
"format": "date-time",
"description": "ISO-8601 timestamp of verify"
}
},
"additionalProperties": true
},
"parents": {
"type": "array",
"items": { "type": "string" },
"description": "Durable parent goal ids (dual_rsi_program, ship_substrate, glassbox_leg)"
},
"gemma": {
"type": "object",
"description": "Gemma track stage machine snapshot",
"properties": {
"stage": { "type": "string" },
"adapter_path": { "type": "string" },
"sft_rows": { "type": "integer", "minimum": 0 }
},
"additionalProperties": true
}
},
"additionalProperties": true
}
Loading
Loading