Metadata-Version: 2.4
Name: gl-skill-binary
Version: 0.0.1b2
Summary: Standalone GL Skill loader and bounded asynchronous executor.
Author-email: Raymond Christopher <raymond.christopher@gdplabs.id>
License: Apache-2.0
Requires-Python: <3.14,>=3.11
Description-Content-Type: text/markdown
Requires-Dist: gllm-inference-binary[openai]<0.6.137,>=0.6.130
Requires-Dist: jsonschema>=4.26.0
Requires-Dist: pyyaml<7.0,>=6.0

# GL Skill

`gl-skill` is a standalone Python library for loading one local Skill and running one bounded
model/tool loop. Clients start a run through `run()` and consume ordered events plus
one terminal result; they do not resume unfinished tool turns.

An application (for example, AIP) initiates every run. GL Skill composes its **Loader** and
**Executor**, while **Tool Runtime** is the dispatch authority for admitted capabilities. Built-in
workspace operations flow through **SkillWorkspaceRuntime** into [gl-sandbox](../gl-sandbox);
caller tools are separately registered host implementations. GL Skill never asks AIP to continue an
unfinished tool turn.

## Quick start

After installing `gl-skill` and setting `OPENAI_API_KEY`, an instruction-only local Skill needs
only its directory and a query. The default capability allowlist is empty, so this path does not
create a workspace:

```python
import asyncio

from gl_skill import GLSkill, SkillRunResult


async def main() -> None:
    result = None
    async for item in GLSkill.run(skill="./skills/hello", query="Say hello."):
        if isinstance(item, SkillRunResult):
            if not item.succeeded:
                detail = item.error.message if item.error is not None else f"status={item.status.value}"
                raise RuntimeError(f"GL Skill run failed: {detail}")
            result = item
    assert result is not None
    print(result.output_text)


if __name__ == "__main__":
    asyncio.run(main())
```

This normal-file example requires an existing `./skills/hello/SKILL.md`. From `libs/gl-skill`, run
the live default-model path after explicitly supplying its process-level credential prerequisite:

```bash
# Set OPENAI_API_KEY in this process through your credential manager first.
uv run python examples/hello_world.py
```

This command makes a paid provider request through the configured default model route. GL Skill
does not read `.env`; the key must already be in the process environment. The deterministic
convergence test exercises this same facade and asserts the printed greeting without making a
network request.

`GLSkill.run()` streams the same ordered lifecycle. Advanced callers can import `GLSkillClient`,
`PublicRunRequest`, and `PublicDependencies` to inject a provider-neutral model runtime, caller
capabilities, event sink, or sandbox backend explicitly. The facade never searches for `.env` files;
the default model path reads only the process-level `OPENAI_API_KEY` value.

For migration details and the typed stream contract, see
[`docs/migration-run-only.md`](docs/migration-run-only.md).

