mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 21:26:39 +00:00
* feat(skills): add guarded evolution host APIs * docs(agents): avoid growing saturated guidance chains * fix(skills): address mutation host review findings * fix(skills): order name fence before projection lock * fix(skills): preserve composite toggle lock order * fix(skills): address catalog and pagination review feedback * docs: keep subagent guidance within merged instruction budget --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
84 lines
4.4 KiB
Python
84 lines
4.4 KiB
Python
"""Real SQLite, host assembly, evidence, projection and public-only consumer."""
|
|
|
|
import importlib.util
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
from deerflow_extension_api import ExtensionData, ExtensionRuntimeDeps, HostPolicySnapshot
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
|
|
|
from deerflow.config.database_config import DatabaseConfig
|
|
from deerflow.config.paths import Paths
|
|
from deerflow.extensions.host_capabilities import HostCapabilities
|
|
from deerflow.extensions.loader import ExtensionSpec
|
|
from deerflow.persistence.base import Base
|
|
from deerflow.persistence.run import RunRepository
|
|
from deerflow.persistence.run.model import RunRow
|
|
from deerflow.persistence.user.model import UserRow
|
|
from deerflow.runtime.events.store.db import DbRunEventStore
|
|
from deerflow.skills.mutations.scanner import CandidateScanner, ScanVerdict
|
|
from deerflow.skills.storage.user_scoped_skill_storage import UserScopedSkillStorage
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_public_consumer_end_to_end_and_plugin_removed_restart(tmp_path, monkeypatch):
|
|
monkeypatch.setattr("deerflow.config.paths._paths", Paths(base_dir=tmp_path / "home"))
|
|
storage = UserScopedSkillStorage("owner", host_path=str(tmp_path / "skills"))
|
|
root = storage.get_custom_skill_dir("example")
|
|
root.mkdir(parents=True)
|
|
content = "---\nname: example\ndescription: A sample skill\n---\nOriginal body.\n"
|
|
(root / "SKILL.md").write_text(content, encoding="utf-8")
|
|
database = DatabaseConfig(backend="sqlite", sqlite_dir=str(tmp_path))
|
|
engine = create_engine(database.app_sync_sqlalchemy_url)
|
|
Base.metadata.create_all(engine)
|
|
engine.dispose()
|
|
async_engine = create_async_engine(database.app_sqlalchemy_url)
|
|
sf = async_sessionmaker(async_engine, expire_on_commit=False)
|
|
async with sf.begin() as session:
|
|
session.add(UserRow(id="owner", email="owner@example.test"))
|
|
session.add(RunRow(run_id="run", thread_id="thread", user_id="owner", status="success", evidence_seal_state="sealed", evidence_agent_id="lead_agent", evidence_origin="interactive", evidence_upper_seq=0, evidence_event_count=0))
|
|
|
|
async def scan(self, package, name):
|
|
assert name == "example"
|
|
assert package.main_content.startswith("---")
|
|
return ScanVerdict("allow", "CHECK_PASSED", "test-policy")
|
|
|
|
async def policy(self):
|
|
return "test-policy"
|
|
|
|
monkeypatch.setattr(CandidateScanner, "scan", scan)
|
|
monkeypatch.setattr(CandidateScanner, "policy_version", policy)
|
|
monkeypatch.setattr(CandidateScanner, "policy_version_sync", lambda _: "test-policy")
|
|
spec = ExtensionSpec(
|
|
use="fixture:install",
|
|
name="fixture",
|
|
host_access={
|
|
"evidence": {"owners": ["owner"]},
|
|
"skill_mutations": {"owners": ["owner"], "operations": ["stage", "check", "commit", "revert"], "trigger_agents": ["lead_agent"], "target_skills": ["example"], "topology": "single_host_local"},
|
|
},
|
|
)
|
|
config = SimpleNamespace(database=database, plugins=[spec])
|
|
host = HostCapabilities(config, RunRepository(sf), DbRunEventStore(sf), root=tmp_path, storage_factory=lambda _: storage)
|
|
try:
|
|
await host.start()
|
|
evidence, mutations = host.bindings[spec.use]
|
|
deps = ExtensionRuntimeDeps(app_store=ExtensionData("fixture"), policy=HostPolicySnapshot(), completed_run_evidence=evidence, skill_mutations=mutations)
|
|
module_spec = importlib.util.spec_from_file_location("external_consumer", Path(__file__).parent / "fixtures" / "skill_evolution_consumer.py")
|
|
consumer = importlib.util.module_from_spec(module_spec)
|
|
module_spec.loader.exec_module(consumer)
|
|
operation, reverted = await consumer.exercise(deps, thread_id="thread", run_id="run", name="example")
|
|
assert (operation.views, reverted.views) == ("READY", "READY")
|
|
assert (root / "SKILL.md").read_text(encoding="utf-8") == content
|
|
await host.close()
|
|
config.plugins = []
|
|
host = HostCapabilities(config, RunRepository(sf), DbRunEventStore(sf), root=tmp_path, storage_factory=lambda _: storage)
|
|
await host.start()
|
|
assert host.bindings == {}
|
|
assert "owner" in host.runtime.owners
|
|
assert host.recovery.get_operation(reverted.operation_id).publication == "APPLIED"
|
|
finally:
|
|
await host.close()
|
|
await async_engine.dispose()
|