Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions .autoskillit/skills/eval-agent/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ semantic_requirements:
logical_roles:
- name: evaluated-agent
purpose: run the named agent definition under evaluation against the prepared prompt and return its full output
runtime_bound: true
child_spawns:
- role: evaluated-agent
count: 1
Expand All @@ -34,8 +35,8 @@ or evaluation. Runs one agent against one prompt and captures output.

The skill receives its arguments as a single string. Parse these arguments:

- `--agent-name {name}` — The agent definition name (without `autoskillit:` prefix).
The skill prepends `autoskillit:` when calling the Agent tool.
- `--agent-name {name}` — The bundled agent definition name.
The backend-adapted contract determines the native agent type.
- `--prompt-file {path}` — Absolute path to a file containing the prompt text.

Example invocation:
Expand All @@ -57,7 +58,7 @@ Example invocation:
**ALWAYS:**
- Parse `--agent-name` and `--prompt-file` from the ARGUMENTS string
- Read the prompt file using the Read tool
- Invoke the agent via child delegation bound to the `evaluated-agent` logical role, targeting the named agent definition `autoskillit:{agent_name}` with the prompt file contents
- Invoke the bundled agent definition named by `--agent-name` via child delegation bound to the `evaluated-agent` logical role, using the backend-adapted contract for its native agent type and the prompt file contents
- Write output JSON to `{{AUTOSKILLIT_TEMP}}/eval-agent/{agent_name}_output.json` (relative to the current working directory) using the Write tool
- Emit `agent_output_path = <absolute_path>` as a structured output token (plain text, no markdown formatting on the token name)
- The absolute path in the structured output token must be constructed by prepending the current working directory to the relative output path
Expand All @@ -80,8 +81,9 @@ If the file cannot be read, proceed to Step 4 (error handling).
### Step 3: Invoke Agent and Capture Output

Invoke the agent as a child delegation bound to the `evaluated-agent` logical role:
delegate to the named agent definition `autoskillit:{agent_name}`, passing the prompt
file contents verbatim as the delegation prompt.
delegate to the bundled agent definition named by `--agent-name`, using the
backend-adapted contract for its native agent type and passing the prompt file
contents verbatim as the delegation prompt.

Capture the agent's full response text.

