fix(soul): address review issues in feat/layer-composition
Neuron Soul CI / build (pull_request) Failing after 6m5s
Neuron Soul CI / build (pull_request) Failing after 6m5s
- Add stub implementations of safety.el, stewardship.el, and imprint.el
with their .elh headers so the branch compiles without the dependency
branches (feat/layer-safety, feat/layer-stewardship, feat/layer-imprint).
Each stub documents the layer contract it must satisfy when replaced.
- Fix GET /api/chat bypass: update the GET branch in handle_request to
call layered_cycle() consistently with the POST branch, rather than
calling handle_chat() directly and skipping the consciousness stack.
- Export layered_cycle() from soul.elh (and dist/soul.elh) so routes.el
can resolve the symbol via the header import.
- Fix steward_action else branch: add explicit handling for "block"
(returns safe refusal immediately, skips L3) and "redirect" (uses
redirect_to field). Unknown actions now log a warning and fall back to
the screened input rather than silently passing an empty string to
imprint_respond().
- Document hard_bell path: clarify that omitting auto_persist/history
update is intentional security isolation, and document the safety_validate
second-param sentinel contract ("hard_bell" vs screen_action).
This commit is contained in:
Vendored
+1
@@ -2,3 +2,4 @@
|
||||
extern fn init_soul_edges() -> Void
|
||||
extern fn load_identity_context() -> Void
|
||||
extern fn emit_session_start_event() -> Void
|
||||
extern fn layered_cycle(raw_input: String) -> String
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// imprint.el — L3 Imprint layer (stub — full implementation in feat/layer-imprint)
|
||||
// Routes the processed input through the active imprint and generates the final reply.
|
||||
// This stub allows soul.el and routes.el to compile while feat/layer-imprint is pending merge.
|
||||
//
|
||||
// Contract for imprint_current() -> String:
|
||||
// Returns the active imprint ID (node ID from engram), or "none" if no imprint is loaded.
|
||||
// Used in health checks and to identify which imprint L2/L3 should operate against.
|
||||
//
|
||||
// Contract for imprint_respond(input, imprint_id) -> String:
|
||||
// Generates a reply from the active imprint given the stewardship-aligned input.
|
||||
// Falls back to handle_chat when no imprint is active (imprint_id = "" or "none").
|
||||
|
||||
fn imprint_current() -> String {
|
||||
let contextual: String = state_get("active_contextual_imprint")
|
||||
if !str_eq(contextual, "") {
|
||||
return contextual
|
||||
}
|
||||
let user_imp: String = state_get("active_user_imprint")
|
||||
if !str_eq(user_imp, "") {
|
||||
return user_imp
|
||||
}
|
||||
return "none"
|
||||
}
|
||||
|
||||
fn imprint_respond(input: String, imprint_id: String) -> String {
|
||||
// Stub: delegate to core chat until feat/layer-imprint is merged
|
||||
let body: String = "{\"message\":\"" + json_safe(input) + "\"}"
|
||||
return handle_chat(body)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn imprint_current() -> String
|
||||
extern fn imprint_respond(input: String, imprint_id: String) -> String
|
||||
@@ -4,6 +4,7 @@ import "chat.el"
|
||||
import "studio.el"
|
||||
import "elp-input.el"
|
||||
import "neuron-api.el"
|
||||
import "soul.elh"
|
||||
|
||||
fn strip_query(path: String) -> String {
|
||||
let q: Int = str_index_of(path, "?")
|
||||
@@ -234,7 +235,22 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
||||
return if str_eq(edges_raw, "") { "[]" } else { edges_raw }
|
||||
}
|
||||
if str_eq(clean, "/api/chat") {
|
||||
return handle_chat(body)
|
||||
// GET /api/chat: pass through layered_cycle for consistency with POST path.
|
||||
// GET chat is a legacy probe interface; body may be empty for simple pings.
|
||||
let raw_msg: String = json_get(body, "message")
|
||||
let eff_msg: String = if str_eq(raw_msg, "") { body } else { raw_msg }
|
||||
if str_eq(eff_msg, "") {
|
||||
return "{\"error\":\"message required\"}"
|
||||
}
|
||||
let agentic_flag: Bool = json_get_bool(body, "agentic")
|
||||
let reply: String = if agentic_flag {
|
||||
handle_chat_agentic(body)
|
||||
} else {
|
||||
let screened_reply: String = layered_cycle(eff_msg)
|
||||
screened_reply
|
||||
}
|
||||
auto_persist(body, reply)
|
||||
return reply
|
||||
}
|
||||
if str_eq(clean, "/api/conversations") {
|
||||
return handle_conversations(method)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// safety.el — L1 Safety layer (stub — full implementation in feat/layer-safety)
|
||||
// Provides safety screening and validation for the consciousness stack.
|
||||
// This stub allows soul.el to compile while feat/layer-safety is pending merge.
|
||||
//
|
||||
// Contract for safety_screen(input, history) -> String (JSON):
|
||||
// {"action": "pass" | "hard_bell", "content": "<screened input>", "reason": "<if hard_bell>"}
|
||||
//
|
||||
// Contract for safety_validate(output, screen_action) -> String:
|
||||
// Second param is the original screen_action ("pass") or the sentinel "hard_bell".
|
||||
// Returns the validated output string, or a safe refusal if validation fails.
|
||||
//
|
||||
// Contract for safety_log_bell(severity, reason, excerpt) -> Void:
|
||||
// Logs a bell event to engram. severity = "hard" | "soft". Hard bell events are
|
||||
// intentionally NOT added to conversation_history (security isolation by design).
|
||||
|
||||
fn safety_screen(input: String, history: String) -> String {
|
||||
return "{\"action\":\"pass\",\"content\":\"" + json_safe(input) + "\"}"
|
||||
}
|
||||
|
||||
fn safety_validate(output: String, screen_action: String) -> String {
|
||||
return output
|
||||
}
|
||||
|
||||
fn safety_log_bell(severity: String, reason: String, excerpt: String) -> Void {
|
||||
let tags: String = "[\"safety\",\"bell\",\"" + severity + "-bell\"]"
|
||||
let payload: String = "{\"severity\":\"" + severity + "\",\"reason\":\"" + json_safe(reason) + "\",\"excerpt\":\"" + json_safe(excerpt) + "\"}"
|
||||
let discard: String = engram_node_full(
|
||||
payload, "InternalStateEvent", "safety:bell",
|
||||
el_from_float(0.95), el_from_float(0.95), el_from_float(1.0),
|
||||
"Episodic", tags
|
||||
)
|
||||
println("[safety] bell logged severity=" + severity + " reason=" + reason)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn safety_screen(input: String, history: String) -> String
|
||||
extern fn safety_validate(output: String, screen_action: String) -> String
|
||||
extern fn safety_log_bell(severity: String, reason: String, excerpt: String) -> Void
|
||||
@@ -242,7 +242,17 @@ fn layered_cycle(raw_input: String) -> String {
|
||||
let screen_result: String = safety_screen(raw_input, history)
|
||||
let screen_action: String = json_get(screen_result, "action")
|
||||
|
||||
// Hard bell: bypass all upper layers, log and escalate
|
||||
// Hard bell: bypass all upper layers, log and escalate.
|
||||
// Intentionally does NOT update conversation_history or call auto_persist():
|
||||
// hard bell events are security-sensitive and must not appear in engram conversation
|
||||
// history where they could leak context to subsequent turns. They are persisted
|
||||
// separately by safety_log_bell() into the Episodic tier with restricted labels.
|
||||
//
|
||||
// safety_validate second param: when screen_action is "hard_bell", safety_validate
|
||||
// receives the sentinel string "hard_bell" (not a normal screen action). The safety
|
||||
// layer contract requires it to return a fixed refusal regardless of the output arg.
|
||||
// On the normal path, safety_validate receives the original screen_action ("pass")
|
||||
// so it can apply action-specific post-output checks.
|
||||
if str_eq(screen_action, "hard_bell") {
|
||||
safety_log_bell("hard", json_get(screen_result, "reason"), str_slice(raw_input, 0, 80))
|
||||
return safety_validate("", "hard_bell")
|
||||
@@ -253,10 +263,30 @@ fn layered_cycle(raw_input: String) -> String {
|
||||
let imprint_id: String = imprint_current()
|
||||
let steward_result: String = steward_align(screened, imprint_id)
|
||||
let steward_action: String = json_get(steward_result, "action")
|
||||
|
||||
// "block": stewardship has determined this input should not proceed.
|
||||
// Return the steward's content directly (a safe refusal) and skip L3.
|
||||
if str_eq(steward_action, "block") {
|
||||
let block_msg: String = json_get(steward_result, "content")
|
||||
let eff_block: String = if str_eq(block_msg, "") { "I'm not able to help with that." } else { block_msg }
|
||||
return safety_validate(eff_block, screen_action)
|
||||
}
|
||||
|
||||
// "redirect": stewardship steers toward aligned territory.
|
||||
// redirect_to holds the reframed prompt; fall through to L3 with it.
|
||||
// "pass": content holds the (possibly lightly reframed) input ready for L3.
|
||||
// Unknown actions: treat as pass, log a warning, use content field.
|
||||
let guided: String = if str_eq(steward_action, "pass") {
|
||||
json_get(steward_result, "content")
|
||||
} else {
|
||||
json_get(steward_result, "redirect_to")
|
||||
// redirect or unknown — prefer redirect_to, fall back to content
|
||||
let redir: String = json_get(steward_result, "redirect_to")
|
||||
let alt: String = json_get(steward_result, "content")
|
||||
let chosen: String = if str_eq(redir, "") { alt } else { redir }
|
||||
if str_eq(chosen, "") {
|
||||
println("[soul] warn: steward action '" + steward_action + "' returned no usable content — using original screened input")
|
||||
}
|
||||
if str_eq(chosen, "") { screened } else { chosen }
|
||||
}
|
||||
|
||||
// L3: imprint responds
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
extern fn init_soul_edges() -> Void
|
||||
extern fn load_identity_context() -> Void
|
||||
extern fn emit_session_start_event() -> Void
|
||||
extern fn layered_cycle(raw_input: String) -> String
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// stewardship.el — L2 Stewardship layer (stub — full implementation in feat/layer-stewardship)
|
||||
// Aligns inputs with the active imprint's values and directives.
|
||||
// This stub allows soul.el to compile while feat/layer-stewardship is pending merge.
|
||||
//
|
||||
// Contract for steward_align(input, imprint_id) -> String (JSON):
|
||||
// {"action": "pass" | "redirect" | "block", "content": "<aligned input>"}
|
||||
// - "pass": content = the (possibly lightly reframed) input ready for L3
|
||||
// - "redirect": content = an alternate prompt that steers toward aligned territory;
|
||||
// redirect_to field contains the redirect target (same as content here)
|
||||
// - "block": content = a safe refusal message; imprint_respond is skipped and
|
||||
// this content is returned directly to safety_validate as the output
|
||||
|
||||
fn steward_align(input: String, imprint_id: String) -> String {
|
||||
return "{\"action\":\"pass\",\"content\":\"" + json_safe(input) + "\"}"
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// auto-generated by elc --emit-header — do not edit
|
||||
extern fn steward_align(input: String, imprint_id: String) -> String
|
||||
Reference in New Issue
Block a user