# remote

Remote is Python running under another target's identity and capability set, through a
host-owned namespace. You get what the target advertises, and a target that is not there is
refused rather than emulated locally.

```python
from klbr import remote

async with remote.connect("build-host") as kernel:
    print(kernel.identity.version, list(kernel.capabilities))
```

## the trail

`connect` ensures a namespace: it starts a real worker on that target and returns a handle
bound to the namespace and generation it was issued. The binding is what makes a later call
addressable, and a call whose binding is gone fails instead of quietly reconnecting.

```python
kernel = await remote.connect("build-host")
print(await kernel.control.eval("sys.version_info[:2]"))
```

Control calls return handles, not values. Await the handle, `poll()` for what is ready, or
watch it while it runs:

```python
handle = kernel.control.execute("import time; time.sleep(30)")
print(handle.running, handle.output())
outcome = await handle.acancel()
print(outcome.state, outcome.confirmed)
```

Cancellation is three-valued on purpose: `confirmed`, `requested`, `unknown`. `unknown` means
the transport did not confirm it, which is not the same as "stopped" - say so rather than
assume it.

Shell is its own typed call with its own result type:

```python
bash = await kernel.bash("uname -a")
print(bash.exit_code, bash.stdout, bash.stderr_truncated)
```

## the target's tree, with your own verbs

The five verbs you already have locally exist on the handle too, and they return the same
shapes, because the target runs the same `klbr.workspace` this cell does:

```python
listing = await kernel.ls("src", recursive=True)     # Listing of Entry
found = await kernel.search("needle", ".")           # SearchResult of TextMatch
file = await kernel.open_file("src/main.py")         # not a read, and not a stat
print(await file.text())
await file.write("print('rewritten')\n")
await file.replace("rewritten", "replaced")
ran = await kernel.run(("cargo", "check"), cwd=".")  # RunResult; argv, not a shell string
await kernel.delete("build", recursive=True)
```

`open_file` gives back a handle with the local names - `.relative .text() .read() .exists()
.hashlines() .write() .replace() .edit()` - and every call is a remote request.

The refusals are the target's own, because they are the same rule and not a second copy of
it: `delete("build")` on a non-empty directory comes back saying how many entries are in the
way and to pass `recursive=True`. A `run` deadline that expires is reported in the result
(`timed_out`, `completed=False`) rather than raised.

Underneath, `kernel.pathlib`, `kernel.os`, `kernel.sys`, `kernel.shutil` and `kernel.bash`
stay available as the lower-level mirror of the target's own namespaces, one remote request
per call. Use them for what the five verbs do not cover; use the verbs for the tree.

Advertised capabilities become attributes. If a target publishes a skill, it appears as
`kernel.<capability>.<callable>`, and calling it returns a handle. Reflection gives you the
real signature instead of a guess:

```python
print(await kernel.some_capability.some_call.signature())
```

## refusals

There is no SSH fallback and no local fallback. If no placement is configured, every target is
`unavailable`, and nothing will quietly run the work here instead. A capability that is not
advertised is refused before anything runs. Opaque call dispatch to a relay target is
deliberately refused until that target runs a dispatcher - a refusal, not something to retry.
