Author runnable skill bodies via OpenAI-compatible codegen model

create_skill now writes a real predict/act body: the small decision model
still authors title + description (engine.generate), then a larger
OpenAI-compatible model (default qwen38-iq3s) writes the runnable code
against the SKILL.md contract. Bodies persist to data/skills/<cat>/<name>.py,
are hot-loaded via importlib, merged into the running tree, and the request
re-dispatches to the new leaf. The dashboard decision-flow view shows the
title/description with a writing badge while the body is being written.
Codegen failure degrades to a navigable stub.
This commit is contained in:
Denton Social
2026-09-24 00:36:35 -05:00
parent 789ed4ae25
commit 440e49e76e
14 changed files with 910 additions and 18 deletions
+43 -1
View File
@@ -12,10 +12,20 @@ from pathlib import Path
import pytest
from semif_agent.cli import build_scheduler, load_config
from semif_agent.codegen import CodegenClient, generate_skill_body
from semif_agent.decisions import Request
from semif_agent.dream import dream
from semif_agent.engine import EngineUnavailable
from semif_agent.skills import CategoryDraft, SkillDraft, generate_category, generate_skill
from semif_agent.skills import (
CategoryDraft,
SkillBodyStore,
SkillDraft,
build_skills,
build_tree,
generate_category,
generate_skill,
materialize_skill,
)
def require_real(config: dict):
@@ -140,6 +150,38 @@ def test_generate_skill(tmp_path):
assert draft.name and draft.description
def test_generate_skill_body_codegen(tmp_path):
"""A real OpenAI-compatible model writes a runnable skill body.
Slow: uses the big codegen model (qwen38-iq3s by default). Run this one in
the background and poll — long-lived ssh sessions get SIGHUP'd.
"""
config = load_config()
require_real(config)
codegen_cfg = config.get("codegen", {})
client = CodegenClient(
base_url=codegen_cfg.get("base_url", "http://localhost:11434/v1"),
model=codegen_cfg.get("model", "qwen38-iq3s"),
timeout=float(codegen_cfg.get("timeout", 600.0)),
)
tree = build_tree(build_skills({"skills": {}}))
draft = SkillDraft(
name="check_service",
description="Check whether a service is reachable.",
)
code = generate_skill_body(
client,
Request("is my home server reachable right now?"),
"tracking",
draft,
tree,
)
print(f"generated {len(code)} bytes of skill body")
store = SkillBodyStore(str(tmp_path / "skills"))
skill = materialize_skill(draft, "tracking", store)
assert callable(skill.predict) and callable(skill.act)
def test_create_skill_empty_category_does_not_wedge(tmp_path):
"""A dispatch that lands on an empty category must not leave the scheduler wedged.