Skip to content

Commit 002305b

Browse files
authored
chore: update agentkit skills to 0.10.8 (#23)
1 parent c6116d7 commit 002305b

9 files changed

Lines changed: 63 additions & 155 deletions

File tree

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "kit"
3-
version = "0.1.90"
3+
version = "0.1.91"
44
edition = "2024"
55
rust-version = "1.94.0"
66
publish = false
@@ -23,7 +23,7 @@ agentkit-plugins = "=0.10.7"
2323
agentkit-provider-openrouter = "=0.10.7"
2424
agentkit-task-manager = "=0.10.6"
2525
agentkit-tool-compose = { version = "=0.10.9", default-features = false, features = ["runlet"] }
26-
agentkit-tool-skills = "=0.10.7"
26+
agentkit-tool-skills = "=0.10.8"
2727
agentkit-tools-core = "=0.10.5"
2828
async-trait = "=0.1.92"
2929
atomicwrites = "=0.4.4"

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ scope that could safely support unattended approval.
349349

350350
## Agent Plugins
351351

352-
Kit loads validated Agent Plugin packages from local directories and SHA-256-pinned ZIP, tar.gz, or tar URLs. Plugin skills join the existing `activate_skill` catalog, and supported plugin MCP declarations work without an explicit MCP JSON file. Kit runs plugin `stdio` servers and connects `streamable-http` servers; deprecated `sse` entries are skipped with a stderr diagnostic. An explicit `--mcp-config` or `mcp_config` file overlays plugins by server name and remains live-reloadable. See [Agent Plugins](docs/user/agent-plugins.md) for placeholders, data directories, collisions, precedence, and security details.
352+
Kit loads validated Agent Plugin packages from local directories and SHA-256-pinned ZIP, tar.gz, or tar URLs. Plugin skills join the existing `skill` catalog, and supported plugin MCP declarations work without an explicit MCP JSON file. Kit runs plugin `stdio` servers and connects `streamable-http` servers; deprecated `sse` entries are skipped with a stderr diagnostic. An explicit `--mcp-config` or `mcp_config` file overlays plugins by server name and remains live-reloadable. See [Agent Plugins](docs/user/agent-plugins.md) for placeholders, data directories, collisions, precedence, and security details.
353353

354354
## MCP
355355

docs/user/agent-plugins.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Agent Plugins
22

3-
Kit can load Agent Plugin packages from a local directory or a checksum-pinned online archive. Source resolution happens at startup. Kit uses `agentkit-plugins` to validate the resolved package, exposes its valid Agent Skills through the existing `activate_skill` tool, and registers its supported MCP servers. A plugin-only configuration works without `--mcp-config` or `mcp_config`.
3+
Kit can load Agent Plugin packages from a local directory or a checksum-pinned online archive. Source resolution happens at startup. Kit uses `agentkit-plugins` to validate the resolved package, exposes its valid Agent Skills through the existing `skill` tool, and registers its supported MCP servers. A plugin-only configuration works without `--mcp-config` or `mcp_config`.
44

55
## Configure a source
66

docs/user/compose-and-local-tools.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,15 @@ return published
5656

5757
Calls lexically created inside the `after` block start only after `prepared` succeeds. If the prerequisite fails, dependent work does not run. Ordering one call does not make the whole program sequential; unrelated nodes may still overlap. Add explicit data dependencies or `after` edges around every required read-before-write or write-before-write relationship. In particular, do not launch concurrent edits of the same path or let a check race the command that creates its input.
5858

59-
## Activate Agent Skills
59+
## Load Agent Skills
6060

61-
When valid skills exist under `<root>/.agents/skills` or `~/.agents/skills`, the hidden `activate_skill` tool lists their names and descriptions. If a task matches one, return the activation result through `compose` before proceeding so the instructions enter the model conversation:
61+
When valid skills exist under `<root>/.agents/skills` or `~/.agents/skills`, the hidden `skill` tool lists their names and descriptions. If a task matches one, return the loaded skill through `compose` before proceeding so the instructions enter the model conversation:
6262

6363
```text
64-
return activate_skill({ name: "review" })
64+
return skill({ name: "review" })
6565
```
6666

67-
Activation progressively discloses the skill's full `SKILL.md` body, directory, and resource paths. A hidden child result that is discarded by the Runlet is not separately added to the conversation, so do not call `activate_skill` without returning its value. The available-name schema is captured when the compose source is created; start a new session after changing the installed skill set.
67+
Loading progressively discloses the skill's full `SKILL.md` body, directory, and resource paths. A hidden child result that is discarded by the Runlet is not separately added to the conversation, so do not call `skill` without returning its value. Skills can be loaded repeatedly. The available-name schema is captured when the compose source is created; start a new session after changing the installed skill set.
6868

6969
## Run commands with `shell`
7070

docs/user/getting-started-and-configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ description: Review code changes for correctness.
261261
Review the change and run the smallest relevant checks.
262262
```
263263

264-
The `activate_skill` entry in `compose` initially discloses only valid skill names and descriptions. When a task matches, the agent activates the skill before proceeding; activation returns the full Markdown body, skill directory, and paths to supporting resources. Project and user skill files are read with the Kit process's normal host permissions. Invalid or unreadable skills are omitted, and repeated activation of the same skill is deduplicated for a session within the current Kit process.
264+
The `skill` entry in `compose` initially discloses only valid skill names and descriptions. When a task matches, the agent loads the skill before proceeding; the result contains the full Markdown body, skill directory, and paths to supporting resources. Project and user skill files are read with the Kit process's normal host permissions. Invalid or unreadable skills are omitted, and the same skill can be loaded repeatedly.
265265

266266
The hidden-tool catalog is captured when Kit creates the session's compose source. Restart the session after adding or removing a skill so its advertised schema is refreshed.
267267

src/compaction.rs

Lines changed: 2 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,8 @@ use agentkit_loop::{
1212
Agent, AgentEvent, LoopCtx, LoopError, LoopInterrupt, LoopMutator, LoopStep, ModelAdapter,
1313
MutationPoint, SessionConfig, TelemetryConfig, TranscriptCursor,
1414
};
15-
use agentkit_tool_skills::SkillRegistry;
1615
use async_trait::async_trait;
17-
use std::{
18-
collections::{HashMap, HashSet},
19-
sync::Arc,
20-
time::Instant,
21-
};
16+
use std::{collections::HashMap, time::Instant};
2217

2318
use crate::{
2419
events::{self, RuntimeEvent},
@@ -227,7 +222,6 @@ pub fn automatic<M>(
227222
adapter: M,
228223
telemetry: TelemetryConfig,
229224
persistence: Option<SessionObserver>,
230-
skills: Arc<SkillRegistry>,
231225
session_id: impl Into<SessionId>,
232226
) -> Result<AutomaticCompactor, String>
233227
where
@@ -251,11 +245,7 @@ where
251245
.with_strategy(SummarizeForContinuation::default()),
252246
)
253247
.with_backend(backend);
254-
Ok(AutomaticCompactor {
255-
inner,
256-
persistence,
257-
skills,
258-
})
248+
Ok(AutomaticCompactor { inner, persistence })
259249
}
260250

261251
struct KitCompactionBackend<M> {
@@ -480,43 +470,9 @@ fn user_message_from_marker(mut marker: Item, part_index: usize, message: &str)
480470
marker
481471
}
482472

483-
fn removed_skill_instructions(before: &[Item], after: &[Item]) -> bool {
484-
let activation_calls = before
485-
.iter()
486-
.flat_map(|item| &item.parts)
487-
.filter_map(|part| match part {
488-
Part::ToolCall(call) if call.name == "activate_skill" => Some(call.id.to_string()),
489-
_ => None,
490-
})
491-
.collect::<HashSet<_>>();
492-
if activation_calls.is_empty() {
493-
return false;
494-
}
495-
let activation_outputs = |items: &[Item]| {
496-
items
497-
.iter()
498-
.flat_map(|item| &item.parts)
499-
.filter_map(|part| match part {
500-
Part::ToolResult(result)
501-
if activation_calls.contains(&result.call_id.to_string()) =>
502-
{
503-
Some((result.call_id.to_string(), result.output.clone()))
504-
}
505-
_ => None,
506-
})
507-
.collect::<HashMap<_, _>>()
508-
};
509-
let before_outputs = activation_outputs(before);
510-
let after_outputs = activation_outputs(after);
511-
before_outputs
512-
.iter()
513-
.any(|(id, output)| after_outputs.get(id) != Some(output))
514-
}
515-
516473
pub struct AutomaticCompactor {
517474
inner: StrategyCompactor,
518475
persistence: Option<SessionObserver>,
519-
skills: Arc<SkillRegistry>,
520476
}
521477

522478
#[async_trait]
@@ -590,11 +546,6 @@ impl LoopMutator for AutomaticCompactor {
590546
finish(false, false);
591547
return Err(LoopError::Mutator(error));
592548
}
593-
// Reset only when model-facing skill instructions were actually removed,
594-
// and only after durable replacement succeeds.
595-
if removed_skill_instructions(cursor.as_slice(), &compacted) {
596-
self.skills.reset_activations();
597-
}
598549
metadata.insert(
599550
"replaced_items".into(),
600551
(before.saturating_sub(compacted.len()) as u64).into(),
@@ -1036,35 +987,6 @@ mod tests {
1036987
);
1037988
}
1038989

1039-
#[test]
1040-
fn skill_reset_is_needed_only_when_activation_output_is_removed() {
1041-
let call = Item::new(
1042-
ItemKind::Assistant,
1043-
vec![Part::ToolCall(ToolCallPart::new(
1044-
"skill-call",
1045-
"activate_skill",
1046-
json!({"name": "simplify"}),
1047-
))],
1048-
);
1049-
let result = Item::new(
1050-
ItemKind::Tool,
1051-
vec![Part::ToolResult(ToolResultPart::success(
1052-
"skill-call",
1053-
ToolOutput::text("instructions".repeat(TOOL_OUTPUT_MAX_CHARS)),
1054-
))],
1055-
);
1056-
let before = vec![call.clone(), result.clone()];
1057-
1058-
assert!(!removed_skill_instructions(&before, &before));
1059-
assert!(removed_skill_instructions(
1060-
&before,
1061-
std::slice::from_ref(&call)
1062-
));
1063-
1064-
let truncated = vec![call, compact_tool_outputs(result)];
1065-
assert!(removed_skill_instructions(&before, &truncated));
1066-
}
1067-
1068990
#[test]
1069991
fn compact_recent_tool_output_is_bounded_and_drops_stale_usage() {
1070992
let item = Item::new(

src/runtime.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,7 @@ impl Runtime {
627627
.register(Observed::new(AuthTool::new(self.mcp.clone())))
628628
.register(Observed::new(McpTool::new(self.mcp.clone())));
629629
let skill_tools = skills.tool_registry();
630-
if let Some(skill_tool) = skill_tools.get(&ToolName::new("activate_skill")) {
630+
if let Some(skill_tool) = skill_tools.get(&ToolName::new("skill")) {
631631
children.register(observe_shared(skill_tool));
632632
}
633633
let child_specs = children.specs();
@@ -695,7 +695,6 @@ impl Runtime {
695695
self.adapter.clone(),
696696
self.agentkit_telemetry(),
697697
Some(opened.observer.clone()),
698-
Arc::clone(&skills),
699698
format!("compaction-{}", crate::session::new_id()),
700699
)
701700
.map_err(|error| {
@@ -794,7 +793,6 @@ impl Runtime {
794793
self.adapter.clone(),
795794
self.agentkit_telemetry(),
796795
None,
797-
Arc::clone(&skills),
798796
format!("compaction-{session}"),
799797
)
800798
.map_err(LoopError::InvalidState)?;
@@ -907,7 +905,6 @@ impl Runtime {
907905
adapter.clone(),
908906
self.agentkit_telemetry(),
909907
Some(opened.observer.clone()),
910-
Arc::clone(&skills),
911908
format!("compaction-{}", crate::session::new_id()),
912909
)
913910
.map_err(AcpRuntimeError::Loop)?;

src/runtime/tests.rs

Lines changed: 48 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ fn plugin_skills_join_the_catalog_without_broadening_discovery() {
334334
let runtime = Runtime::new(root.path(), "gpt-5.4").unwrap();
335335
let runtime = Runtime::with_plugin_skills(runtime, vec![plugin], vec![plugin_skill]).unwrap();
336336
let skills = runtime.skills.tool_registry();
337-
let tool = ToolSource::get(&skills, &ToolName::new("activate_skill")).unwrap();
337+
let tool = ToolSource::get(&skills, &ToolName::new("skill")).unwrap();
338338
let spec = tool.current_spec().unwrap();
339339
let catalog = spec.input_schema.to_string();
340340
assert!(catalog.contains("project-skill"));
@@ -365,7 +365,7 @@ fn plugin_skill_symlink_retargeting_fails_closed() {
365365
symlink(replacement, skill.join("SKILL.md")).unwrap();
366366

367367
let skills = runtime.skills.tool_registry();
368-
let tool = ToolSource::get(&skills, &ToolName::new("activate_skill")).unwrap();
368+
let tool = ToolSource::get(&skills, &ToolName::new("skill")).unwrap();
369369
let catalog = tool
370370
.current_spec()
371371
.map(|spec| spec.description)
@@ -396,14 +396,14 @@ fn project_skills_take_precedence_over_plugin_skills() {
396396
Runtime::with_plugin_skills(runtime, vec![root.path().to_path_buf()], vec![plugin_skill])
397397
.unwrap();
398398
let skills = runtime.skills.tool_registry();
399-
let tool = ToolSource::get(&skills, &ToolName::new("activate_skill")).unwrap();
399+
let tool = ToolSource::get(&skills, &ToolName::new("skill")).unwrap();
400400
let catalog = tool.current_spec().unwrap().description;
401401
assert!(catalog.contains("Project version."));
402402
assert!(!catalog.contains("Plugin version."));
403403
}
404404

405405
#[tokio::test]
406-
async fn session_skill_registries_reset_independently() {
406+
async fn compose_can_load_a_skill_repeatedly() {
407407
let root = tempfile::tempdir().unwrap();
408408
write_skill(
409409
&root.path().join(".agents/skills/reusable"),
@@ -412,70 +412,59 @@ async fn session_skill_registries_reset_independently() {
412412
"full instructions",
413413
);
414414
let runtime = Runtime::new(root.path(), "gpt-5.4").unwrap();
415-
let registry = runtime.fresh_skills();
416-
let other_registry = runtime.fresh_skills();
417-
let skills = registry.tool_registry();
418-
let other_skills = other_registry.tool_registry();
419-
let tool = ToolSource::get(&skills, &ToolName::new("activate_skill")).unwrap();
420-
let other_tool = ToolSource::get(&other_skills, &ToolName::new("activate_skill")).unwrap();
415+
let compose = runtime.compose(0);
416+
let source: Arc<dyn ToolSource> = Arc::new(compose.compose.clone());
417+
let executor: Arc<dyn ToolExecutor> = Arc::new(BasicToolExecutor::new([source]));
421418
let permissions = Arc::new(AllowAllPermissions);
422419
let resources: Arc<dyn agentkit_tools_core::ToolResources> = Arc::new(());
420+
let session_id = SessionId::new("session");
421+
let turn_id = TurnId::new("turn");
423422
let owned = OwnedToolContext {
424-
session_id: SessionId::new("session"),
425-
turn_id: TurnId::new("turn"),
423+
session_id: session_id.clone(),
424+
turn_id: turn_id.clone(),
426425
metadata: MetadataMap::new(),
427-
permissions,
428-
resources,
426+
permissions: permissions.clone(),
427+
resources: resources.clone(),
429428
cancellation: None,
430-
execution_scope: None,
429+
execution_scope: Some(ToolExecutionScope {
430+
executor,
431+
session_id: session_id.clone(),
432+
turn_id: turn_id.clone(),
433+
permissions,
434+
resources,
435+
cancellation: None,
436+
}),
431437
approved_request: None,
432438
};
433-
let request = |call_id| {
434-
ToolRequest::new(
435-
ToolCallId::new(call_id),
436-
ToolName::new("activate_skill"),
437-
json!({ "name": "reusable" }),
438-
SessionId::new("session"),
439-
TurnId::new("turn"),
439+
let outcome = compose
440+
.backgroundable
441+
.invoke_outcome(
442+
ToolRequest::new(
443+
ToolCallId::new("call"),
444+
ToolName::new("compose"),
445+
json!({
446+
"script": "first = skill({ name: \"reusable\" })\nsecond = skill({ name: \"reusable\" })\nreturn [first, second]"
447+
}),
448+
session_id,
449+
turn_id,
450+
),
451+
&mut owned.borrowed(),
440452
)
441-
};
442-
let mut context = owned.borrowed();
443-
444-
let first = tool.invoke(request("first"), &mut context).await.unwrap();
445-
assert!(
446-
matches!(first.result.output, ToolOutput::Text(ref text) if text.contains("full instructions"))
447-
);
448-
let duplicate = tool
449-
.invoke(request("duplicate"), &mut context)
450-
.await
451-
.unwrap();
452-
let other_first = other_tool
453-
.invoke(request("other-first"), &mut context)
454-
.await
455-
.unwrap();
456-
assert!(
457-
matches!(duplicate.result.output, ToolOutput::Text(ref text) if text == "Skill already read.")
458-
);
459-
assert!(
460-
matches!(other_first.result.output, ToolOutput::Text(ref text) if text.contains("full instructions"))
461-
);
462-
463-
registry.reset_activations();
453+
.await;
464454

465-
let reactivated = tool
466-
.invoke(request("reactivated"), &mut context)
467-
.await
468-
.unwrap();
469-
assert!(
470-
matches!(reactivated.result.output, ToolOutput::Text(ref text) if text.contains("full instructions"))
471-
);
472-
let other_duplicate = other_tool
473-
.invoke(request("other-duplicate"), &mut context)
474-
.await
475-
.unwrap();
476-
assert!(
477-
matches!(other_duplicate.result.output, ToolOutput::Text(ref text) if text == "Skill already read.")
478-
);
455+
let ToolExecutionOutcome::Completed(result) = outcome else {
456+
panic!("skill calls did not complete through compose: {outcome:?}");
457+
};
458+
let ToolOutput::Structured(loaded) = result.result.output else {
459+
panic!("compose did not return structured output");
460+
};
461+
let loaded = loaded.as_array().expect("compose returned an array");
462+
assert_eq!(loaded.len(), 2);
463+
assert!(loaded.iter().all(|skill| {
464+
skill
465+
.as_str()
466+
.is_some_and(|text| text.contains("full instructions"))
467+
}));
479468
}
480469

481470
#[test]

0 commit comments

Comments
 (0)