This repository provides the independently installable provider-neutral Loader and Executor from
[roadmap #6138](https://github.com/GDP-ADMIN/gl-sdk/issues/6138). Architecture contracts live in
[contracts/](contracts/README.md), with design material under [docs/architecture/](docs/architecture/README.md).

## Install

The package includes the supported GLLM inference runtime, `jsonschema>=4.26` for Executor schema
validation, and `pyyaml>=6,<7` for the metadata seam. It does not require credentials, `.env`, AIP,
GL Connectors, Hermes, `gl-sandbox`, or workspace backends:

```bash
uv pip install dist/gl_skill-*.whl
python -c "import gl_skill"
```

The executor's default workspace policy is schema-complete: 100 files, 10,000,000 total bytes,
1,000,000 bytes per file, 20-second command timeout, 100,000 command-output bytes, no environment
variables, and network access disabled. Unknown or malformed policy fields fail closed; network
capabilities are not admitted by the MVP, and sandbox-required capabilities must be validated
built-in workspace capabilities.
The default install includes the public binary distribution for the GL SDK model runtime. Until this
package is published, install the built wheel and its runtime dependency first:

```bash
uv pip install 'gllm-inference-binary[openai]>=0.6.130,<0.6.137' dist/gl_skill-*.whl
```

The runtime's import surface remains `gllm_inference`. Imports stay lazy for callers that inject a
custom `ModelRuntimeProtocol`, but the supported default dependency is installed with `gl-skill`.
The binary runtime currently publishes wheels for CPython 3.11–3.13 on Linux
`manylinux_2_31_x86_64`, Windows `win_amd64`, and macOS `macosx_13_0_arm64`.
Support follows the selected GLLM binary release; other platforms cannot use the
default install until a compatible distribution is published or the package is
split into a provider-neutral core.

Release validation records the resolved dependency graph and installed footprint
for each runtime pin; this is intentionally not a fixed value in the user guide.

## Inputs and configuration

GL Skill reads no `.env` file implicitly and performs no environment discovery beyond the documented
default-model path. Compose these inputs explicitly:

- **Skill source:** an absolute local `file:///<skills-root>` URI plus a portable directory-name
  `skill_ref`. The referenced directory must contain a regular `SKILL.md`.
- **Request:** caller identity, correlation ID, query, stable allowed capability IDs, model ID, run
  limits, and workspace policy.
- **Dependencies:** a Skill provider, an optional custom model runtime, an optional `SandboxBackend`,
  registered caller tools, and an optional event sink. If no model is injected, `OPENAI_API_KEY` must
  already be present in the process environment and the installed GLLM runtime supplies the default.
- **Workspace:** an explicit backend object is required before built-in `workspace.*` capabilities
  can be admitted. There is no automatic sandbox selection or credential discovery.

## Advanced explicit composition

This first example creates one temporary local Skill and runs it with a scripted model. It has no
network access and requires only the core development environment:

```bash
cd libs/gl-skill
make setup
uv run python examples/quickstart_run.py
```

Expected terminal summary:

```text
status=succeeded output='The note was saved.'
```

The complete deterministic source is
[`examples/quickstart_run.py`](examples/quickstart_run.py). The installed-wheel
convergence gate executes that canonical file on Linux and Windows. Replace its
scripted model with a real model adapter when your application owns that
dependency.

A successful result contains one authoritative terminal status, final text, typed receipts, evidence,
and usage. Its wire shape is frozen by
[contracts/schemas/skill-run-result.schema.json](contracts/schemas/skill-run-result.schema.json);
examples live beside it in [contracts/examples/](contracts/examples/).

## Streaming

Use `client.run(request)` when the initiating application wants ordered progress. When consumed to
completion, it yields events and then exactly one `SkillRunResult`:

```python
from gl_skill import SkillRunResult

async for item in client.run(request):
    if isinstance(item, SkillRunResult):
        print(f"result={item.status.value}")
    else:
        print(f"event={item.type} sequence={item.sequence} terminal={item.terminal}")
```

Closing the stream early ends observation and runs bounded cleanup, but does not deliver a normalized
cancelled terminal item to the detached consumer. Use `contextlib.aclosing()` (or explicitly await
`stream.aclose()`) when breaking early so cleanup is prompt:

```python
from contextlib import aclosing

from gl_skill import SkillRunResult

async with aclosing(client.run(request)) as stream:
    async for item in stream:
        if should_stop_observing(item):
            break
        if isinstance(item, SkillRunResult):
            print(item.status.value)
```

Cancelling the consumer task instead runs cleanup and propagates `asyncio.CancelledError`, with no
terminal-delivery guarantee. To receive a cancelled terminal event and `SkillRunResult`, pass an
`asyncio.Event` as `cancellation_token` to `GLSkillClient.run()` and remain attached through the final
item.

The runnable version also uses the same temporary Skill and scripted model:

```bash
uv run python examples/streaming_run.py
```

## Tools, resources, policy, and events

### Stable capability IDs and model-visible names

Admission uses stable versioned IDs such as `workspace.read@1` and `caller.echo@1`. Model-visible
names such as `workspace_read` and `caller_echo` exist only in schemas projected to the model. Do not
persist model-visible names as authorization identities.

### Caller tools

Caller handlers are trusted host callbacks registered by the application. They execute in the host
process and do **not** inherit the gl-sandbox guarantee. Validate inputs at their boundary and give
them truthful effects metadata:

```bash
uv run python examples/caller_tool.py
```

See [examples/caller_tool.py](examples/caller_tool.py) for complete input/output JSON Schemas,
effects, handler registration, receipt inspection, and expected offline output.

### Sandboxed workspace operations

Four built-in capabilities are available when an explicit backend is supplied:

| Stable capability ID | Model-visible name | Operation |
| --- | --- | --- |
| `workspace.list@1` | `workspace_list` | List staged files |
| `workspace.read@1` | `workspace_read` | Read bounded file content |
| `workspace.write@1` | `workspace_write` | Write bounded bytes |
| `workspace.execute_command@1` | `workspace_execute_command` | Execute exact argv in sandbox |

The Loader scans regular resource files into an immutable manifest with sizes and SHA-256 hashes.
Sandbox creation is lazy: no backend is created until the Executor needs the workspace. On first use,
SkillWorkspaceRuntime stages manifest resources, verifies hashes through the sandbox command channel,
and applies the caller-supplied `WorkspacePolicy` (file count/size limits, command timeout/output
limits, empty-by-default environment allowlist, and disabled network access). Commands receive exact
argv vectors—not shell strings—and results include exit status, bounded stdout/stderr, timing, and
truncation state.

If a caller allows `workspace.*` without providing a backend, the run fails closed with
`policy_denied` and code `workspace_unavailable`. The deterministic demonstration in
[examples/workspace_demo.py](examples/workspace_demo.py) uses an in-memory backend so it can run
offline; it is **not** a production sandbox substitute. Production callers inject a real
`SandboxBackend` and normally construct its transport from `gl-sandbox` public primitives.

### Events, receipts, cancellation, and retries

Tool Runtime performs whole-batch admission preflight before dispatch. Every dispatched call emits
correlated `gl_skill.tool_call` and `gl_skill.tool_result` events and produces a typed receipt. A
failed call produces a failed receipt; GL Skill does not automatically retry it. Terminal events are
couples to statuses: `final_response` means `succeeded`, `cancelled` means `cancelled`, and
`error` carries other failure statuses.

Pass `asyncio.Event` for cooperative deadline cancellation:

```python
cancellation_token = asyncio.Event()
cancellation_token.set()
async for item in client.run(request, cancellation_token=cancellation_token):
    if isinstance(item, SkillRunResult):
        assert item.status.value == "cancelled"
```

Limits on turns, tool calls, wall-clock time, and output size are set by `RunLimits`; workspace
resource/command bounds are separate in `WorkspacePolicy`.

### Deferred integrations

Deep Agents, remote lifecycle management, and bidirectional synchronization are deferred. AIP/GL
Connectors may initiate runs or supply external integrations, but Skill scripts and commands execute
only inside gl-sandbox.

The internal GLLM boundary is documented separately in
[runtime adapters](docs/runtime-adapters.md); it is never loaded by the core import surface.

## Optional workspace runtime

GL Skill never selects a sandbox provider or reads provider credentials. The application chooses
and configures its supported `gl-sandbox` backend, then passes a factory that returns its
provider-neutral `SandboxBackend`. `lazy_workspace_tool_runtime()` stores that factory without
constructing a backend or importing `gl_sandbox`; the first executor-admitted workspace call
initializes both exactly once. An instruction-only run, or a run with caller tools only, stays
sandbox-free.

Callers consume the public `GLSkillClient.run()` stream; the executor does not
expose a separate one-shot lifecycle.

The executor supplies an immutable `ToolContext` for every admitted call. Its request ID,
remaining deadline, cancellation view, and complete `workspace_policy` are authoritative; unknown
policy fields, invalid bounds, network access, and implicit environment inheritance fail closed.
The default environment allowlist is empty. The workspace layer passes only exact argv vectors to
the two public `gl-sandbox` primitives—no shell, provider-private helper, GNU command, or glob
expansion is required.

Lifecycle ownership is deliberately singular. Normal completion is terminated by executor cleanup.
If a public primitive is cancelled or its outer deadline expires, that primitive terminates its own
sandbox and the workspace runtime only discards the handle; executor cleanup then becomes a no-op.
This prevents a second termination of the same backend.

## Development

Open-format naming, metadata, provider reuse, and runtime-ownership decisions
are documented in [Agent Skills format compatibility](docs/agent-skills-compatibility.md).

```bash
make setup
make check
```

`make check` runs formatting/lint checks, strict typing, unit tests, the bare-import footprint gate,
and wheel construction. It uses the locked development environment; resolving or populating that
environment may require access to the configured package index. Once dependencies and the build
backend are available, the package tests and footprint probe are deterministic and offline.

### Dependency-footprint gate

`scripts/check_dependency_footprint.py` verifies that the wheel contains both `gl_skill` and
`gl_skill/py.typed`; that its base runtime dependencies include GLLM, `jsonschema`, and `pyyaml`; that a
clean wheel-only environment can import `gl_skill`; and that doing so loads none of these forbidden
top-level modules:

- `aip_agents`, `aip_sdk`, `glaip_sdk`
- `gl_connectors`
- `hermes`
- `gllm_core`, `gllm_inference`
- `gl_sandbox`, `e2b_code_interpreter`, `opensandbox`, `boto3`, `aioboto3`
- `openai`, `anthropic`, `google.genai`, `google.generativeai`
- `cohere`, `groq`, `litellm`, `mistralai`, `ollama`, `vertexai`
- `skills_ref` (development-only format oracle)

The gate also records that no optional GLLM extra is required in the built metadata. Future optional
surfaces must extend this evidence with their declared dependency graph instead of relying on an
unmeasured “runtime-free” label.

## Examples index

| Example | Purpose |
| --- | --- |
| [`hello_world.py`](examples/hello_world.py) | Canonical low-code `GLSkill.run()` call |
| [`quickstart_run.py`](examples/quickstart_run.py) | Deterministic explicit-composition `run()` call |
| [`streaming_run.py`](examples/streaming_run.py) | Ordered events followed by one terminal result |
| [`caller_tool.py`](examples/caller_tool.py) | Register a trusted callback and inspect its receipt |
| [`workspace_demo.py`](examples/workspace_demo.py) | Deterministic sandbox-policy demonstration using an in-memory backend |
| [`live_model/openai_smoke.py`](examples/live_model/openai_smoke.py) | Explicitly credential-gated real-model smoke test |
