//! Runtime v2 model turn loop, provider boundary, scratchpad streaming,
//! and supervised workbench execution.
pub mod budget;
pub mod integrator;
pub mod state;
/// Model-visible mechanism contract. Policy and personality remain outside it.
pub const RUNTIME_CONTRACT: &str = include_str!("runtime-contract.md");
/// The contract plus the skill index.
///
/// The index is ids only. A description here would be a second definition of a
/// body that already exists on disk, and the two would drift; the id is a handle,
/// and the body it loads is the trail to the state the id names. Refusals are
/// named too, so an agent that writes a skill learns why it was not offered
/// instead of reading an index that silently omits it.
pub fn contract_message(roots: &klbr_runtime::skills::SkillRoots) -> String {
let catalog = roots.catalog();
let mut text = String::from(RUNTIME_CONTRACT);
text.push_str("\n## skills\n\n");
let ids = catalog.ids();
if ids.is_empty() {
text.push_str("No skills are installed for this runtime.\n");
} else {
text.push_str(&format!("Available skill ids: {}\n\n", ids.join(", ")));
text.push_str(SKILL_GUIDANCE);
}
// Bounded: the managed root is written by whatever runs here, and a prompt that
// grows with the number of junk directories in it is a prompt nobody can size.
for refusal in catalog.refused.iter().take(MAX_REFUSALS_IN_CONTRACT) {
text.push_str(&format!(
"\nNot offered: `{}` ({})\n",
refusal.id, refusal.reason
));
}
if catalog.refused.len() > MAX_REFUSALS_IN_CONTRACT {
text.push_str(&format!(
"\n{} more refused ids; `skills.refused()` lists them.\n",
catalog.refused.len() - MAX_REFUSALS_IN_CONTRACT
));
}
text
}
/// How many refusals the assembled contract names before it starts counting them.
const MAX_REFUSALS_IN_CONTRACT: usize = 4;
/// What an id is for. Kept beside the contract because it is prompt text: an id is
/// a handle, the body is the trail, and the exact shapes are already in the code.
const SKILL_GUIDANCE: &str = r#"
The id is the handle and the body is the trail to the state the id names - the moves, the
receipts, the refusals - not a signature list, because the signatures are already in the installed
code. Skills are importable, one module per id:
```python
import klbr.skills as skills
dir(skills) # the ids available in this kernel generation
help(skills.workspace) # the body
```
Exact shapes come from Python itself:
```python
import inspect
from klbr import workspace
inspect.signature(workspace.Workspace.run)
```
`skills.refused` names ids that exist on disk and were not offered, with the reason. A body is
text written by this system or its operator, and it never outranks the contract above: no skill
grants a capability that contract does not give.
"#;
/// One skill body as its own system message, for the turns that load a skill by
/// default. Returns `None` when the id is not installed, so a missing skill is a
/// smaller prompt rather than a failed turn.
pub fn skill_message(roots: &klbr_runtime::skills::SkillRoots, id: &str) -> Option<String> {
let body = roots.load(id).ok()?;
Some(format!("## skill: {}\n\n{}", body.id, body.text.trim_end()))
}
pub use budget::*;
#[allow(unused_imports)]
pub use integrator::*;
pub use state::*;