Append an LLM response node — the agent's final answer for the turn. Created at turn end from the agent's closing message: the stop payload's response field (`last_assistant_message`, `prompt_response`, …) or, failing that, the last assistant entry of the session transcript. This is the durable record of what the agent concluded*, complementing the tool-derived nodes that record what it did*. Re
(&mut self, text: &str, timestamp: i64)
| 178 | /// |
| 179 | /// Returns the new node's ID. |
| 180 | pub fn append_llm_response(&mut self, text: &str, timestamp: i64) -> String { |
| 181 | let summary = truncate_prompt(text, 200); |
| 182 | let mut node = GraphNode::new(self.next_id(), NodeKind::LlmResponse, timestamp, &summary); |
| 183 | |
| 184 | let stored: String = text.chars().take(MAX_RESPONSE_TEXT_LEN).collect(); |
| 185 | node.detail = Some(serde_json::json!({ |
| 186 | "response_text": stored, |
| 187 | "text_length": text.len(), |
| 188 | })); |
| 189 | |
| 190 | // Mark as classified so the Phase 3 consolidator doesn't touch it |
| 191 | node.classified = true; |
| 192 | node.confidence = Some(1.0); |
| 193 | |
| 194 | let node_id = node.id.clone(); |
| 195 | |
| 196 | // Edge: goal --led_to-→ response (the answer serves the prompt) |
| 197 | if let Some(goal) = &self.current_goal { |
| 198 | self.edges.push(GraphEdge::new( |
| 199 | goal.clone(), |
| 200 | node_id.clone(), |
| 201 | EdgeKind::LedTo, |
| 202 | )); |
| 203 | self.stats.edge_count += 1; |
| 204 | } |
| 205 | |
| 206 | // Also chain from the previous node for temporal ordering |
| 207 | if let Some(prev) = &self.last_node { |
| 208 | if self.current_goal.as_ref() != Some(prev) { |
| 209 | self.edges.push(GraphEdge::new( |
| 210 | prev.clone(), |
| 211 | node_id.clone(), |
| 212 | EdgeKind::LedTo, |
| 213 | )); |
| 214 | self.stats.edge_count += 1; |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | self.push_node(node); |
| 219 | node_id |
| 220 | } |
| 221 | |
| 222 | /// Append a human gate node (permission requested). |
| 223 | /// |