feat(claw): move the bootstrap tool spec into a TOOL.yaml manifest
bootstrap_save now loads its spec from an embedded manifest, one folder under crates/rhema-claw/tools; the Rust side keeps only the invoke half, behind the run.builtin id. Drift tests follow in the registry's own suite shape once the family grows; for now the registry asserts the manifest loads, and the render tests exercise the manifest-served spec through the roster.
This commit is contained in:
1 parent
bcadeb193c
commit
8c14ea7749
2 files changed
+50
-25
No files matched your search
@@ -22,11 +22,12 @@
|
|||||||
//! section renders only while either file is unwritten, so the offer
|
//! section renders only while either file is unwritten, so the offer
|
||||||
//! self-terminates.
|
//! self-terminates.
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use rhema_agent_tools::render_template;
|
use rhema_agent_tools::render_template;
|
||||||
use rhema_autonomous_agent::{Tool, ToolRegistry, async_trait};
|
use rhema_autonomous_agent::{ToolOp, ToolRegistry, async_trait};
|
||||||
use rhema_core::ToolSpec;
|
use rhema_core::ToolSpec;
|
||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
|
|
||||||
@@ -251,28 +252,7 @@ pub struct BootstrapSave {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Tool for BootstrapSave {
|
impl ToolOp for BootstrapSave {
|
||||||
fn spec(&self) -> ToolSpec {
|
|
||||||
ToolSpec::new(
|
|
||||||
BOOTSTRAP_SAVE,
|
|
||||||
"Write one of your identity files. `kind` is `soul` (who you \
|
|
||||||
are: name, tone, purpose) or `user` (who the user is). \
|
|
||||||
Overwrites the file, so send the whole document every time. \
|
|
||||||
Both files enter every request: keep each one small, \
|
|
||||||
stable, and high-signal.",
|
|
||||||
json!({
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"kind": { "type": "string", "enum": ["soul", "user"],
|
|
||||||
"description": "Which file to write: soul or user." },
|
|
||||||
"content": { "type": "string",
|
|
||||||
"description": "The file's full markdown contents." }
|
|
||||||
},
|
|
||||||
"required": ["kind", "content"]
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn invoke(&self, args: Value) -> Result<String, String> {
|
async fn invoke(&self, args: Value) -> Result<String, String> {
|
||||||
let args: SaveArgs =
|
let args: SaveArgs =
|
||||||
serde_json::from_value(args).map_err(|err| format!("bad arguments: {err}"))?;
|
serde_json::from_value(args).map_err(|err| format!("bad arguments: {err}"))?;
|
||||||
@@ -285,11 +265,35 @@ impl Tool for BootstrapSave {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The compiled operation behind the manifest, by `run.builtin` id.
|
||||||
|
fn builtins(bootstrap: Arc<Bootstrap>) -> BTreeMap<String, Arc<dyn ToolOp>> {
|
||||||
|
BTreeMap::from([(
|
||||||
|
"bootstrap.save".to_owned(),
|
||||||
|
Arc::new(BootstrapSave { bootstrap }) as Arc<dyn ToolOp>,
|
||||||
|
)])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The `TOOL.yaml` of the bootstrap tool, embedded so the daemon ships
|
||||||
|
/// it with no filesystem layout.
|
||||||
|
pub const MANIFESTS: &[(&str, &str)] = &[(
|
||||||
|
"bootstrap_save",
|
||||||
|
include_str!("../tools/bootstrap_save/TOOL.yaml"),
|
||||||
|
)];
|
||||||
|
|
||||||
/// The bootstrap tool set over `store`: `bootstrap_save`, the shape the
|
/// The bootstrap tool set over `store`: `bootstrap_save`, the shape the
|
||||||
/// interactive resolver chain composes.
|
/// interactive resolver chain composes — its spec from the embedded
|
||||||
|
/// manifest, the operation compiled here.
|
||||||
pub fn registry(bootstrap: Arc<Bootstrap>) -> ToolRegistry {
|
pub fn registry(bootstrap: Arc<Bootstrap>) -> ToolRegistry {
|
||||||
|
let (tools, skipped) =
|
||||||
|
rhema_autonomous_agent::manifest::builtin_tools(MANIFESTS, &builtins(bootstrap));
|
||||||
|
assert!(
|
||||||
|
skipped.is_empty(),
|
||||||
|
"the bootstrap tool manifest must load: {skipped:?}"
|
||||||
|
);
|
||||||
let mut registry = ToolRegistry::new();
|
let mut registry = ToolRegistry::new();
|
||||||
registry.register(Arc::new(BootstrapSave { bootstrap }));
|
for tool in tools {
|
||||||
|
registry.register(tool);
|
||||||
|
}
|
||||||
registry
|
registry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# The model-facing spec of the bootstrap_save tool; the operation is the
|
||||||
|
# compiled builtin named under `run`.
|
||||||
|
name: bootstrap_save
|
||||||
|
description: >-
|
||||||
|
Write one of your identity files. `kind` is `soul` (who you are:
|
||||||
|
name, tone, purpose) or `user` (who the user is). Overwrites the
|
||||||
|
file, so send the whole document every time. Both files enter every
|
||||||
|
request: keep each one small, stable, and high-signal.
|
||||||
|
parameters:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
kind:
|
||||||
|
type: string
|
||||||
|
enum: [soul, user]
|
||||||
|
description: "Which file to write: soul or user."
|
||||||
|
content:
|
||||||
|
type: string
|
||||||
|
description: The file's full markdown contents.
|
||||||
|
required: [kind, content]
|
||||||
|
run:
|
||||||
|
builtin: bootstrap.save
|
||||||
Reference in new issue
Block a user