Metadata-Version: 2.4
Name: plm-skill-kernel
Version: 1.0.0
Summary: TracePulse PLM Skill Kernel — Wave 2 Conv A. Decorator-based Skill SDK + V1 in-process dispatcher + Kernel HTTP server stub matching CR.10 §7.bis (POST /v1/skills/{id}/invoke). Ships 3 starter skills (cleansing.normalise, cleansing.dedupe, bpmn.generate). Sibling of plm-engine-core; the two communicate over the V1.1 HTTP loopback (SKILL_KERNEL_LOOPBACK=on) — no Python-level coupling at the dispatch boundary.
Author: TracePulse
License: Proprietary
Requires-Python: >=3.12
Description-Content-Type: text/markdown
Requires-Dist: plm-shared
Requires-Dist: pydantic>=2.5.0
Requires-Dist: fastapi>=0.110
Requires-Dist: uvicorn>=0.27
Requires-Dist: httpx>=0.27.0
Requires-Dist: plm-engine-core
Requires-Dist: plm-knowledge
Provides-Extra: test
Requires-Dist: pytest; extra == "test"
Requires-Dist: pytest-asyncio; extra == "test"
Requires-Dist: httpx<1,>=0.27; extra == "test"

# plm-skill-kernel

Wave 2 Conv A (FTR-604). The Skill Kernel package — sibling of
`plm-engine-core` — that hosts the decorator-based Skill SDK, the V1
in-process dispatcher, and the Kernel HTTP server stub matching CR.10
§7.bis verbatim.

## Layout

```
02_App/plm-skill-kernel/
├── pyproject.toml             # editable install + import-linter contracts
├── plm_skill_kernel/
│   ├── __init__.py            # re-exports `skill`, `SkillContext`, etc.
│   ├── __main__.py            # `python -m plm_skill_kernel` → server
│   ├── server.py              # FastAPI app at POST /v1/skills/{id}/invoke
│   ├── dispatcher.py          # V1 in-process invoker
│   ├── registry.py            # boot wiring — discovers + registers skills
│   ├── sdk/
│   │   ├── decorator.py       # `@skill(id=, version=)`
│   │   ├── context.py         # SkillContext Pydantic
│   │   ├── types.py           # SkillInvokeRequest / SkillInvokeResult
│   │   └── _registry.py       # in-process decorator registry
│   └── skills/                # 3 starter skills (Q-W2A-1 nested layout)
│       ├── cleansing/
│       │   ├── normalise.py   # cleansing.normalise / 1.0.0 / L2
│       │   └── dedupe.py      # cleansing.dedupe / 1.0.0 / L2
│       └── bpmn/
│           └── generate.py    # bpmn.generate / 1.0.0 / L1 (thin proxy)
└── tests/                     # unit + ASGI in-process integration
```

## Editable install

```bash
cd 02_App/backend
.\venv\Scripts\Activate.ps1                  # Windows
pip install -e ../plm-skill-kernel
```

Mirrors the plm-engine-core editable-install pattern.

## Dev launch (Q-W2A-3)

```bash
python -m plm_skill_kernel               # uvicorn on port 8100
plm-skill-kernel --port 8100             # equivalent CLI
```

The backend at port 8000 is unaffected. The V1.1 loopback round-trip
(D-CONV-M-4 closure) sends HTTP from the Engine Core dispatcher to
this Kernel server when `SKILL_KERNEL_LOOPBACK=on`.

## V1 wire contract

`POST /v1/skills/{id}/invoke` — frozen at CR.10 §7.bis.

```
Headers:
  X-Core-Caller: <UUID>            (required when caller is Core)
  Idempotency-Key: <uuid>          (optional, V1)
  traceparent: <W3C>               (optional)

Request body (SkillInvokeRequest):
  payload:        Dict[str, Any]
  version:        str  ("1.0.0", etc.)
  autonomy_hint:  Optional[str]    ("L1" / "L2" / "L3")

Response body (SkillInvokeResult):
  ok:             bool
  result:         Optional[Dict[str, Any]]
  error:          Optional[SkillErrorEnvelope]
  deferred_to:    Optional[str]
  cancelled:      Optional[bool]
```

## Skill authoring (Decision #59 — decorator)

```python
from plm_skill_kernel import skill, SkillContext

@skill(id="cleansing.normalise", version="1.0.0")
async def normalise(payload: dict, ctx: SkillContext) -> dict:
    return {"normalised": _normalise_records(payload["records"])}
```

The decorator auto-registers into the in-process registry at module
import time + carries `version` through to the federated manifest
builder (CR.9).

## Import-linter contracts

Two Forbidden contracts live in `pyproject.toml`:

1. `plm_skill_kernel` MUST NOT import `plm_engine_core` — federation
   contract preservation. The two communicate over HTTP, not Python.
2. `plm_skill_kernel` MUST NOT import `plm_accelerators` (Workbench)
   — mirror of the plm-engine-core constraint.

Run:

```bash
lint-imports --config 02_App/plm-skill-kernel/pyproject.toml
```

## Tests

```bash
cd 02_App/plm-skill-kernel
python -m pytest -q
```

The V1.1 HTTP round-trip integration test mounts the FastAPI app via
httpx.AsyncClient(ASGITransport) — no subprocess, no real port
(Q-W2A-4).
