agent-loop: root turn driven by the library kernel (phase 2)
ChatSessionHandler now runs the root turn on the agent-loop kernel instead of run_agent_turn; sub-agents follow on the same kernel via DelegateTool. The old loop stays for resume/recovery until phase 3. agent-loop: - DelegateTool + AgentCatalog/AgentProfile (full toolset override, per-child selector/assembler, frame-scoped get), StaticCatalog, FilteredToolSet; sync flow with sticky child_token; batch via the generic fan-out - manager: start_loop skips the registry (children are not double-driving; they ride the parent's token tree); LoopParams gains selector/token overrides - store: get_frame, get_call, set_call_extras; HistoryStore result text aligned to raw-stored semantics (projection formats) - events: ApprovalRequired.request_id, AgentSpawned/Finished parent info; AskUserTool with_name + suggested_answers alias + Question.frame skald-core (loop_adapters + handler): - SkaldAssembler (byte-parity port of MessageBuilder's projection: scratchpad/summary/window, DTL Kimi/Anthropic injection, media, user coalescing, reasoning echo) + AgentSystemContext (prompt layers, substitutions, MCP list, shared folders, user profile) - SkaldAgentCatalog (build_sub_agent_config port), SkaldHumanChannel, scratchpad/todos tools, execute_task sync/async alias, LegacyInterfaceTool, PendingLiveInput - ApprovalGate: PendingWrite diffs via LoopEvent::Host (memory/disk routed like the fs-tools); SkaldWritePreviewHook for executed-write diffs; EventTranslator LoopEvent→ServerEvent (display meta, preview, FileChanged, AgentStart/Done, root-only Done/Truncated/Cancelled) - handle_message: builds TurnParams and drives the kernel; resume of pending tools runs first (results belong to the previous turn); ChatEvent publication stays handler-side; /stop cancels the live loop - ToolRegistry.get_tool/all_tools; def builders made pub(crate) Full workspace suite green (179 skald-core, 34 agent-loop, adapters incl.); two pre-existing doc-test failures fixed along the way.
This commit is contained in:
@@ -83,18 +83,23 @@ pub(crate) async fn run(
|
||||
events: events.clone(),
|
||||
};
|
||||
|
||||
// ToolCtx extensions: host extensions + the event sink, so shipped tools
|
||||
// (ask_user, activate_tools) can emit out-of-band.
|
||||
// ToolCtx extensions: host extensions + the event sink + the turn's tool
|
||||
// set, so shipped tools (ask_user, activate_tools, delegate) reach what
|
||||
// they need.
|
||||
let tool_extensions = || {
|
||||
let mut ext = params.extensions.clone();
|
||||
ext.insert(Arc::new(events.clone()));
|
||||
ext.insert(Arc::new(crate::tool::SharedToolSet(params.tools.clone())));
|
||||
ext
|
||||
};
|
||||
|
||||
events.emit(frame, parent, LoopEvent::TurnStarted);
|
||||
|
||||
// Per-loop selector override (sub-agents with their own strength, D14).
|
||||
let selector: &Arc<dyn ModelSelector> = params.selector.as_ref().unwrap_or(&deps.models);
|
||||
|
||||
// First selection of the turn.
|
||||
let mut handle: ModelHandle = match deps.models.select(¶ms.model_hint, &[]).await {
|
||||
let mut handle: ModelHandle = match selector.select(¶ms.model_hint, &[]).await {
|
||||
Ok(h) => h,
|
||||
Err(e) => {
|
||||
events.emit(frame, parent, LoopEvent::Error(format!("model selection failed: {e}")));
|
||||
@@ -171,11 +176,11 @@ pub(crate) async fn run(
|
||||
|
||||
match result {
|
||||
Ok(resp) => {
|
||||
deps.models.report_success(&handle.id).await;
|
||||
selector.report_success(&handle.id).await;
|
||||
break resp;
|
||||
}
|
||||
Err(e) => {
|
||||
deps.models.report_failure(&handle.id, &e.to_string()).await;
|
||||
selector.report_failure(&handle.id, &e.to_string()).await;
|
||||
let retriable = handle.model.is_retriable(&e);
|
||||
warn!(model = %handle.id, error = %e, retriable, "llm call failed");
|
||||
if !retriable || tried.len() >= deps.retry.max_attempts {
|
||||
@@ -185,7 +190,7 @@ pub(crate) async fn run(
|
||||
});
|
||||
return Err(anyhow!("llm call failed on {}: {e}", handle.id));
|
||||
}
|
||||
match deps.models.select(¶ms.model_hint, &tried).await {
|
||||
match selector.select(¶ms.model_hint, &tried).await {
|
||||
Ok(next) => {
|
||||
events.emit(frame, parent, LoopEvent::ModelFallback {
|
||||
from: handle.id.clone(),
|
||||
@@ -527,6 +532,7 @@ async fn pre_execution(
|
||||
name: ptc.name.clone(),
|
||||
args: ptc.arguments.clone(),
|
||||
frame: params.frame,
|
||||
parent_frame: params.parent_frame,
|
||||
agent: params.agent.clone(),
|
||||
extensions: params.extensions.clone(),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user