use std::sync::Arc;
use agentplane::manifest::{Manifest, ManifestError, MemoryRegistry, Registry, RegistryError};
use agentplane::model::fake::FakeProvider;
use agentplane::model::{ModelId, ModelProvider};
use agentplane::prelude::*;
use agentplane::runtime::Agent;
use serde_json::{Value, json};
const AGENT: &str = include_str!("triage.yaml");
#[derive(Debug)]
struct Triage;
#[async_trait::async_trait]
impl Skill for Triage {
fn descriptor(&self) -> SkillDescriptor {
SkillDescriptor::new("triage").provides("support.triage")
}
async fn invoke(
&self,
cx: &mut StepCtx<'_>,
input: Tainted<Value>,
) -> Result<Outcome, SkillError> {
let (system, schema) = {
let manifest = cx.manifest().expect("this agent runs under a manifest");
let system = manifest
.spec
.identity
.as_ref()
.map(agentplane::manifest::Identity::system_prompt)
.unwrap_or_default();
(system, manifest.output_schema().cloned())
};
let expecting = |schema: &Option<Value>| {
let schema = schema.clone();
move |mut call: agentplane::model::ModelCall| {
if let Some(schema) = schema {
call = call.expecting(schema);
}
call
}
};
let first_prompt = Tainted::object([
("system", Tainted::trusted(json!(system.clone()))),
("ticket", input),
]);
let first = cx.complete_with(&first_prompt, expecting(&schema)).await?;
let draft = first.map(|completion| {
completion
.structured
.unwrap_or_else(|| json!({ "text": completion.text }))
});
let checked_prompt = Tainted::object([
("system", Tainted::trusted(json!(system))),
("verify", draft),
]);
let checked = cx
.complete_with(&checked_prompt, expecting(&schema))
.await?;
Ok(Outcome::done(checked.map(|c| {
c.structured.unwrap_or_else(|| json!({ "text": c.text }))
})))
}
}
fn runtime_for(
store: &Arc<dyn JournalStore>,
manifest: &Arc<Manifest>,
provider: &Arc<FakeProvider>,
) -> Arc<Runtime> {
let driver: Arc<dyn ModelProvider> = provider.clone();
Runtime::builder(Arc::clone(store))
.provider("fake", driver)
.agent(Agent::new(manifest).skill(Triage))
.build()
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let store: Arc<dyn JournalStore> = Arc::new(RedbStore::open_in_memory()?);
let manifest = Arc::new(Manifest::parse(AGENT)?);
println!(
"agent: {} {}",
manifest.metadata.name, manifest.metadata.version
);
println!("governed by: {}", manifest.digest()?.to_hex());
let provider = FakeProvider::new();
let rt = runtime_for(&store, &manifest, &provider);
let ticket = json!({ "id": "T-4711", "body": "checkout returns 500 for EU cards" });
let live = rt
.run("support.triage", Tainted::trusted(ticket.clone()))
.await?;
println!("\n1. live run → {:?}", live.status);
assert_eq!(
live.status,
RunStatus::Succeeded,
"the nominal manifest run failed — this example is the executable proof that a \
declared prompt can handle untrusted input without making its system instruction \
untrusted"
);
let asked = provider.asked();
let ask = asked.first().expect("the provider was asked");
for (index, line) in ask.prompt["system"]
.as_str()
.unwrap_or("<none>")
.lines()
.filter(|line| !line.trim().is_empty())
.enumerate()
{
let label = if index == 0 { "system prompt:" } else { "" };
println!(" {label:<15} {line}");
}
println!(" schema sent: {}", ask.schema.is_some());
assert_eq!(
ask.prompt["system"].as_str(),
Some("Support ticket triage\n\nClassify severity. Never promise a refund."),
"the provider was asked with something other than the prompt in the file, \
which would make the manifest's digest a record of the wrong thing"
);
assert_eq!(
ask.schema.as_ref(),
manifest.output_schema(),
"the declared result contract did not reach the provider, where the \
constraint is enforced during generation rather than rejected afterwards"
);
assert_eq!(ask.model, ModelId::new("fake", "triage-1"), "wrong model");
let tight = Arc::new(Manifest::parse(
&AGENT.replace("max_tokens: 120000", "max_tokens: 1"),
)?);
assert_ne!(
manifest.digest()?,
tight.digest()?,
"changing a ceiling must change the declaration's identity"
);
let provider = FakeProvider::new();
let rt = runtime_for(&store, &tight, &provider);
let capped = rt.run("support.triage", Tainted::trusted(ticket)).await?;
println!("\n2. one-token ceiling → {:?}", capped.status);
println!(" spend: {} tokens", capped.spend().tokens);
println!(
" provider calls: {} of 2 — the second never started",
provider.calls()
);
assert!(
!matches!(capped.status, RunStatus::Succeeded),
"a ceiling declared in the manifest did not bind the run, so the file \
documents a limit the runtime does not apply"
);
assert_eq!(
provider.calls(),
1,
"the first call is always allowed — its cost is unknown until it runs — \
but the second must not start once consumption has reached the limit"
);
let typo = AGENT.replace("max_tokens:", "max_tokns:");
match Manifest::parse(&typo) {
Err(ManifestError::Syntax(detail)) => {
println!(
"\n3. `max_tokns:` refused → {}",
detail.lines().next().unwrap_or(&detail)
);
}
Err(e) => panic!("wrong refusal: {e}"),
Ok(m) => panic!(
"a typo was ignored and the token ceiling silently became {:?} — this \
is the failure mode the format exists to prevent",
m.budget().max_tokens
),
}
registry_refusals(&manifest).await?;
for run in [live.run_id, capped.run_id] {
store.verify(run).await?;
}
println!("\n5. both journals verify");
Ok(())
}
async fn registry_refusals(manifest: &Manifest) -> Result<(), Box<dyn std::error::Error>> {
let registry = MemoryRegistry::new();
let published = registry.publish(manifest).await?;
println!("\n4. published → {}", published.to_hex());
assert_eq!(
registry.publish(manifest).await?,
published,
"republishing identical content must succeed"
);
let widened = Manifest::parse(
&AGENT.replace(" tools:\n", " tools:\n - ref: \"tool://shell/exec\"\n"),
)?;
match registry.publish(&widened).await {
Err(RegistryError::Immutable { .. }) => {
println!(" rewriting 2.0.0 with a wider grant → refused");
}
Err(e) => panic!("wrong refusal: {e}"),
Ok(_) => panic!("a published version was replaced with a wider grant"),
}
let hostile = MemoryRegistry::new();
hostile.publish(&widened).await?;
match hostile
.resolve_pinned("support-triage", "2.0.0", published)
.await
{
Err(RegistryError::PinBroken { .. }) => {
println!(" pinned resolve against substituted content → refused");
}
Err(e) => panic!("wrong refusal: {e}"),
Ok(_) => panic!("a pinned resolve accepted content the caller never reviewed"),
}
Ok(())
}