Expand Down
6 changes: 6 additions & 0 deletions src/autoskillit/core/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ from .plugins._plugin_artifact_retirement import (
)
from .plugins._plugin_ids import _AUTOSKILLIT_INSTALL_ROOT_KEY as _AUTOSKILLIT_INSTALL_ROOT_KEY
from .plugins._plugin_ids import _AUTOSKILLIT_PLUGIN_KEY as _AUTOSKILLIT_PLUGIN_KEY
from .plugins._plugin_ids import CLAUDE_PLUGIN_AGENT_NAMESPACE as CLAUDE_PLUGIN_AGENT_NAMESPACE
from .plugins._plugin_ids import DIRECT_INSTALL_CACHE_SUBDIR as DIRECT_INSTALL_CACHE_SUBDIR
from .plugins._plugin_ids import DIRECT_PREFIX as DIRECT_PREFIX
from .plugins._plugin_ids import PLUGIN_PREFIX as PLUGIN_PREFIX
Expand Down Expand Up @@ -307,6 +308,7 @@ from .plugins._retiring_cache import repair_corrupt_retiring_cache as repair_cor
from .plugins.agent_definition import (
AGENT_DEFINITION_DIGEST_DOMAIN as AGENT_DEFINITION_DIGEST_DOMAIN,
)
from .plugins.agent_definition import AGENT_DIRECTORY_GUIDE_FILES as AGENT_DIRECTORY_GUIDE_FILES
from .plugins.agent_definition import AGENT_PROVISIONING_BASELINE as AGENT_PROVISIONING_BASELINE
from .plugins.agent_definition import (
AGENT_PROVISIONING_SKILL_DERIVED as AGENT_PROVISIONING_SKILL_DERIVED,
Expand All @@ -328,6 +330,7 @@ from .plugins.agent_definition import agent_definition_digest as agent_definitio
from .plugins.agent_definition import (
canonical_reader_tools_to_bare as canonical_reader_tools_to_bare,
)
from .plugins.agent_definition import is_agent_definition_file as is_agent_definition_file
from .plugins.agent_definition import load_agent_definition as load_agent_definition
from .plugins.agent_definition import load_agent_definitions as load_agent_definitions
from .plugins.agent_definition import (
Expand Down Expand Up @@ -451,6 +454,7 @@ from .types import AGENT_BACKEND_CLAUDE_CODE as AGENT_BACKEND_CLAUDE_CODE
from .types import AGENT_BACKEND_CODEX as AGENT_BACKEND_CODEX
from .types import AGENT_BACKEND_DYNACONF_ENV_VAR as AGENT_BACKEND_DYNACONF_ENV_VAR
from .types import AGENT_BACKEND_ENV_VAR as AGENT_BACKEND_ENV_VAR
from .types import AGENT_NAME_PATTERN as AGENT_NAME_PATTERN
from .types import AGENT_PACK_REGISTRY as AGENT_PACK_REGISTRY
from .types import ALL_PROJECT_LOCAL_SKILL_SEARCH_DIRS as ALL_PROJECT_LOCAL_SKILL_SEARCH_DIRS
from .types import ALL_SESSION_SHAPES as ALL_SESSION_SHAPES
Expand Down Expand Up @@ -544,6 +548,7 @@ from .types import (
from .types import CONTEXT_EXHAUSTION_MARKER as CONTEXT_EXHAUSTION_MARKER
from .types import CORE_PACKS as CORE_PACKS
from .types import DATA_MANIFEST_SOURCE_TYPES as DATA_MANIFEST_SOURCE_TYPES
from .types import DELEGATED_WORKER_ROLE as DELEGATED_WORKER_ROLE
from .types import DISPATCH_ID_ENV_VAR as DISPATCH_ID_ENV_VAR
from .types import DRY_WALKTHROUGH_VERIFIED_MARKER as DRY_WALKTHROUGH_VERIFIED_MARKER
from .types import DURABLE_ARTIFACT_WRITERS as DURABLE_ARTIFACT_WRITERS
Expand Down Expand Up @@ -1055,6 +1060,7 @@ from .types import LineDriver as LineDriver
from .types import LineDriverError as LineDriverError
from .types import LoadReport as LoadReport
from .types import LoadResult as LoadResult
from .types import LogicalRoleNameError as LogicalRoleNameError
from .types import LogicalRoleSpec as LogicalRoleSpec
from .types import MaintenanceInstallArgv as MaintenanceInstallArgv
from .types import ManagedCodexRoute as ManagedCodexRoute
Expand Down
6 changes: 6 additions & 0 deletions src/autoskillit/core/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
from autoskillit.core.plugins._plugin_ids import (
_AUTOSKILLIT_INSTALL_ROOT_KEY,
_AUTOSKILLIT_PLUGIN_KEY,
CLAUDE_PLUGIN_AGENT_NAMESPACE,
DIRECT_INSTALL_CACHE_SUBDIR,
DIRECT_PREFIX,
PLUGIN_PREFIX,
Expand All @@ -95,6 +96,7 @@
)
from autoskillit.core.plugins.agent_definition import (
AGENT_DEFINITION_DIGEST_DOMAIN,
AGENT_DIRECTORY_GUIDE_FILES,
AGENT_PROVISIONING_BASELINE,
AGENT_PROVISIONING_SKILL_DERIVED,
BUNDLED_EXPLORER_ROLES,
Expand All @@ -108,6 +110,7 @@
CodexAgentProjectionDef,
agent_definition_digest,
canonical_reader_tools_to_bare,
is_agent_definition_file,
load_agent_definition,
load_agent_definitions,
load_bundled_agent_definitions,
Expand All @@ -116,6 +119,7 @@

__all__ = [
"AGENT_DEFINITION_DIGEST_DOMAIN",
"AGENT_DIRECTORY_GUIDE_FILES",
"AGENT_PROVISIONING_BASELINE",
"AGENT_PROVISIONING_SKILL_DERIVED",
"ActiveKitchensReadResult",
Expand All @@ -126,6 +130,7 @@
"CODEX_DISABLED_WEB_SEARCH_POLICY",
"CODEX_EXPLORER_IDENTITY",
"CodexAgentProjectionDef",
"CLAUDE_PLUGIN_AGENT_NAMESPACE",
"DIRECT_INSTALL_CACHE_SUBDIR",
"DIRECT_PREFIX",
"INSTALLED_PLUGIN_ARTIFACT_MANIFEST_FIELDS",
Expand Down Expand Up @@ -182,6 +187,7 @@
"installed_plugin_artifact_root",
"installed_plugin_cache_dir",
"installed_plugin_semantic_key",
"is_agent_definition_file",
"is_marketplace_plugin_registered",
"is_reclaimable_artifact_path",
"kitchen_entry_alive",
Expand Down
3 changes: 3 additions & 0 deletions src/autoskillit/core/plugins/_plugin_ids.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
__all__ = [
"DIRECT_INSTALL_CACHE_SUBDIR",
"DIRECT_PREFIX",
"CLAUDE_PLUGIN_AGENT_NAMESPACE",
"PLUGIN_PREFIX",
"claude_plugin_tool_prefix",
"find_qualified_autoskillit_tool_names",
Expand Down Expand Up @@ -50,6 +51,8 @@
# session (--plugin-dir and marketplace alike):
# mcp__plugin_<plugin.json name>_<.mcp.json server key>__
PLUGIN_PREFIX = "mcp__plugin_autoskillit_autoskillit__"
# Claude Code registers plugin agents as <plugin.json name>:<agent name>.
CLAUDE_PLUGIN_AGENT_NAMESPACE = "autoskillit:"

_CLAUDE_TOOL_SEGMENT_INVALID_CHARS = re.compile(r"[^A-Za-z0-9_-]")
_QUALIFIED_AUTOSKILLIT_TOOL_RE = re.compile(
Expand Down
14 changes: 11 additions & 3 deletions src/autoskillit/core/plugins/agent_definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from ..io import load_yaml
from ..io.paths import pkg_root
from ..types import (
AGENT_NAME_PATTERN,
CODEX_EFFORT_MAPPING,
CODEX_MODEL_ALIASES,
CODEX_VALID_MODEL_IDS,
Expand All @@ -27,6 +28,7 @@

__all__ = [
"AGENT_DEFINITION_DIGEST_DOMAIN",
"AGENT_DIRECTORY_GUIDE_FILES",
"AGENT_PROVISIONING_BASELINE",
"AGENT_PROVISIONING_SKILL_DERIVED",
"BUNDLED_EXPLORER_ROLES",
Expand All @@ -40,6 +42,7 @@
"CodexAgentProjectionDef",
"agent_definition_digest",
"canonical_reader_tools_to_bare",
"is_agent_definition_file",
"load_agent_definition",
"load_agent_definitions",
"load_bundled_agent_definitions",
Expand All @@ -48,6 +51,7 @@


AGENT_DEFINITION_DIGEST_DOMAIN = "autoskillit.agent-definition.v1"
AGENT_DIRECTORY_GUIDE_FILES: Final = frozenset({"AGENTS.md", "CLAUDE.md"})
AGENT_PROVISIONING_BASELINE: Final = "baseline"
AGENT_PROVISIONING_SKILL_DERIVED: Final = "skill-derived"
CODEX_DISABLED_WEB_SEARCH_POLICY: Literal["disabled"] = "disabled"
Expand All @@ -58,7 +62,6 @@
BUNDLED_EXPLORER_ROLES: frozenset[str] = frozenset(
{SEMANTIC_CODE_NAVIGATOR_ROLE, REPOSITORY_IMPACT_PROFILER_ROLE}
)
_AGENT_NAME_RE = re.compile(r"^[a-z][a-z0-9-]*$")
_DIRECT_TOOL_NAME_RE = re.compile(r"^[a-z][a-z0-9_]*$")
_CODEX_CLI_VERSION_RE = re.compile(r"(?:codex-cli )?(?P<version>[0-9]+\.[0-9]+\.[0-9]+)")
_READ_ONLY_AGENT_TOOLS = frozenset({"Read", "Grep", "Glob", "LSP"})
Expand Down Expand Up @@ -199,7 +202,7 @@ class AgentDef:
provisioning: Literal["skill-derived", "baseline"] = AGENT_PROVISIONING_SKILL_DERIVED

def __post_init__(self) -> None:
if not _AGENT_NAME_RE.fullmatch(self.name):
if not AGENT_NAME_PATTERN.fullmatch(self.name):
raise AgentDefinitionError(f"invalid agent name: {self.name!r}")
if not self.description.strip():
raise AgentDefinitionError("agent description must be non-empty")
Expand Down Expand Up @@ -396,12 +399,17 @@ def load_agent_definition(path: Path) -> AgentDef:
)


def is_agent_definition_file(path: Path) -> bool:
"""Return whether *path* is a Markdown agent definition rather than a guide."""
return path.is_file() and path.suffix == ".md" and path.name not in AGENT_DIRECTORY_GUIDE_FILES


def load_agent_definitions(agents_dir: Path) -> tuple[AgentDef, ...]:
"""Load the complete agent catalog and reject duplicate registered names."""
definitions = tuple(
load_agent_definition(path)
for path in sorted(agents_dir.glob("*.md"))
if path.name not in {"AGENTS.md", "CLAUDE.md"}
if is_agent_definition_file(path)
)
names = tuple(definition.name for definition in definitions)
duplicates = sorted({name for name in names if names.count(name) > 1})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,16 @@ class SkillContractRemediationDef(NamedTuple):
"definition is effective"
),
),
SkillContractRemediationDef(
kind=SkillInvalidityKind.SEMANTIC_ROLE_NAME_NONCANONICAL,
introduced_in="0.10.1256",
action=RemediationAction.DETERMINISTIC,
hint=(
"logical role names are bare agent names matching `^[a-z][a-z0-9-]*$`; drop any "
"`autoskillit:` namespace, because each backend adds its own native namespace. "
"Migration strips it."
),
),
)
SKILL_CONTRACT_REMEDIATIONS: Mapping[SkillInvalidityKind, SkillContractRemediationDef] = (
MappingProxyType(
Expand Down
1 change: 1 addition & 0 deletions src/autoskillit/core/types/foundation/_type_enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ class SkillInvalidityKind(StrEnum):
SEMANTIC_MISSING_VERSION = "semantic_missing_version"
SEMANTIC_VERSION_MISMATCH = "semantic_version_mismatch"
SEMANTIC_CHILD_CARDINALITY_INVALID = "semantic_child_cardinality_invalid"
SEMANTIC_ROLE_NAME_NONCANONICAL = "semantic_role_name_noncanonical"
SEMANTIC_PLAN_INVALID = "semantic_plan_invalid"


Expand Down
5 changes: 5 additions & 0 deletions src/autoskillit/core/types/foundation/_type_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"ExplorationBindingFailed",
"InfrastructureFaultError",
"LineDriverError",
"LogicalRoleNameError",
"PluginArtifactContentionError",
"PluginArtifactPublicationError",
"PluginArtifactUnavailableError",
Expand Down Expand Up @@ -197,6 +198,10 @@ class SkillContractError(ValueError):
"""A skill machine contract is malformed or exceeds its execution role."""


class LogicalRoleNameError(SkillContractError):
"""A logical role name is not a bare canonical agent name."""


class ChildSpawnCardinalityError(SkillContractError):
"""A child spawn does not declare exactly one valid cardinality authority."""

Expand Down
2 changes: 1 addition & 1 deletion src/autoskillit/core/types/launch/_type_skill_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
)
MANAGED_SKILL_FILENAME = "SKILL.md"
# Static resources are now compiled into projected SKILL.md bytes.
SKILL_PROJECTION_VERSION = 10
SKILL_PROJECTION_VERSION = 12
SKILL_SESSION_CONTRACT_SCHEMA_VERSION = 5
PARENT_SANDBOX_MODES: frozenset[str] = frozenset({"read-only", "workspace-write"})
_CANONICAL_IDENTIFIER_RE = re.compile(r"[a-z][a-z0-9]*(?:-[a-z0-9]+)*\Z")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,12 @@ def setup_session_dir(
agent_defs: tuple[AgentDef, ...] | None = None,
explorer_binding_env: Mapping[str, Mapping[str, str]] | None = None,
execution_role: SkillExecutionRole = SkillExecutionRole.SESSION,
) -> frozenset[str] | None: ...
) -> frozenset[str]:
"""Finalize the session and return the native agent names it can resolve;

admission prunes skills whose native spawn targets fall outside this set.
"""
...

def refresh_explorer_binding_env(
self,
Expand Down
Loading
Loading