agent-loop: Skald adapters behind the crate traits (phase 1)
New skald-core::loop_adapters module — implements the agent-loop trait
surface over existing infrastructure, unused by the current loop (wired
in phase 2):
- SqliteHistory: HistoryStore over chat_sessions_stack/chat_history/
chat_llm_tools/chat_summaries, no schema change; CallState maps 1:1 on
the existing status strings; wire call ids synthesized as tc_{id}
- SkaldSelector: ModelSelector over LlmManager with the agent's strength
captured per-turn (D14); DtlMode → ToolRendering mapping (D15)
- SkaldActivationSource + SkaldToolActivator: DTL catalog + persistence
(activated_tools, anchored at the triggering message) behind the
crate's protocol traits; unifies the grants/persistence split
- ApprovalGate: port of run_approval_gate (pre-approved, engine, fs
fast-path, auto-deny, AwaitingHuman + block on human); a closed human
channel maps to the new GateDecision::Suspend in agent-loop
- SkaldToolSet + CoreToolBridge/McpToolBridge: core-api and MCP tools
run inside the crate's kernel (execution bridged, execute_cmd keeps
its teardown; D7 MarkInterrupted for shell)
- agent-loop: re-export async_trait at root; EventSink::new made public
17 adapter tests green (temp-DB integration); full workspace suite green
(pre-existing honcho-client doc-test failure untouched: missing dev-deps).
This commit is contained in:
@@ -123,7 +123,10 @@ pub struct EventSink {
|
||||
}
|
||||
|
||||
impl EventSink {
|
||||
pub(crate) fn new(conversation: ConversationId, tx: broadcast::Sender<Event<LoopEvent>>) -> Self {
|
||||
/// Wrap a bus sender for one conversation. Public so hosts can build
|
||||
/// sinks in their own tests and adapters; the kernel builds them via the
|
||||
/// manager.
|
||||
pub fn new(conversation: ConversationId, tx: broadcast::Sender<Event<LoopEvent>>) -> Self {
|
||||
Self { conversation, tx }
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,10 @@ pub struct PendingCall {
|
||||
pub enum GateDecision {
|
||||
Allow,
|
||||
Reject { reason: String },
|
||||
/// The gate was waiting for a human and the channel closed: the turn ends
|
||||
/// and the call STAYS `AwaitingHuman` (the gate marked it before
|
||||
/// suspending) — the same semantics as `ToolFailure::Suspend`.
|
||||
Suspend,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
@@ -348,6 +348,7 @@ async fn run_sequential(
|
||||
continue;
|
||||
}
|
||||
PreExecution::TurnCancelled => return Ok(Some(TurnOutcome::Cancelled)),
|
||||
PreExecution::Suspended => return Ok(Some(TurnOutcome::Cancelled)),
|
||||
};
|
||||
|
||||
let ctx = ToolCtx {
|
||||
@@ -468,6 +469,7 @@ async fn phase2_one<'a>(
|
||||
}
|
||||
Ok(PreExecution::Resolved(outcome)) => Phase2::Done(outcome),
|
||||
Ok(PreExecution::TurnCancelled) => Phase2::Done(CallOutcome::Cancelled),
|
||||
Ok(PreExecution::Suspended) => Phase2::Suspended,
|
||||
Err(e) => Phase2::Done(CallOutcome::Failed(format!("pre-execution error: {e}"))),
|
||||
};
|
||||
(idx, phase)
|
||||
@@ -507,6 +509,9 @@ enum PreExecution {
|
||||
Run(Arc<dyn crate::tool::Tool>),
|
||||
Resolved(CallOutcome),
|
||||
TurnCancelled,
|
||||
/// The gate suspended awaiting a human: the call STAYS `AwaitingHuman`
|
||||
/// (never resolved) and the turn ends.
|
||||
Suspended,
|
||||
}
|
||||
|
||||
/// Gate + hooks.pre + tool lookup — shared by sequential and fan-out paths.
|
||||
@@ -530,8 +535,12 @@ async fn pre_execution(
|
||||
_ = token.cancelled() => return Ok(PreExecution::TurnCancelled),
|
||||
d = deps.gate.check(&pending, events) => d,
|
||||
};
|
||||
if let GateDecision::Reject { reason } = decision {
|
||||
return Ok(PreExecution::Resolved(CallOutcome::Rejected { reason }));
|
||||
match decision {
|
||||
GateDecision::Reject { reason } => {
|
||||
return Ok(PreExecution::Resolved(CallOutcome::Rejected { reason }));
|
||||
}
|
||||
GateDecision::Suspend => return Ok(PreExecution::Suspended),
|
||||
GateDecision::Allow => {}
|
||||
}
|
||||
|
||||
let mut ptc_mut = ptc.clone();
|
||||
|
||||
@@ -27,6 +27,10 @@ pub mod store_memory;
|
||||
pub mod testing;
|
||||
pub mod tool;
|
||||
|
||||
/// Re-exported so implementors of the crate's async traits can write
|
||||
/// `#[agent_loop::async_trait]` without a direct dependency.
|
||||
pub use async_trait::async_trait;
|
||||
|
||||
/// Application name sent as the `X-Title` header by the shipped clients
|
||||
/// (OpenRouter rankings). Clients accept an override.
|
||||
pub const APP_NAME: &str = "Skald";
|
||||
|
||||
Reference in New Issue
Block a user