¶ciel.runtime — Skill Library (Autonomía I)
Subsistema de Skill Library (Fase 12). Store dinámico escribible, verificación
offline, versioning + changelog, composition, doc auto-generation, integración
con ciel.Agent y métricas por tenant. Todo offline-safe.
¶
ciel.runtime.skills_lib
¶
Skill
dataclass
¶
SkillError
¶
SkillLibrary
Writable, in-memory skill store that wraps a :class:SkillRegistry.
The registry remains the source of truth for disk-loaded skills; the
library layer adds creation, registration, update (with version bump) and
removal of skills that live only in memory. Tenant isolation is supported
via the optional tenant_id key on each stored :class:Skill.
Source code in src/ciel/runtime/skills_lib.py
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | |
¶
create_from_code(*, name: str, description: str, code: str, category: Optional[str] = None, tenant_id: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None) -> Skill
Compile code (syntax check) and store it as a new skill.
Raises :class:SkillError if the code does not compile. The skill is
NOT executed here — execution belongs to :class:SkillVerifier.
Source code in src/ciel/runtime/skills_lib.py
¶
history(name: str) -> List[Skill]
¶
load_from_disk() -> List[Skill]
Discover skills from the registry roots and index them in-memory.
¶
register(skill: Skill) -> Skill
Register an already-built :class:Skill (e.g. disk-loaded).
Source code in src/ciel/runtime/skills_lib.py
¶
update(*, name: str, description: Optional[str] = None, code: Optional[str] = None, category: Optional[str] = None, bump: str = 'patch') -> Skill
Create a new version of an existing skill, preserving history.
bump is one of major/minor/patch (semantic) and is
recorded in metadata.version. The previous version is preserved in
history(name).
Source code in src/ciel/runtime/skills_lib.py
¶
SkillRegistry
Source code in src/ciel/runtime/skills.py
¶
SkillVerificationError
Bases: SkillError
Raised when a skill fails verification.
¶
SkillVerificationResult
dataclass
Outcome of :meth:SkillVerifier.verify.
Source code in src/ciel/runtime/skills_lib.py
¶
SkillVerifier
Offline verifier: syntax check + executable test cases.
A test case is a dict {"call": {...}, "expect": <value>}. The verifier
executes the skill code in an isolated namespace, looks up a callable named
after the skill (or the first callable defined), invokes it with call
arguments and compares the result to expect.
Source code in src/ciel/runtime/skills_lib.py
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 | |
¶
SkillVersion
dataclass
A single immutable version entry stored in the library history.
Source code in src/ciel/runtime/skills_lib.py
¶
INITIAL_VERSION = '0.0.0'
module-attribute
¶
Skill
dataclass
¶
SkillError
¶
SkillLibrary
Writable, in-memory skill store that wraps a :class:SkillRegistry.
The registry remains the source of truth for disk-loaded skills; the
library layer adds creation, registration, update (with version bump) and
removal of skills that live only in memory. Tenant isolation is supported
via the optional tenant_id key on each stored :class:Skill.
Source code in src/ciel/runtime/skills_lib.py
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | |
¶
create_from_code(*, name: str, description: str, code: str, category: Optional[str] = None, tenant_id: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None) -> Skill
Compile code (syntax check) and store it as a new skill.
Raises :class:SkillError if the code does not compile. The skill is
NOT executed here — execution belongs to :class:SkillVerifier.
Source code in src/ciel/runtime/skills_lib.py
¶
history(name: str) -> List[Skill]
¶
load_from_disk() -> List[Skill]
Discover skills from the registry roots and index them in-memory.
¶
register(skill: Skill) -> Skill
Register an already-built :class:Skill (e.g. disk-loaded).
Source code in src/ciel/runtime/skills_lib.py
¶
update(*, name: str, description: Optional[str] = None, code: Optional[str] = None, category: Optional[str] = None, bump: str = 'patch') -> Skill
Create a new version of an existing skill, preserving history.
bump is one of major/minor/patch (semantic) and is
recorded in metadata.version. The previous version is preserved in
history(name).
Source code in src/ciel/runtime/skills_lib.py
¶
SkillVersion
dataclass
Enriched semantic version for a single skill release.
Carries major.minor.patch plus the human changelog text and the release
timestamp (released_at) — the extra fields that turn a bare "0.1.0"
string into a first-class, reviewable release record.
Source code in src/ciel/runtime/skill_versioning.py
¶
version: str
property
"major.minor.patch" string.
¶
bump(kind: str = 'patch') -> 'SkillVersion'
Return a new :class:SkillVersion bumped by major/minor/patch.
Source code in src/ciel/runtime/skill_versioning.py
¶
from_skill(skill: Skill) -> 'SkillVersion'
classmethod
Build a :class:SkillVersion from a stored :class:Skill.
Source code in src/ciel/runtime/skill_versioning.py
¶
parse(version: str) -> 'SkillVersion'
classmethod
Build a :class:SkillVersion from a "major.minor.patch" string.
Source code in src/ciel/runtime/skill_versioning.py
¶
_now_iso() -> str
¶
_version_key(skill: Skill) -> str
¶
changelog(lib: SkillLibrary, name: str) -> Dict[str, str]
Return {version: changelog_text} for every version of name.
Versions are ordered oldest-first (matching lib.history). The initial
version is keyed "0.0.0" when the library has not assigned an explicit
semantic version yet.
Source code in src/ciel/runtime/skill_versioning.py
¶
evolution_tree(lib: SkillLibrary, name: str) -> Dict[str, Any]
Return the lineage / evolution tree of skill name.
The result is the seed of Ciel's unique Skill Evolution Tree: it records,
for every version, its parent (previous_version) so the autonomous agent
can see how a skill evolved / branched.
Structure::
{
"name": <skill name>,
"root": <version key of the base of the lineage>,
"lineage": [root, ..., latest], # ordered oldest -> newest
"nodes": {
<version>: {
"version": <version>,
"parent": <parent version or None>,
"previous_version": <raw library value>,
"children": [<child version>, ...],
"sha256": <hash>,
"changelog": <text>,
"released_at": <iso string or None>,
},
...
},
}
The parent link is normalised so the very first update() (whose
previous_version is None because the initial skill had no explicit
version) is still chained to its true parent in history order.
Source code in src/ciel/runtime/skill_versioning.py
¶
set_changelog(lib: SkillLibrary, name: str, version: str, text: str) -> SkillVersion
Attach a changelog text to a specific version of skill name.
The changelog (and, if absent, the released_at timestamp) is written into
the matching :class:Skill's metadata inside the passed library. The
function returns the enriched :class:SkillVersion for that release.
Raises :class:SkillError if the skill or the requested version is unknown,
or if text is empty.
Source code in src/ciel/runtime/skill_versioning.py
¶
Skill
dataclass
¶
SkillComposition
dataclass
Builds a single composed :class:Skill from N source skills.
Source code in src/ciel/runtime/skill_composition.py
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | |
¶
compose(name: str, skills: Sequence[Skill], combinator: str, *, library: Optional[SkillLibrary] = None, description: Optional[str] = None, category: Optional[str] = None) -> Skill
Fuse skills into a new :class:Skill named name.
¶Parameters
name:
Name of the produced (composed) skill.
skills:
Source skills to combine (must be non-empty).
combinator:
One of "sequence", "parallel" or "selector".
library:
Optional :class:SkillLibrary to register the composed skill into.
If omitted, self.library (if set at construction) is used.
description / category:
Override the auto-generated description / category.
¶Returns
Skill
The newly built composed skill (also registered into library
when one is supplied).
Source code in src/ciel/runtime/skill_composition.py
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | |
¶
SkillCompositionError
¶
SkillLibrary
Writable, in-memory skill store that wraps a :class:SkillRegistry.
The registry remains the source of truth for disk-loaded skills; the
library layer adds creation, registration, update (with version bump) and
removal of skills that live only in memory. Tenant isolation is supported
via the optional tenant_id key on each stored :class:Skill.
Source code in src/ciel/runtime/skills_lib.py
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | |
¶
create_from_code(*, name: str, description: str, code: str, category: Optional[str] = None, tenant_id: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None) -> Skill
Compile code (syntax check) and store it as a new skill.
Raises :class:SkillError if the code does not compile. The skill is
NOT executed here — execution belongs to :class:SkillVerifier.
Source code in src/ciel/runtime/skills_lib.py
¶
history(name: str) -> List[Skill]
¶
load_from_disk() -> List[Skill]
Discover skills from the registry roots and index them in-memory.
¶
register(skill: Skill) -> Skill
Register an already-built :class:Skill (e.g. disk-loaded).
Source code in src/ciel/runtime/skills_lib.py
¶
update(*, name: str, description: Optional[str] = None, code: Optional[str] = None, category: Optional[str] = None, bump: str = 'patch') -> Skill
Create a new version of an existing skill, preserving history.
bump is one of major/minor/patch (semantic) and is
recorded in metadata.version. The previous version is preserved in
history(name).
Source code in src/ciel/runtime/skills_lib.py
¶
_composed_body(name: str, callables: Sequence[str], combinator: str) -> str
Generate the source of the composed function for the given combinator.
Source code in src/ciel/runtime/skill_composition.py
¶
_main_callable(content: str, fallback_name: str) -> Optional[str]
Return the name of the primary callable defined in content.
Preference order: a top-level function matching fallback_name (usually
the skill's own name), otherwise the first top-level function definition.
Returns None if the code defines no functions.
Source code in src/ciel/runtime/skill_composition.py
¶
Skill
dataclass
¶
SkillDocError
¶
_parse_first_callable(content: str) -> Tuple[str, Optional[str], List[str]]
Parse content and return (name, docstring, args) of the first callable.
A "callable" is the first :class:ast.FunctionDef or
:class:ast.AsyncFunctionDef in the module body. The args list contains
the positional/keyword argument names (with * / ** markers for the
variadic ones). Raises :class:SkillDocError for empty/bad/non-callable code.
Source code in src/ciel/runtime/skill_doc.py
¶
_yaml_escape_scalar(value: str) -> str
Escape a string so it is safe as a single-line YAML double-quoted scalar.
¶
generate_doc(skill: Skill) -> Dict[str, Any]
Build {"name", "description", "category"} from a skill's compiled code.
name and description are taken from the first callable defined in
skill.content (its function name and docstring); description falls
back to an empty string when the callable has no docstring. category is
preserved verbatim from skill.category (which may be None).
Source code in src/ciel/runtime/skill_doc.py
¶
to_markdown(skill: Skill) -> str
Render a skill as a markdown string with YAML frontmatter + body.
The frontmatter carries name, description and (when present)
category. The body repeats the title, the full docstring and the
callable's argument signature, so the generated doc is useful on its own.
Source code in src/ciel/runtime/skill_doc.py
Fase 12 Item 5 — Integración con ciel.Agent.
Este módulo conecta la :class:~ciel.runtime.skills_lib.SkillLibrary (Item 1
de Fase 12) con la fachada de alto nivel ciel.api (Fases 10/11) de forma
100% offline y sin romper la API existente del Agent.
Piezas que aporta:
@ciel.skill— decorador que registra una función comoSkillen una :class:SkillLibraryglobal (singleton), validando su sintaxis en tiempo de definición (compiledel source).global_skill_library— la instancia singleton compartida por todo el proceso (mismo patrón que el registro de herramientas).teach(agent, skill, ...)— helper que registra un skill verificado en unAgentya construido, convirtiéndolo en unToolFunctionejecutable a través delToolRegistryexistente.install_agent_skill_support(Agent)— enganchaAgent(skills=[...])yAgent.teach(...)alAgentpúblico sin reescribirapi.py: se invoca al final deciel/api.py(ver regla 2 del ítem).
No se cambia la firma pública de Agent existente: el soporte de skills se
inyecta envuelto en __init__ y como método teach.
¶
__all__ = ['global_skill_library', 'skill', 'teach', 'load_agent_skills', 'install_agent_skill_support']
module-attribute
¶
global_skill_library: SkillLibrary = SkillLibrary()
module-attribute
¶
Skill
dataclass
¶
SkillError
¶
SkillLibrary
Writable, in-memory skill store that wraps a :class:SkillRegistry.
The registry remains the source of truth for disk-loaded skills; the
library layer adds creation, registration, update (with version bump) and
removal of skills that live only in memory. Tenant isolation is supported
via the optional tenant_id key on each stored :class:Skill.
Source code in src/ciel/runtime/skills_lib.py
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | |
¶
create_from_code(*, name: str, description: str, code: str, category: Optional[str] = None, tenant_id: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None) -> Skill
Compile code (syntax check) and store it as a new skill.
Raises :class:SkillError if the code does not compile. The skill is
NOT executed here — execution belongs to :class:SkillVerifier.
Source code in src/ciel/runtime/skills_lib.py
¶
history(name: str) -> List[Skill]
¶
load_from_disk() -> List[Skill]
Discover skills from the registry roots and index them in-memory.
¶
register(skill: Skill) -> Skill
Register an already-built :class:Skill (e.g. disk-loaded).
Source code in src/ciel/runtime/skills_lib.py
¶
update(*, name: str, description: Optional[str] = None, code: Optional[str] = None, category: Optional[str] = None, bump: str = 'patch') -> Skill
Create a new version of an existing skill, preserving history.
bump is one of major/minor/patch (semantic) and is
recorded in metadata.version. The previous version is preserved in
history(name).
Source code in src/ciel/runtime/skills_lib.py
¶
SkillVerificationError
Bases: SkillError
Raised when a skill fails verification.
¶
SkillVerifier
Offline verifier: syntax check + executable test cases.
A test case is a dict {"call": {...}, "expect": <value>}. The verifier
executes the skill code in an isolated namespace, looks up a callable named
after the skill (or the first callable defined), invokes it with call
arguments and compares the result to expect.
Source code in src/ciel/runtime/skills_lib.py
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 | |
¶
Tool
dataclass
¶
_function_source(func: Any) -> str
Devuelve el source de func para validación de sintaxis.
Se elimina cualquier línea de decorador @skill / @ciel.skill para
que el contenido sea ejecutable de forma aislada (p.ej. por el
:class:SkillVerifier) sin depender del decorador en runtime.
Source code in src/ciel/runtime/skill_agent_integration.py
¶
_register_tool_function(agent: Any, tool_function: Any) -> None
Registra un ToolFunction en el registry del agente en runtime.
Source code in src/ciel/runtime/skill_agent_integration.py
¶
_skill_to_tool_function(skill_obj: Skill) -> Any
Construye un ToolFunction ejecutable a partir de un Skill.
Prefiere la función original guardada en metadata["_callable"]; si no
existe (skill creado solo desde código fuente) lo reconstruye ejecutando el
contenido en un namespace aislado. Reusa ciel.api.tool para inferir el
esquema JSON a partir de las type hints.
Source code in src/ciel/runtime/skill_agent_integration.py
¶
install_agent_skill_support(agent_cls: Any) -> Any
Inyecta skills=[...] en __init__ y teach como método.
No reescribe nada de api.py: envuelve __init__ para consumir el
kwarg skills (y cargarlos como tools) y adjunta teach / load_skills
como métodos de instancia.
Source code in src/ciel/runtime/skill_agent_integration.py
¶
load_agent_skills(agent: Any, skill_names: Sequence[str]) -> List[Any]
Carga una lista de nombres de skills (desde la librería global) en el agente.
Source code in src/ciel/runtime/skill_agent_integration.py
¶
skill(fn: Optional[Any] = None, *, name: Optional[str] = None, description: Optional[str] = None, category: Optional[str] = None) -> Any
Decorador que registra fn como un Skill en la librería global.
Valida la sintaxis de la función (compile de su source) en tiempo de
definición y guarda una referencia a la función original en
metadata["_callable"] para poder exponerla luego como ToolFunction
sin tener que re-ejecutar el código fuente.
Uso::
import ciel
@ciel.skill
def add(a: int, b: int) -> int:
"Suma dos enteros."
return a + b
# Disponible de inmediato en la librería singleton:
ciel.runtime.skill_agent_integration.global_skill_library.get("add")
Source code in src/ciel/runtime/skill_agent_integration.py
¶
teach(agent: Any, skill_obj: Skill, *, test_cases: Optional[Sequence[Dict[str, Any]]] = None, verify: bool = True) -> Any
Registra skill_obj como herramienta ejecutable en agent.
Si test_cases se provee y verify=True, el skill pasa primero por el
:class:SkillVerifier offline; se lanza :class:SkillVerificationError si
no pasa. Devuelve el ToolFunction registrado.
Source code in src/ciel/runtime/skill_agent_integration.py
¶
SkillMetrics
In-memory performance metrics for skills, optionally isolated by tenant.
Usage::
metrics = SkillMetrics()
metrics.record_usage("lib", "add", success=True, latency_ms=12.5)
print(metrics.metrics("add"))
# {'calls': 1, 'successes': 1, 'failures': 0,
# 'success_rate': 1.0, 'avg_latency_ms': 12.5}
The lib argument records which skill library/source emitted the call
(kept for traceability) while name is the key the metrics are stored
under. When tenant_id is supplied, metrics are kept in a separate
namespace so two tenants with the same skill name never share counters.
Source code in src/ciel/runtime/skill_metrics.py
¶
metrics(name: str, tenant_id: Optional[str] = None) -> Dict[str, float]
Return aggregated metrics for name as a dict.
Returns {calls, successes, failures, success_rate, avg_latency_ms}.
Unknown skills return all-zero counters (success_rate 0.0) rather than
raising, so callers can read metrics before any usage has been recorded.
Source code in src/ciel/runtime/skill_metrics.py
¶
record_usage(lib: str, name: str, success: bool, latency_ms: float = 0.0, tenant_id: Optional[str] = None) -> None
Record one skill invocation.
lib identifies the originating library/source (stored for
traceability but does not affect the metric key). name is the
skill name the metrics are aggregated under. success marks the
outcome and latency_ms is the observed latency. tenant_id
optionally isolates the record into a tenant-specific namespace.
Source code in src/ciel/runtime/skill_metrics.py
¶
reset(*, tenant_id: Optional[str] = None) -> None
Clear recorded metrics.
With tenant_id only that tenant's namespace is cleared; with no
tenant_id the global (non-tenant) namespace is cleared.
Source code in src/ciel/runtime/skill_metrics.py
¶
_SkillStat
dataclass
Accumulated metrics for a single skill (within one tenant scope).