> Canonical guide: https://developers.foxlight.ai/build/sdk/first-child/
> Contract snapshots: Skulk 2.0.0 (b0af39c79b6b7102b2478062904f1d7cc8619975); SDK 0.4.0 (31bb090b8f64689f87514e48385e22b6ad94a6c2). Check the installed runtime when versions differ.

# Your first managed _child_

Start with a read-only echo operation. A managed child is an isolated process: the host supplies its startup context, authenticates it, checks health, and dispatches negotiated calls. Your code implements the operation.

:::note Package availability
This guide targets SDK 0.4.0. Publication is pending; there is no public installation command yet. Use an approved SDK wheel supplied through the developer distribution channel. Do not substitute the private owner package for the authoring SDK.
:::

## 1. Define the descriptor

```python
from skulk_capability_sdk.contracts import Descriptor

TEXT_SCHEMA = {
    "type": "object",
    "properties": {"text": {"type": "string", "maxLength": 1024}},
    "required": ["text"],
    "additionalProperties": False,
}
ECHO = Descriptor(
    id="echo", version="1.0.0", title="Echo",
    description="Return up to 1024 characters of input text unchanged.",
    io_mode="unary",
    input_schema=TEXT_SCHEMA,
    output_schema=TEXT_SCHEMA,
)
print(ECHO.qualified_id)
print(ECHO.revision)
```

Declare this descriptor in the installation's manifest. The host's exact manifest digest, executable digest, protocol and Skulk version constraint must agree with the installed runtime. See [contracts and startup](https://developers.foxlight.ai/build/sdk/contracts/) and [packaging](https://developers.foxlight.ai/build/manifest/).

## 2. Read the inherited startup record

Your executable accepts the host's `--config-fd` argument and calls `storage.read_startup` with that descriptor number. The bounded startup record provides `socket`, `token`, identities, manifest, bounds and configuration. It is not a file supplied by a caller.

```python
from skulk_capability_sdk.storage import read_startup

startup = read_startup(config_fd)
```

`config_fd` comes from your command-line argument parser. Never log the startup record, token, credential paths or credential values. Refuse invalid startup instead of trying arbitrary sockets or discovering another executable on `PATH`.

## 3. Authenticate and answer the control channel

Connect with `asyncio.open_unix_connection(startup.socket)`. Send a `Hello` whose protocol matches `startup.manifest.protocol` and whose identity fields come from the startup record:

| Hello field                   | Value                                  |
| ----------------------------- | -------------------------------------- |
| `host_id`                     | `startup.host_id`                      |
| `transport_node_id`           | `startup.transport_id`                 |
| `capability_node_id`          | `startup.node_id`                      |
| `bundle_id`, `bundle_version` | The startup manifest's bundle identity |
| `token`                       | `startup.token`                        |
| `manifest_sha256`             | `manifest_digest(startup.manifest)`    |

Use [`read_message` and `write_message`](https://developers.foxlight.ai/build/sdk/wire/) for the primary channel. Answer a `Health` challenge with the same nonce and your actual readiness. `Invoke` starts one unary operation; reply with one correlated `Result` containing either a payload or a typed error. `Shutdown` ends the loop and cleans up owned resources.

## 4. Keep the operation independently testable

```python
def echo(payload: dict[str, object]) -> dict[str, object]:
    """Return schema-valid input unchanged without external effects."""
    text = payload.get("text")
    if not isinstance(text, str) or len(text) > 1024:
        raise ValueError("Expected text of at most 1024 characters")
    return {"text": text}

assert echo({"text": "Hello from the fabric."}) == {
    "text": "Hello from the fabric."
}
```

The host performs schema validation, but provider validation still protects your own direct test and internal call paths. Translate a known invalid input into `invalid_payload`; do not disguise an unexpected bug as an input failure.

## 5. Add streaming when needed

Protocol 4 sends `StreamInvoke` on the control channel. Own a separate task running `streams.execute_stream(startup, message, handler)`, continue answering health, and cancel/await that task on shutdown or control-channel loss. Use the [managed streaming guide](https://developers.foxlight.ai/build/sdk/streams/) for a bidirectional handler and exact terminal/cleanup rules.

## Complete example

The [managed echo example](https://developers.foxlight.ai/build/examples/echo/) puts startup, authentication, health, invocation and shutdown together in one child entrypoint.

## 6. Qualify before distribution

Test wrong startup identity and digest, invalid input/output, deadlines, overload, cancellation, lost connections and restart. Then qualify a real signed installation with the compatible host and Skulk reader. A direct function test does not establish package or Fabric readiness.

For a complete runnable public extension package available today, use the [in-process echo tutorial](https://developers.foxlight.ai/build/first-capability/). Managed packaging tooling and SDK distribution complete a separate delivery gate.
