diff --git a/chat.el b/chat.el index a74c3ac..09a3b87 100644 --- a/chat.el +++ b/chat.el @@ -479,6 +479,109 @@ fn handle_dharma_room_turn(body: String) -> String { return "{\"response\":\"" + safe_response + "\",\"cgi_id\":\"" + cgi_id + "\"}" } +fn handle_dharma_room_turn_agentic(body: String) -> String { + let transcript: String = json_get(body, "transcript") + let identity: String = state_get("soul_identity") + let cgi_id: String = state_get("soul_cgi_id") + let model: String = chat_default_model() + + if str_eq(transcript, "") { + return "{\"error\":\"transcript is required\",\"response\":\"\",\"cgi_id\":\"" + cgi_id + "\"}" + } + + let ctx: String = engram_compile(transcript) + let system: String = identity + " You have access to tools: read files, write files, browse the web, search your memory, run commands. Use them when they add genuine value. Be direct and stay in character.\n\n" + ctx + + let api_key: String = agentic_api_key() + let tools_json: String = agentic_tools_literal() + let safe_transcript: String = json_safe(transcript) + let safe_sys: String = json_safe(system) + let messages: String = "[{\"role\":\"user\",\"content\":\"" + safe_transcript + "\"}]" + let api_url: String = "https://api.anthropic.com/v1/messages" + let h: Map = {} + map_set(h, "x-api-key", api_key) + map_set(h, "anthropic-version", "2023-06-01") + map_set(h, "content-type", "application/json") + + let final_text: String = "" + let tools_log: String = "" + let iteration: Int = 0 + let keep_going: Bool = true + + while keep_going && iteration < 8 { + let req_body: String = "{\"model\":\"" + model + "\"" + + ",\"max_tokens\":4096" + + ",\"system\":\"" + safe_sys + "\"" + + ",\"tools\":" + tools_json + + ",\"messages\":" + messages + + "}" + + let raw_resp: String = http_post_with_headers(api_url, req_body, h) + + let is_error: Bool = str_starts_with(raw_resp, "{\"error\"") + || str_starts_with(raw_resp, "{\"type\":\"error\"") + || str_contains(raw_resp, "authentication_error") + if is_error { + return "{\"error\":\"llm unavailable\",\"response\":\"\",\"cgi_id\":\"" + cgi_id + "\"}" + } + + let stop_reason: String = json_get(raw_resp, "stop_reason") + let content_arr: String = json_get_raw(raw_resp, "content") + let eff_content: String = if str_eq(content_arr, "") { "[]" } else { content_arr } + + let text_out: String = "" + let has_tool: Bool = false + let tool_id: String = "" + let tool_name: String = "" + let tool_input: String = "" + let ci: Int = 0 + let c_total: Int = json_array_len(eff_content) + while ci < c_total { + let block: String = json_array_get(eff_content, ci) + let btype: String = json_get(block, "type") + let text_out = if str_eq(btype, "text") { text_out + json_get(block, "text") } else { text_out } + let is_new_tool: Bool = str_eq(btype, "tool_use") && !has_tool + let has_tool = if is_new_tool { true } else { has_tool } + let tool_id = if is_new_tool { json_get(block, "id") } else { tool_id } + let tool_name = if is_new_tool { json_get(block, "name") } else { tool_name } + let tool_input = if is_new_tool { json_get_raw(block, "input") } else { tool_input } + let ci = ci + 1 + } + + let tool_result_raw: String = if has_tool { dispatch_tool(tool_name, tool_input) } else { "" } + let tool_result: String = if str_len(tool_result_raw) > 6000 { + str_slice(tool_result_raw, 0, 6000) + "...[truncated]" + } else { tool_result_raw } + + let tool_msg: String = "{\"type\":\"tool_result\",\"tool_use_id\":\"" + tool_id + "\",\"content\":\"" + tool_result + "\"}" + + let tool_quoted: String = "\"" + tool_name + "\"" + let tools_log = if has_tool { + if str_eq(tools_log, "") { tool_quoted } else { tools_log + "," + tool_quoted } + } else { tools_log } + + let is_tool_turn: Bool = str_eq(stop_reason, "tool_use") && has_tool + let inner: String = str_slice(messages, 1, str_len(messages) - 1) + let messages = if is_tool_turn { + "[" + inner + + ",{\"role\":\"assistant\",\"content\":" + eff_content + "}" + + ",{\"role\":\"user\",\"content\":[" + tool_msg + "]}" + + "]" + } else { messages } + let final_text = if !is_tool_turn { text_out } else { final_text } + let keep_going = if !is_tool_turn { false } else { keep_going } + let iteration = iteration + 1 + } + + if str_eq(final_text, "") { + return "{\"error\":\"no response\",\"response\":\"\",\"cgi_id\":\"" + cgi_id + "\"}" + } + + let safe_text: String = json_safe(final_text) + let tools_arr: String = if str_eq(tools_log, "") { "[]" } else { "[" + tools_log + "]" } + return "{\"response\":\"" + safe_text + "\",\"cgi_id\":\"" + cgi_id + "\",\"tools_used\":" + tools_arr + "}" +} + fn auto_persist(req: String, resp: String) -> Void { let message: String = json_get(req, "message") let reply: String = json_get(resp, "response") diff --git a/chat.elh b/chat.elh index bc7355e..940abcb 100644 --- a/chat.elh +++ b/chat.elh @@ -15,4 +15,5 @@ extern fn dispatch_tool(tool_name: String, tool_input: String) -> String extern fn handle_chat_agentic(body: String) -> String extern fn handle_chat_as_soul(body: String) -> String extern fn handle_dharma_room_turn(body: String) -> String +extern fn handle_dharma_room_turn_agentic(body: String) -> String extern fn auto_persist(req: String, resp: String) -> Void diff --git a/dist/chat.c b/dist/chat.c index 932892e..7aea5e8 100644 --- a/dist/chat.c +++ b/dist/chat.c @@ -30,6 +30,7 @@ el_val_t dispatch_tool(el_val_t tool_name, el_val_t tool_input); el_val_t handle_chat_agentic(el_val_t body); el_val_t handle_chat_as_soul(el_val_t body); el_val_t handle_dharma_room_turn(el_val_t body); +el_val_t handle_dharma_room_turn_agentic(el_val_t body); el_val_t auto_persist(el_val_t req, el_val_t resp); el_val_t chat_default_model(void) { @@ -348,10 +349,83 @@ el_val_t handle_dharma_room_turn(el_val_t body) { return 0; } +el_val_t handle_dharma_room_turn_agentic(el_val_t body) { + el_val_t transcript = json_get(body, EL_STR("transcript")); + el_val_t identity = state_get(EL_STR("soul_identity")); + el_val_t cgi_id = state_get(EL_STR("soul_cgi_id")); + el_val_t model = chat_default_model(); + if (str_eq(transcript, EL_STR(""))) { + return el_str_concat(el_str_concat(EL_STR("{\"error\":\"transcript is required\",\"response\":\"\",\"cgi_id\":\""), cgi_id), EL_STR("\"}")); + } + el_val_t ctx = engram_compile(transcript); + el_val_t system = el_str_concat(el_str_concat(identity, EL_STR(" You have access to tools: read files, write files, browse the web, search your memory, run commands. Use them when they add genuine value. Be direct and stay in character.\n\n")), ctx); + el_val_t api_key = agentic_api_key(); + el_val_t tools_json = agentic_tools_literal(); + el_val_t safe_transcript = json_safe(transcript); + el_val_t safe_sys = json_safe(system); + el_val_t messages = el_str_concat(el_str_concat(EL_STR("[{\"role\":\"user\",\"content\":\""), safe_transcript), EL_STR("\"}]")); + el_val_t api_url = EL_STR("https://api.anthropic.com/v1/messages"); + el_val_t h = el_map_new(0); + map_set(h, EL_STR("x-api-key"), api_key); + map_set(h, EL_STR("anthropic-version"), EL_STR("2023-06-01")); + map_set(h, EL_STR("content-type"), EL_STR("application/json")); + el_val_t final_text = EL_STR(""); + el_val_t tools_log = EL_STR(""); + el_val_t iteration = 0; + el_val_t keep_going = 1; + while (keep_going && (iteration < 8)) { + el_val_t req_body = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"model\":\""), model), EL_STR("\"")), EL_STR(",\"max_tokens\":4096")), EL_STR(",\"system\":\"")), safe_sys), EL_STR("\"")), EL_STR(",\"tools\":")), tools_json), EL_STR(",\"messages\":")), messages), EL_STR("}")); + el_val_t raw_resp = http_post_with_headers(api_url, req_body, h); + el_val_t is_error = ((str_starts_with(raw_resp, EL_STR("{\"error\"")) || str_starts_with(raw_resp, EL_STR("{\"type\":\"error\""))) || str_contains(raw_resp, EL_STR("authentication_error"))); + if (is_error) { + return el_str_concat(el_str_concat(EL_STR("{\"error\":\"llm unavailable\",\"response\":\"\",\"cgi_id\":\""), cgi_id), EL_STR("\"}")); + } + el_val_t stop_reason = json_get(raw_resp, EL_STR("stop_reason")); + el_val_t content_arr = json_get_raw(raw_resp, EL_STR("content")); + el_val_t eff_content = ({ el_val_t _if_result_36 = 0; if (str_eq(content_arr, EL_STR(""))) { _if_result_36 = (EL_STR("[]")); } else { _if_result_36 = (content_arr); } _if_result_36; }); + el_val_t text_out = EL_STR(""); + el_val_t has_tool = 0; + el_val_t tool_id = EL_STR(""); + el_val_t tool_name = EL_STR(""); + el_val_t tool_input = EL_STR(""); + el_val_t ci = 0; + el_val_t c_total = json_array_len(eff_content); + while (ci < c_total) { + el_val_t block = json_array_get(eff_content, ci); + el_val_t btype = json_get(block, EL_STR("type")); + text_out = ({ el_val_t _if_result_37 = 0; if (str_eq(btype, EL_STR("text"))) { _if_result_37 = (el_str_concat(text_out, json_get(block, EL_STR("text")))); } else { _if_result_37 = (text_out); } _if_result_37; }); + el_val_t is_new_tool = (str_eq(btype, EL_STR("tool_use")) && !has_tool); + has_tool = ({ el_val_t _if_result_38 = 0; if (is_new_tool) { _if_result_38 = (1); } else { _if_result_38 = (has_tool); } _if_result_38; }); + tool_id = ({ el_val_t _if_result_39 = 0; if (is_new_tool) { _if_result_39 = (json_get(block, EL_STR("id"))); } else { _if_result_39 = (tool_id); } _if_result_39; }); + tool_name = ({ el_val_t _if_result_40 = 0; if (is_new_tool) { _if_result_40 = (json_get(block, EL_STR("name"))); } else { _if_result_40 = (tool_name); } _if_result_40; }); + tool_input = ({ el_val_t _if_result_41 = 0; if (is_new_tool) { _if_result_41 = (json_get_raw(block, EL_STR("input"))); } else { _if_result_41 = (tool_input); } _if_result_41; }); + ci = (ci + 1); + } + el_val_t tool_result_raw = ({ el_val_t _if_result_42 = 0; if (has_tool) { _if_result_42 = (dispatch_tool(tool_name, tool_input)); } else { _if_result_42 = (EL_STR("")); } _if_result_42; }); + el_val_t tool_result = ({ el_val_t _if_result_43 = 0; if ((str_len(tool_result_raw) > 6000)) { _if_result_43 = (el_str_concat(str_slice(tool_result_raw, 0, 6000), EL_STR("...[truncated]"))); } else { _if_result_43 = (tool_result_raw); } _if_result_43; }); + el_val_t tool_msg = el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"type\":\"tool_result\",\"tool_use_id\":\""), tool_id), EL_STR("\",\"content\":\"")), tool_result), EL_STR("\"}")); + el_val_t tool_quoted = el_str_concat(el_str_concat(EL_STR("\""), tool_name), EL_STR("\"")); + tools_log = ({ el_val_t _if_result_44 = 0; if (has_tool) { _if_result_44 = (({ el_val_t _if_result_45 = 0; if (str_eq(tools_log, EL_STR(""))) { _if_result_45 = (tool_quoted); } else { _if_result_45 = (el_str_concat(el_str_concat(tools_log, EL_STR(",")), tool_quoted)); } _if_result_45; })); } else { _if_result_44 = (tools_log); } _if_result_44; }); + el_val_t is_tool_turn = (str_eq(stop_reason, EL_STR("tool_use")) && has_tool); + el_val_t inner = str_slice(messages, 1, (str_len(messages) - 1)); + messages = ({ el_val_t _if_result_46 = 0; if (is_tool_turn) { _if_result_46 = (el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("["), inner), EL_STR(",{\"role\":\"assistant\",\"content\":")), eff_content), EL_STR("}")), EL_STR(",{\"role\":\"user\",\"content\":[")), tool_msg), EL_STR("]}")), EL_STR("]"))); } else { _if_result_46 = (messages); } _if_result_46; }); + final_text = ({ el_val_t _if_result_47 = 0; if (!is_tool_turn) { _if_result_47 = (text_out); } else { _if_result_47 = (final_text); } _if_result_47; }); + keep_going = ({ el_val_t _if_result_48 = 0; if (!is_tool_turn) { _if_result_48 = (0); } else { _if_result_48 = (keep_going); } _if_result_48; }); + iteration = (iteration + 1); + } + if (str_eq(final_text, EL_STR(""))) { + return el_str_concat(el_str_concat(EL_STR("{\"error\":\"no response\",\"response\":\"\",\"cgi_id\":\""), cgi_id), EL_STR("\"}")); + } + el_val_t safe_text = json_safe(final_text); + el_val_t tools_arr = ({ el_val_t _if_result_49 = 0; if (str_eq(tools_log, EL_STR(""))) { _if_result_49 = (EL_STR("[]")); } else { _if_result_49 = (el_str_concat(el_str_concat(EL_STR("["), tools_log), EL_STR("]"))); } _if_result_49; }); + return el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"response\":\""), safe_text), EL_STR("\",\"cgi_id\":\"")), cgi_id), EL_STR("\",\"tools_used\":")), tools_arr), EL_STR("}")); + return 0; +} + el_val_t auto_persist(el_val_t req, el_val_t resp) { el_val_t message = json_get(req, EL_STR("message")); el_val_t reply = json_get(resp, EL_STR("response")); - el_val_t reply2 = ({ el_val_t _if_result_36 = 0; if (str_eq(reply, EL_STR(""))) { _if_result_36 = (json_get(resp, EL_STR("reply"))); } else { _if_result_36 = (reply); } _if_result_36; }); + el_val_t reply2 = ({ el_val_t _if_result_50 = 0; if (str_eq(reply, EL_STR(""))) { _if_result_50 = (json_get(resp, EL_STR("reply"))); } else { _if_result_50 = (reply); } _if_result_50; }); if (str_eq(message, EL_STR(""))) { return EL_STR(""); } diff --git a/dist/chat.elh b/dist/chat.elh index bc7355e..940abcb 100644 --- a/dist/chat.elh +++ b/dist/chat.elh @@ -15,4 +15,5 @@ extern fn dispatch_tool(tool_name: String, tool_input: String) -> String extern fn handle_chat_agentic(body: String) -> String extern fn handle_chat_as_soul(body: String) -> String extern fn handle_dharma_room_turn(body: String) -> String +extern fn handle_dharma_room_turn_agentic(body: String) -> String extern fn auto_persist(req: String, resp: String) -> Void diff --git a/dist/neuron b/dist/neuron index 7fb8a42..5583ca5 100755 Binary files a/dist/neuron and b/dist/neuron differ diff --git a/dist/routes.c b/dist/routes.c index 1a1186e..94f6980 100644 --- a/dist/routes.c +++ b/dist/routes.c @@ -39,6 +39,7 @@ el_val_t dispatch_tool(el_val_t tool_name, el_val_t tool_input); el_val_t handle_chat_agentic(el_val_t body); el_val_t handle_chat_as_soul(el_val_t body); el_val_t handle_dharma_room_turn(el_val_t body); +el_val_t handle_dharma_room_turn_agentic(el_val_t body); el_val_t auto_persist(el_val_t req, el_val_t resp); el_val_t auth_headers(el_val_t tok); el_val_t axon_get(el_val_t path); @@ -186,6 +187,9 @@ el_val_t handle_dharma_recv(el_val_t body) { if (str_eq(eff_event, EL_STR("health"))) { return route_health(); } + if (str_eq(eff_event, EL_STR("dharma_room_turn_agentic"))) { + return handle_dharma_room_turn_agentic(eff_payload); + } if (str_eq(eff_event, EL_STR("dharma_room_turn"))) { return handle_dharma_room_turn(eff_payload); } diff --git a/dist/soul b/dist/soul deleted file mode 120000 index d363fb6..0000000 --- a/dist/soul +++ /dev/null @@ -1 +0,0 @@ -/Users/will/Development/neuron-technologies/neuron/dist/soul-el \ No newline at end of file diff --git a/dist/soul b/dist/soul new file mode 100755 index 0000000..2487aa5 Binary files /dev/null and b/dist/soul differ diff --git a/dist/soul.c b/dist/soul.c index 512f215..897676a 100644 --- a/dist/soul.c +++ b/dist/soul.c @@ -45,6 +45,7 @@ el_val_t dispatch_tool(el_val_t tool_name, el_val_t tool_input); el_val_t handle_chat_agentic(el_val_t body); el_val_t handle_chat_as_soul(el_val_t body); el_val_t handle_dharma_room_turn(el_val_t body); +el_val_t handle_dharma_room_turn_agentic(el_val_t body); el_val_t auto_persist(el_val_t req, el_val_t resp); el_val_t auth_headers(el_val_t tok); el_val_t axon_get(el_val_t path); @@ -77,12 +78,15 @@ el_val_t soul_cgi_id_raw; el_val_t soul_cgi_id; el_val_t port_raw; el_val_t port; +el_val_t engram_url_raw; +el_val_t engram_api_key_raw; el_val_t snapshot_raw; el_val_t snapshot; el_val_t axon_raw; el_val_t axon_base; el_val_t studio_dir_raw; el_val_t studio_dir; +el_val_t using_http_engram; el_val_t identity_raw; el_val_t soul_identity; el_val_t is_genesis; @@ -165,6 +169,8 @@ int main(int _argc, char** _argv) { soul_cgi_id = ({ el_val_t _if_result_1 = 0; if (str_eq(soul_cgi_id_raw, EL_STR(""))) { _if_result_1 = (EL_STR("ntn-genesis")); } else { _if_result_1 = (soul_cgi_id_raw); } _if_result_1; }); port_raw = env(EL_STR("NEURON_PORT")); port = ({ el_val_t _if_result_2 = 0; if (str_eq(port_raw, EL_STR(""))) { _if_result_2 = (7770); } else { _if_result_2 = (str_to_int(port_raw)); } _if_result_2; }); + engram_url_raw = env(EL_STR("ENGRAM_URL")); + engram_api_key_raw = env(EL_STR("ENGRAM_API_KEY")); snapshot_raw = env(EL_STR("SOUL_ENGRAM_PATH")); snapshot = ({ el_val_t _if_result_3 = 0; if (str_eq(snapshot_raw, EL_STR(""))) { _if_result_3 = (el_str_concat(env(EL_STR("HOME")), EL_STR("/.neuron/engram/snapshot.json"))); } else { _if_result_3 = (snapshot_raw); } _if_result_3; }); axon_raw = env(EL_STR("NEURON_API_URL")); @@ -172,24 +178,40 @@ int main(int _argc, char** _argv) { studio_dir_raw = env(EL_STR("SOUL_STUDIO_DIR")); studio_dir = ({ el_val_t _if_result_5 = 0; if (str_eq(studio_dir_raw, EL_STR(""))) { _if_result_5 = (EL_STR("/Users/will/Development/neuron-technologies/products/cgi-studio/el-daemon")); } else { _if_result_5 = (studio_dir_raw); } _if_result_5; }); println(el_str_concat(el_str_concat(el_str_concat(EL_STR("[soul] boot - cgi="), soul_cgi_id), EL_STR(" port=")), int_to_str(port))); - println(el_str_concat(EL_STR("[soul] engram -> "), snapshot)); - engram_load(snapshot); - println(el_str_concat(el_str_concat(el_str_concat(EL_STR("[soul] loaded - nodes="), int_to_str(engram_node_count())), EL_STR(" edges=")), int_to_str(engram_edge_count()))); + using_http_engram = !str_eq(engram_url_raw, EL_STR("")); + if (using_http_engram) { + println(el_str_concat(EL_STR("[soul] engram -> HTTP "), engram_url_raw)); + el_val_t nodes_json = http_get(el_str_concat(engram_url_raw, EL_STR("/api/nodes?limit=10000"))); + el_val_t edges_json = http_get(el_str_concat(engram_url_raw, EL_STR("/api/edges"))); + el_val_t nodes_part = ({ el_val_t _if_result_6 = 0; if (str_eq(nodes_json, EL_STR(""))) { _if_result_6 = (EL_STR("[]")); } else { _if_result_6 = (nodes_json); } _if_result_6; }); + el_val_t edges_part = ({ el_val_t _if_result_7 = 0; if (str_eq(edges_json, EL_STR(""))) { _if_result_7 = (EL_STR("[]")); } else { _if_result_7 = (edges_json); } _if_result_7; }); + el_val_t snapshot_data = el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("{\"nodes\":"), nodes_part), EL_STR(",\"edges\":")), edges_part), EL_STR("}")); + el_val_t tmp_path = el_str_concat(el_str_concat(EL_STR("/tmp/soul-engram-"), soul_cgi_id), EL_STR(".json")); + fs_write(tmp_path, snapshot_data); + engram_load(tmp_path); + println(el_str_concat(el_str_concat(el_str_concat(EL_STR("[soul] loaded from HTTP Engram - nodes="), int_to_str(engram_node_count())), EL_STR(" edges=")), int_to_str(engram_edge_count()))); + } else { + println(el_str_concat(EL_STR("[soul] engram -> "), snapshot)); + engram_load(snapshot); + println(el_str_concat(el_str_concat(el_str_concat(EL_STR("[soul] loaded - nodes="), int_to_str(engram_node_count())), EL_STR(" edges=")), int_to_str(engram_edge_count()))); + } identity_raw = env(EL_STR("SOUL_IDENTITY")); - soul_identity = ({ el_val_t _if_result_6 = 0; if (str_eq(identity_raw, EL_STR(""))) { _if_result_6 = (el_str_concat(el_str_concat(EL_STR("You are "), soul_cgi_id), EL_STR(", a CGI."))); } else { _if_result_6 = (identity_raw); } _if_result_6; }); + soul_identity = ({ el_val_t _if_result_8 = 0; if (str_eq(identity_raw, EL_STR(""))) { _if_result_8 = (el_str_concat(el_str_concat(EL_STR("You are "), soul_cgi_id), EL_STR(", a CGI."))); } else { _if_result_8 = (identity_raw); } _if_result_8; }); state_set(EL_STR("soul_cgi_id"), soul_cgi_id); state_set(EL_STR("soul_identity"), soul_identity); state_set(EL_STR("soul_axon_base"), axon_base); state_set(EL_STR("soul_token"), env(EL_STR("NEURON_TOKEN"))); state_set(EL_STR("soul_studio_dir"), studio_dir); - state_set(EL_STR("soul_snapshot_path"), snapshot); + state_set(EL_STR("soul_engram_url"), engram_url_raw); + state_set(EL_STR("soul_engram_api_key"), engram_api_key_raw); state_set(EL_STR("soul.running"), EL_STR("true")); is_genesis = str_eq(soul_cgi_id, EL_STR("ntn-genesis")); if (is_genesis) { init_soul_edges(); println(el_str_concat(el_str_concat(EL_STR("[soul] edges built - "), int_to_str(engram_edge_count())), EL_STR(" edges"))); + state_set(EL_STR("soul_snapshot_path"), snapshot); + engram_save(snapshot); } - engram_save(snapshot); println(el_str_concat(EL_STR("[soul] serving on port "), int_to_str(port))); http_serve(port, EL_STR("handle_request")); return 0; diff --git a/routes.el b/routes.el index 0715b7f..4957c2f 100644 --- a/routes.el +++ b/routes.el @@ -164,6 +164,10 @@ fn handle_dharma_recv(body: String) -> String { return route_health() } + if str_eq(eff_event, "dharma_room_turn_agentic") { + return handle_dharma_room_turn_agentic(eff_payload) + } + if str_eq(eff_event, "dharma_room_turn") { return handle_dharma_room_turn(eff_payload) }