@agentshouse/kit 0.1.0-alpha.36 → 0.1.0-alpha.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -2277
- package/bin/connect-linux.sh +291 -78
- package/bin/connect-windows.ps1 +1 -1
- package/bootstrap.json +5 -5
- package/docs/configuration.md +104 -0
- package/docs/google.md +33 -0
- package/docs/linux.md +71 -0
- package/docs/macos.md +26 -0
- package/docs/telegram.md +29 -0
- package/docs/windows.md +36 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,2309 +1,70 @@
|
|
|
1
1
|
# House Kit
|
|
2
2
|
|
|
3
|
-
House Kit
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
House Kit connects a computer you control to [agents.house](https://agents.house).
|
|
4
|
+
It runs your Agents' sessions there with the agent CLIs House selects, keeps
|
|
5
|
+
local working copies of the Rooms you choose, captures the sources you
|
|
6
|
+
configure, and can serve your own Telegram bot.
|
|
6
7
|
|
|
7
8
|
The Skill set Kit installs is Matt Pocock's
|
|
8
9
|
[`mattpocock/skills`](https://github.com/mattpocock/skills); thank you, Matt.
|
|
9
10
|
|
|
10
|
-
## Connect a
|
|
11
|
+
## Connect a computer
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
Linux (`amd64`, `arm64`) or macOS (Apple silicon, Intel):
|
|
13
14
|
|
|
14
15
|
```sh
|
|
15
|
-
bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.
|
|
16
|
+
bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.38/connect-linux.sh)
|
|
16
17
|
```
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
computer. A Docker Engine that already answers is used as it is, on any
|
|
20
|
-
distribution. On Ubuntu 22.04, 24.04 or 26.04 LTS it follows Docker's
|
|
21
|
-
[apt repository recipe](https://docs.docker.com/engine/install/ubuntu/) for
|
|
22
|
-
whatever is missing: it installs `docker-ce`, `docker-ce-cli`,
|
|
23
|
-
`containerd.io`, `docker-buildx-plugin` and `docker-compose-plugin` from
|
|
24
|
-
`download.docker.com`, starts the `docker` service when it is stopped and adds
|
|
25
|
-
the account to the `docker` group. Every such step runs through `sudo`, which
|
|
26
|
-
asks for the password as it ordinarily does. A new group membership applies
|
|
27
|
-
from the account's next sign-in, so the invocation that grants it reaches
|
|
28
|
-
Docker Engine through `sudo` for the rest of its own run; a later invocation
|
|
29
|
-
from a session older than the membership is refused until the account signs
|
|
30
|
-
out and back in. The bootstrap refuses an Ubuntu computer whose installed
|
|
31
|
-
`docker.io`, `docker-compose`, `docker-compose-v2`, `docker-doc`,
|
|
32
|
-
`docker-buildx`, `podman-docker`, `containerd` or `runc` package conflicts with
|
|
33
|
-
that recipe rather than removing it. Any other distribution needs a working
|
|
34
|
-
Docker Engine supplied by its owner, and without one the bootstrap stops with
|
|
35
|
-
that missing prerequisite. A step that fails names its cause and changes
|
|
36
|
-
nothing further; running the same bootstrap again continues from the state it
|
|
37
|
-
left, including after a restart.
|
|
38
|
-
|
|
39
|
-
The release asset pins one immutable multi-architecture image digest. It creates
|
|
40
|
-
owner-protected `~/.house-kit` and `~/AgentsHouse`, mounts those as the complete
|
|
41
|
-
Kit home and `/agents/house`, enrolls through House Login in the host browser,
|
|
42
|
-
and starts the container with Docker's `unless-stopped` lifecycle. Pass
|
|
43
|
-
`--workspace /absolute/path` for one other workspace root or `--manual` for
|
|
44
|
-
the existing URL and one-time-code login route. `--house <origin>` selects
|
|
45
|
-
another House and is retained in Kit home. A local loopback House,
|
|
46
|
-
`http://127.0.0.1:<port>` or `http://localhost:<port>`, runs the resident
|
|
47
|
-
container on the host network, as the one-shot login, credential check and
|
|
48
|
-
apply containers always run, so the resident reaches the House the host serves;
|
|
49
|
-
an HTTPS House leaves it on Docker's own network. A repeated invocation reuses the
|
|
50
|
-
same container, credential and Environment. Every invocation that is not a
|
|
51
|
-
forwarded command also sets `skills` in Kit's configuration: `--no-skills`
|
|
52
|
-
declines [the Skill set](#the-skill-set) and an invocation without it accepts
|
|
53
|
-
the set again.
|
|
54
|
-
|
|
55
|
-
Running a newer release's bootstrap the same way is the only update. When the
|
|
56
|
-
installed container runs another image, the bootstrap pulls this release's
|
|
57
|
-
image and asks the running resident Kit to drain with `kit resident drain`. The
|
|
58
|
-
resident stops acquiring Runs, resumptions, setup, agent CLI work, Google
|
|
59
|
-
sign-ins and scheduled Google captures, while the Sessions it supervises keep
|
|
60
|
-
their renewal, owner stops, answers and reports and a capture already running
|
|
61
|
-
finishes.
|
|
62
|
-
While any Agent Session is still running or unreported, including one whose Run
|
|
63
|
-
was being acquired when the drain began, the bootstrap names it and leaves the
|
|
64
|
-
container as it is; rerun it once they end. Once nothing remains,
|
|
65
|
-
the resident stops renewing, releases its exact lease and stays quiescent, and
|
|
66
|
-
the bootstrap replaces the container with one on this release's image over the
|
|
67
|
-
same Kit home and workspace. The credential, Environment, Kit-home state and
|
|
68
|
-
everything under the workspace are kept; anything installed elsewhere in the old
|
|
69
|
-
container is not. A stopped container is replaced without a drain.
|
|
70
|
-
|
|
71
|
-
If House refuses the installed Kit with `kit_upgrade_required`, including in
|
|
72
|
-
its answer to a command the resident serves, the resident calls House no more,
|
|
73
|
-
neither to release nor to report. It keeps supervising
|
|
74
|
-
every running Session locally and never stops one to make progress. A Session
|
|
75
|
-
that ends keeps its exact binding, stop observation and outcome in Kit home,
|
|
76
|
-
and the drain answers ready once no supervised process runs. The updated Kit
|
|
77
|
-
waits for the old lease to expire, acquires the next one and reports each held
|
|
78
|
-
Session before it acquires any work, restoring no process and inventing no
|
|
79
|
-
outcome.
|
|
80
|
-
|
|
81
|
-
The versioned `bootstrap.json` is the machine-readable command source for this
|
|
82
|
-
release. It is exported from the npm package as `@agentshouse/kit/bootstrap`
|
|
83
|
-
(JSON import) and published byte-for-byte beside `connect-linux.sh` and `connect-windows.ps1`
|
|
84
|
-
in the GitHub release. Its schema version is `1`, its `version` is the Kit package
|
|
85
|
-
version, and `hosts` contains only bootstrap scripts published in that release.
|
|
86
|
-
The package exports only the `./bootstrap` subpath; `./package.json` and source
|
|
87
|
-
subpaths are not exported. For this release, `hosts.linux` and `hosts.windows`
|
|
88
|
-
exist. In this schema example, `<version>` stands for the package's version:
|
|
89
|
-
|
|
90
|
-
```json
|
|
91
|
-
{
|
|
92
|
-
"schemaVersion": 1,
|
|
93
|
-
"version": "<version>",
|
|
94
|
-
"hosts": {
|
|
95
|
-
"linux": {
|
|
96
|
-
"command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v<version>/connect-linux.sh)",
|
|
97
|
-
"asset": {
|
|
98
|
-
"name": "connect-linux.sh",
|
|
99
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v<version>/connect-linux.sh"
|
|
100
|
-
},
|
|
101
|
-
"workspace": { "argument": "--workspace", "quoting": "posix" }
|
|
102
|
-
},
|
|
103
|
-
"windows": {
|
|
104
|
-
"command": "& ([scriptblock]::Create((irm https://github.com/agentshouse/kit/releases/download/v<version>/connect-windows.ps1)))",
|
|
105
|
-
"asset": {
|
|
106
|
-
"name": "connect-windows.ps1",
|
|
107
|
-
"url": "https://github.com/agentshouse/kit/releases/download/v<version>/connect-windows.ps1"
|
|
108
|
-
},
|
|
109
|
-
"workspace": { "argument": "--workspace", "quoting": "powershell" }
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
`command` is complete for the default workspace. To show a selected absolute
|
|
116
|
-
workspace, append the declared `workspace.argument` and the path as one POSIX
|
|
117
|
-
shell-quoted argument. For example, `/home/user/My Agents' House` becomes
|
|
118
|
-
`--workspace '/home/user/My Agents'\'' House'`. A `powershell` workspace is one
|
|
119
|
-
single-quoted PowerShell string with each `'` doubled: `C:\Users\Alice\Alice's Room`
|
|
120
|
-
becomes `--workspace 'C:\Users\Alice\Alice''s Room'`. Quote the entire path before
|
|
121
|
-
adding it to a copyable shell command; never interpolate it as shell syntax.
|
|
122
|
-
`connect-linux.sh` validates an absolute workspace root and encodes it as one Docker
|
|
123
|
-
`--mount` CSV source field, including commas and double quotes in the name.
|
|
124
|
-
`connect-windows.ps1` refuses a workspace root containing a comma.
|
|
125
|
-
The package manifest contains no image digest: the release workflow
|
|
126
|
-
fills the immutable image digest into the published script after the package
|
|
127
|
-
has been built.
|
|
128
|
-
|
|
129
|
-
The bootstrap does not install native Kit on an ordinary computer. The native
|
|
130
|
-
package remains for managed Nodes and an explicitly selected eligible Kit host.
|
|
131
|
-
|
|
132
|
-
The same bootstrap is the host's entry point for the existing `kit` commands:
|
|
133
|
-
append `kit` and the command to the connect command above, as in
|
|
134
|
-
`bash <(curl -fsSL …/connect-linux.sh) kit apply` or
|
|
135
|
-
`bash <(curl -fsSL …/connect-linux.sh) kit telegram connect`. It forwards the
|
|
136
|
-
command and its arguments unchanged into the installed container through the
|
|
137
|
-
host's Docker client and ends with the command's own output and exit status.
|
|
138
|
-
Standard input stays attached and a terminal stays a terminal, so a hidden
|
|
139
|
-
prompt such as the Telegram bot token is read inside the container and never
|
|
140
|
-
becomes an argument or printed output. With the output redirected, the
|
|
141
|
-
command's output and errors stay separate and the terminal stops echoing typed
|
|
142
|
-
input until the command ends. A running container runs the command beside its
|
|
143
|
-
resident Kit; a stopped one runs it in a one-off container over the same Kit
|
|
144
|
-
home and workspace and stays stopped. `kit login` takes the bootstrap's own
|
|
145
|
-
login route, a one-off container on the host network so the host browser can
|
|
146
|
-
return to its loopback listener, or `--manual` for the URL and one-time code.
|
|
147
|
-
Forwarding installs no host-native Kit and gives the container no Docker socket
|
|
148
|
-
or further mount; it refuses when no Kit is installed or the installed one
|
|
149
|
-
differs from this bootstrap's.
|
|
150
|
-
|
|
151
|
-
The owner's working copy push is forwarded the same way: run
|
|
152
|
-
`bash <(curl -fsSL …/connect-linux.sh) house git push [commit] --owner` from a
|
|
153
|
-
directory beneath the workspace root, such as a Room copy under
|
|
154
|
-
`working-copies/rooms/`. The bootstrap runs the container's `house` in the
|
|
155
|
-
matching directory beneath `/agents/house`, through the resident's lease when
|
|
156
|
-
the container runs, and refuses a directory outside the workspace before
|
|
157
|
-
anything reaches House.
|
|
158
|
-
|
|
159
|
-
## Connect a macOS computer
|
|
160
|
-
|
|
161
|
-
On a Mac with Apple silicon or an Intel chip, run the same
|
|
162
|
-
`bash <(curl -fsSL …/connect-linux.sh)` command as
|
|
163
|
-
[a Linux computer](#connect-a-linux-computer). It runs in macOS's own
|
|
164
|
-
`bash` and reads the chip from the hardware, so it selects `arm64` or `amd64`
|
|
165
|
-
correctly even from a terminal running under Rosetta.
|
|
166
|
-
|
|
167
|
-
The bootstrap first establishes Docker Desktop from what it observes. A Docker
|
|
168
|
-
Desktop that already answers is used as it is. Another Docker engine that
|
|
169
|
-
answers, such as Colima's or OrbStack's, is refused before Kit home exists,
|
|
170
|
-
because `docker info` does not report it as Docker Desktop. Without
|
|
171
|
-
`/Applications/Docker.app` it follows Docker's
|
|
172
|
-
[command-line installation](https://docs.docker.com/desktop/setup/install/mac-install/):
|
|
173
|
-
it downloads `Docker.dmg` for the chip from `desktop.docker.com`, attaches it
|
|
174
|
-
through `sudo hdiutil`, runs its `install --user=<account>` through `sudo`, and
|
|
175
|
-
detaches it. `sudo` asks for the password as it ordinarily does, and the
|
|
176
|
-
installer does not accept Docker's Subscription Service Agreement for you. The
|
|
177
|
-
bootstrap then opens Docker Desktop, including one a restart left stopped, and
|
|
178
|
-
waits up to five minutes for its Linux containers to answer while you accept
|
|
179
|
-
its terms or finish what its window asks. If Docker Desktop is still not ready,
|
|
180
|
-
or the download or the installer fails, the bootstrap names the cause and changes
|
|
181
|
-
nothing further; running it again continues from that state. Docker Desktop
|
|
182
|
-
built for the other chip is refused. The bootstrap reaches `docker` in
|
|
183
|
-
Docker Desktop's app bundle when no terminal has added it to `PATH` yet.
|
|
184
|
-
|
|
185
|
-
Everything after that is the Linux journey above, with `open` as the host
|
|
186
|
-
browser opener. With an HTTPS House, login's container publishes its one
|
|
187
|
-
callback port on the Mac's `127.0.0.1`, so House Login returns from the Mac's
|
|
188
|
-
browser, and no Kit container uses the host network. A loopback House needs
|
|
189
|
-
Docker Desktop's host networking enabled. A `--workspace` root must be a
|
|
190
|
-
directory Docker Desktop shares, such as one under `/Users`.
|
|
191
|
-
|
|
192
|
-
## Connect a Windows computer
|
|
193
|
-
|
|
194
|
-
On Windows 11 `amd64`, run the published, versioned bootstrap once in
|
|
195
|
-
PowerShell:
|
|
19
|
+
Windows 11 (`amd64`), in PowerShell:
|
|
196
20
|
|
|
197
21
|
```powershell
|
|
198
|
-
& ([scriptblock]::Create((irm https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
It runs in the Windows PowerShell 5.1 that Windows ships and needs no change to
|
|
202
|
-
its execution policy. It reads the edition, build and processor from Windows
|
|
203
|
-
itself and refuses Windows Server, Windows 10, a Windows 11 build older than
|
|
204
|
-
23H2 (build 22631), which [Docker Desktop requires](https://docs.docker.com/desktop/setup/install/windows-install/),
|
|
205
|
-
and Windows on ARM, each with its cause. There is no native Windows Kit and no
|
|
206
|
-
Windows container.
|
|
207
|
-
|
|
208
|
-
The bootstrap then establishes Docker Desktop and its WSL 2 engine from what it
|
|
209
|
-
observes. A Docker Desktop that already answers with Linux containers on its
|
|
210
|
-
WSL 2 engine is used as it is. Otherwise, when `wsl --version` does not answer,
|
|
211
|
-
it runs Microsoft's [`wsl --install --no-distribution`](https://learn.microsoft.com/windows/wsl/basic-commands#install),
|
|
212
|
-
which asks you to approve administrator rights in the ordinary Windows prompt,
|
|
213
|
-
and stops until you restart Windows; a WSL older than the 2.1.5 Docker Desktop
|
|
214
|
-
requires is brought up to date with `wsl --update` the same way. Without
|
|
215
|
-
Docker Desktop in `%ProgramFiles%\Docker\Docker` or
|
|
216
|
-
`%LOCALAPPDATA%\Programs\DockerDesktop`, it downloads Docker Desktop's
|
|
217
|
-
installer from `desktop.docker.com` and runs its
|
|
218
|
-
[per-user installation](https://docs.docker.com/desktop/setup/install/windows-install/#install-from-the-command-line),
|
|
219
|
-
`install --user --quiet`, which needs no administrator rights and does not
|
|
220
|
-
accept Docker's Subscription Service Agreement for you. It starts Docker
|
|
221
|
-
Desktop, including one a restart left stopped, and waits up to five minutes for
|
|
222
|
-
it to answer while you accept its terms or finish what its window asks.
|
|
223
|
-
Declined administrator rights, a pending restart, a failed download or
|
|
224
|
-
installer, or a Docker Desktop that is still not ready each stop the bootstrap
|
|
225
|
-
with their cause and change nothing further; running the same command again
|
|
226
|
-
continues from that state. Docker Desktop's Windows containers, another Docker
|
|
227
|
-
engine or Docker Desktop's Hyper-V engine are refused.
|
|
228
|
-
|
|
229
|
-
Everything after that is [the Linux journey](#connect-a-linux-computer):
|
|
230
|
-
the same immutable `linux/amd64` image, owner-only `%USERPROFILE%\.house-kit`
|
|
231
|
-
and `%USERPROFILE%\AgentsHouse` or one `--workspace` root such as
|
|
232
|
-
`--workspace 'D:\Agents'`, mounted as the Kit home and `/agents/house`,
|
|
233
|
-
identity reuse, `unless-stopped`, and House Login in the host browser. As on a
|
|
234
|
-
Mac, login's container publishes one callback port on `127.0.0.1` for an HTTPS
|
|
235
|
-
House, and a loopback House needs Docker Desktop's host networking. Kit home and
|
|
236
|
-
the workspace are restricted to your Windows account alone. A path with a comma
|
|
237
|
-
is refused, because this bootstrap cannot pass one to a Docker mount from
|
|
238
|
-
Windows PowerShell. The containers run as the image's own user, since Windows
|
|
239
|
-
has no Linux user id to map. Run it from the 64-bit Windows PowerShell; from an
|
|
240
|
-
administrator PowerShell it still makes your own account the owner of both
|
|
241
|
-
directories. Append `kit` and a command, or run `house git push … --owner`
|
|
242
|
-
from beneath the workspace, to forward it as on Linux, with each argument
|
|
243
|
-
passed unchanged; with the output redirected, a hidden prompt still echoes
|
|
244
|
-
what you type, so leave the output on the terminal when a command asks for a
|
|
245
|
-
token. Each run and forwarded command leaves its exit status in
|
|
246
|
-
`$LASTEXITCODE` and never closes the PowerShell window.
|
|
247
|
-
|
|
248
|
-
This recipe is authored from Microsoft's and Docker's documentation and checked
|
|
249
|
-
off Windows: the Windows PowerShell 5.1 syntax check and the release packaging
|
|
250
|
-
tests in this repository. No Windows computer has run it yet;
|
|
251
|
-
the one real Windows 11 installation is owed at the final MVP candidate.
|
|
252
|
-
|
|
253
|
-
## Building the package
|
|
254
|
-
|
|
255
|
-
The repository checkout is the development source. Build the package that can
|
|
256
|
-
be published:
|
|
257
|
-
|
|
258
|
-
```sh
|
|
259
|
-
npm run build
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
The command writes the publishable package to `dist/package`. It reads no
|
|
263
|
-
environment and ships no provider OAuth client: each Google account signs in
|
|
264
|
-
with the User's own client, as [the Google preset](#the-google-preset)
|
|
265
|
-
describes.
|
|
266
|
-
|
|
267
|
-
Runtime dependencies stay minimal and exactly pinned in `package.json` and
|
|
268
|
-
`package-lock.json`: House's own `@agentshouse/mdmodel`, `yaml`, which reads
|
|
269
|
-
House's text answers, and the Agent Client Protocol SDK
|
|
270
|
-
`@agentclientprotocol/sdk` with its peer `zod`, which is this Kit's one protocol
|
|
271
|
-
client for the compiled adapter contracts, and `ws` for the authenticated Kit
|
|
272
|
-
control stream. The SDK's approval
|
|
273
|
-
receipt is [`docs/agents/acp-sdk-dependency-approval.json`](docs/agents/acp-sdk-dependency-approval.json),
|
|
274
|
-
which the User wrote once this adoption's proof had passed. The adapter
|
|
275
|
-
packages themselves are Environment prerequisites, not Kit dependencies, and
|
|
276
|
-
carry no record.
|
|
277
|
-
|
|
278
|
-
The bootstrap invokes bare `kit login`, which connects to `https://agents.house`;
|
|
279
|
-
`--house <origin>` names another House. Login records the House in Kit home
|
|
280
|
-
beside the credential. Every House-bound command talks to that recorded House,
|
|
281
|
-
and every command but login refuses `--house` as an unknown argument. The first login names its new
|
|
282
|
-
Environment after the host computer and stores its Environment id in Kit home;
|
|
283
|
-
the panel renames it. An explicit later login reconnects that retained id, and
|
|
284
|
-
`--environment <uuid>` reconnects the Environment it names instead, whether or
|
|
285
|
-
not Kit home records one, and Kit home then records that id. Reconnecting issues the next credential for the same Environment
|
|
286
|
-
and ends the previous one, including one `envs/revoke` ended; House refuses an
|
|
287
|
-
Environment it no longer holds with its cause and never creates a replacement.
|
|
288
|
-
A malformed id or an answer naming a different Environment is refused before
|
|
289
|
-
installation.
|
|
290
|
-
|
|
291
|
-
Use `--manual` when the browser cannot reach the Kit's loopback listener. The
|
|
292
|
-
command stores the issued Kit User credential in `$HOUSE_KIT_HOME/credential`
|
|
293
|
-
or `~/.house-kit/credential` with owner-only permissions, replacing the previous
|
|
294
|
-
file in one rename and leaving configuration, checkpoints, source state and
|
|
295
|
-
Agent relationships untouched. Reconnecting installs authority only: it starts
|
|
296
|
-
no Run and resumes no work, and the next `kit worker run` reconciles this
|
|
297
|
-
installation's bound sessions under its ordinary control rules.
|
|
298
|
-
|
|
299
|
-
If House never answers the exchange, the command refuses with
|
|
300
|
-
`kit_credential_not_installed` and no credential is written; the remedy is a
|
|
301
|
-
fresh owner-authorized `kit login` for the same Environment. The credential is
|
|
302
|
-
never printed by Kit.
|
|
303
|
-
|
|
304
|
-
## Configuration
|
|
305
|
-
|
|
306
|
-
`$HOUSE_KIT_HOME/kit.json` is the one declarative configuration file. Use
|
|
307
|
-
`--config <path>` to name it elsewhere.
|
|
308
|
-
|
|
309
|
-
```json
|
|
310
|
-
{
|
|
311
|
-
"availability": "continuous",
|
|
312
|
-
"execution_authority": "allow",
|
|
313
|
-
"migrators": [],
|
|
314
|
-
"instances": [
|
|
315
|
-
{
|
|
316
|
-
"id": "acme",
|
|
317
|
-
"preset": "custom",
|
|
318
|
-
"services": [
|
|
319
|
-
{
|
|
320
|
-
"service": "notes",
|
|
321
|
-
"source_ref": "src_acme_notes",
|
|
322
|
-
"rooms": ["work-room"],
|
|
323
|
-
"backfill": { "days": 30 },
|
|
324
|
-
"connector": {
|
|
325
|
-
"command": ["/usr/local/bin/acme-export", "--ndjson"],
|
|
326
|
-
"timeout_ms": 120000,
|
|
327
|
-
"environment": { "ACME_REGION": "eu" },
|
|
328
|
-
"secrets": {
|
|
329
|
-
"ACME_TOKEN": { "environment": "ACME_TOKEN_SOURCE" },
|
|
330
|
-
"ACME_KEY": { "file": "/run/secrets/acme-key" }
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
]
|
|
335
|
-
}
|
|
336
|
-
],
|
|
337
|
-
"clients": [
|
|
338
|
-
{
|
|
339
|
-
"kind": "claude-code",
|
|
340
|
-
"settings": "/home/ada/.claude/settings.json"
|
|
341
|
-
}
|
|
342
|
-
],
|
|
343
|
-
"host": null,
|
|
344
|
-
"skills": true
|
|
345
|
-
}
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
This file is the whole desired state, so every section it declares is
|
|
349
|
-
reconciled and every section it omits is refused rather than assumed. The
|
|
350
|
-
agent CLI list is not local configuration: House owns it for this Environment,
|
|
351
|
-
and every controlled pass reads, installs, updates, signs in and reports exactly
|
|
352
|
-
the kinds House names. House also owns every Agent, its Profile and its route,
|
|
353
|
-
so this file declares neither Agents nor agent CLIs. The Kit runs whatever Agent
|
|
354
|
-
House assigns on one of House's selected kinds and refuses any other kind. The required
|
|
355
|
-
top-level `execution_authority` is this Environment's one answer to every agent
|
|
356
|
-
permission request, `"allow"` or `"refuse"`; the Agents sharing the Environment
|
|
357
|
-
share it. The
|
|
358
|
-
required top-level `availability` declares this Environment's availability
|
|
359
|
-
mode: `"intermittent"` for a laptop or other normally offline placement,
|
|
360
|
-
`"continuous"` for a server expected to stay online. Omitting it or declaring
|
|
361
|
-
any other value refuses as `kit_configuration_invalid`; `kit apply` sends the
|
|
362
|
-
declared mode with the rest of the desired state. An
|
|
363
|
-
instance declares one or more services, and under the `custom` preset each
|
|
364
|
-
service owns the command that produces it. The Kit reads only `custom` instances
|
|
365
|
-
and passes over any other entry of `instances`.
|
|
366
|
-
|
|
367
|
-
`host` declares the Linux host this installation measures for
|
|
368
|
-
[host capacity reporting](#host-capacity). `null` declares that this Kit reports
|
|
369
|
-
no capacity; the section is required like the others, so a host is measured only
|
|
370
|
-
where the Environment owner says so.
|
|
371
|
-
|
|
372
|
-
`skills` is this Environment's choice of [the Skill set](#the-skill-set):
|
|
373
|
-
`true` accepts it and `false` declines it. A configuration that omits it
|
|
374
|
-
accepts the set. The bootstrap writes it on every invocation that is not a
|
|
375
|
-
forwarded command.
|
|
376
|
-
|
|
377
|
-
`clients` is the local Agent client catalogue and never leaves the installation.
|
|
378
|
-
Each entry names one client whose lifecycle hook configuration this Kit
|
|
379
|
-
reconciles: `kind` is `claude-code`, the one client this Kit installs a hook
|
|
380
|
-
into, and `settings` is the absolute path of that client's own settings
|
|
381
|
-
document. The section is required like the others and declares `[]` when this
|
|
382
|
-
Environment runs no such client. An unknown `kind`, a relative path, and two
|
|
383
|
-
entries naming the same document each refuse as `kit_configuration_invalid`.
|
|
384
|
-
|
|
385
|
-
A secret is never written into this file. `secrets` names either an
|
|
386
|
-
environment variable the Kit reads at run time or a mounted file it reads from
|
|
387
|
-
disk; the resolved value reaches the connector's environment and nothing else.
|
|
388
|
-
|
|
389
|
-
`backfill` is chosen once and honored on the first capture: `{ "days": 30 }`
|
|
390
|
-
never delivers anything observed earlier, `{ "count": 500 }` bounds the first
|
|
391
|
-
capture to 500 items and then continues from its cursor unbounded, and
|
|
392
|
-
`{ "all": true }` takes every history the connector offers. The boundary is
|
|
393
|
-
fixed when the service first captures, not each time a capture is attempted, so
|
|
394
|
-
a run that fails and is repeated days later still starts where it would have.
|
|
395
|
-
|
|
396
|
-
`timeout_ms` may shorten the connector's 120000 ms bound but never raise it. The
|
|
397
|
-
Kit holds this installation's Environment control lease across the connector run
|
|
398
|
-
and the batch it produces, and the lease has to outlive both.
|
|
399
|
-
|
|
400
|
-
Apply the declared source state to House:
|
|
401
|
-
|
|
402
|
-
```sh
|
|
403
|
-
kit apply
|
|
22
|
+
& ([scriptblock]::Create((irm https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.38/connect-windows.ps1)))
|
|
404
23
|
```
|
|
405
24
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
House answers `kit apply` with its current SessionStart hook: one
|
|
413
|
-
`{id, version, event, instruction}` document whose `instruction` tells the Agent
|
|
414
|
-
which Pulse and continuation reads to perform itself. The document is House's
|
|
415
|
-
own text under a stable `id`, versioned with the Kit contract version, and it
|
|
416
|
-
carries no Room content, open item, count, checkpoint, or identity.
|
|
417
|
-
|
|
418
|
-
`kit apply` writes that instruction to
|
|
419
|
-
`$HOUSE_KIT_HOME/state/hooks/<hook id>.txt` and installs one `SessionStart`
|
|
420
|
-
command hook printing it in every declared client's settings document. The
|
|
421
|
-
installed hook is that read and nothing else: it calls no House operation, marks
|
|
422
|
-
nothing seen, embeds no Room material, and infers no chat Session, so the Agent
|
|
423
|
-
decides what to read from the guidance it receives.
|
|
424
|
-
|
|
425
|
-
`state/hooks.json` records exactly which settings documents this installation
|
|
426
|
-
has written and the exact command it installed in each, so ownership is that
|
|
427
|
-
record and the command it names rather than any resemblance to one. An entry
|
|
428
|
-
whose command merely mentions the instruction is another owner's and is left
|
|
429
|
-
alone. `clients` is the desired state of that record: applying the same served
|
|
430
|
-
document again leaves each settings document byte-identical, dropping a client
|
|
431
|
-
from the catalogue removes this installation's entry from the document it named,
|
|
432
|
-
and an empty catalogue removes every entry, the record, and `state/hooks/`
|
|
433
|
-
without touching a hook, group, or setting the installation does not own.
|
|
25
|
+
The command sets up Docker if it is missing, creates `~/.house-kit` (Kit home)
|
|
26
|
+
and `~/AgentsHouse` (your workspace), signs this computer in to House in your
|
|
27
|
+
browser, and starts Kit in a container that comes back after a restart. Running
|
|
28
|
+
it again is safe: it continues where it stopped. Running a newer release's
|
|
29
|
+
command is the update.
|
|
434
30
|
|
|
435
|
-
|
|
436
|
-
declared and previously recorded document is canonicalized, checked, and parsed
|
|
437
|
-
before House is contacted at all - including the `hooks` object and the
|
|
438
|
-
`SessionStart` array this Kit edits, so a document shaped differently refuses as
|
|
439
|
-
`kit_client_settings_unreadable` without changing anything remotely. The counters
|
|
440
|
-
House answers with are validated before the first local change. Each write then
|
|
441
|
-
re-resolves the whole declared path and re-checks the document it prepared,
|
|
442
|
-
refusing as `kit_client_settings_refused` when any part of the path, or the
|
|
443
|
-
document itself, was replaced meanwhile - a parent directory turned into a link
|
|
444
|
-
after preparation receives no write at all. A failed write or a cancellation
|
|
445
|
-
part-way restores every document already written to its previous bytes, removes
|
|
446
|
-
one this installation created, and names as `kit_client_settings_unrestored` any
|
|
447
|
-
it could not restore.
|
|
448
|
-
The previous instruction stays readable until the last document is written and
|
|
449
|
-
only then is it pruned, so a client left holding an older command still reads an
|
|
450
|
-
instruction that is there.
|
|
31
|
+
Options:
|
|
451
32
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
or - for a document already in the record - that no longer resolves to itself,
|
|
457
|
-
rather than writing through a link that replaced it. An existing document keeps
|
|
458
|
-
its file mode, one the client does not have yet is created owner-only, and one
|
|
459
|
-
the Kit cannot parse refuses as `kit_client_settings_unreadable` rather than
|
|
460
|
-
being replaced. Preparation reads before the Environment control lease is taken;
|
|
461
|
-
every write happens under that renewed lease with the apply it belongs to.
|
|
33
|
+
- `--workspace /absolute/path` uses another workspace directory.
|
|
34
|
+
- `--manual` signs in with a URL and a one-time code instead of the browser.
|
|
35
|
+
- `--no-skills` declines the Skill set; running without it accepts it again.
|
|
36
|
+
- `--house <origin>` connects to another House.
|
|
462
37
|
|
|
463
|
-
|
|
464
|
-
receives the same guidance in the server instructions and the tool descriptions,
|
|
465
|
-
and an installation whose catalogue is empty neither installs a hook nor needs
|
|
466
|
-
one served. A declared client is the opposite: an absent or malformed document
|
|
467
|
-
refuses as `kit_hook_rejected` instead of leaving whatever was installed before.
|
|
38
|
+
## Run a Kit command
|
|
468
39
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
House holds this Environment's Google Sources, and its owner sets them in the
|
|
472
|
-
App. Each entry names an account name the owner chooses, one service -
|
|
473
|
-
`gmail`, `calendar` or `drive` - the Rooms it goes to, and what its first
|
|
474
|
-
capture takes: `{ "new": true }` for new activity only, `{ "days": n }`,
|
|
475
|
-
`{ "items": n }` or `{ "all": true }`. Neither the configuration file nor any
|
|
476
|
-
command on this computer declares one.
|
|
477
|
-
|
|
478
|
-
Each service captures a fixed corpus under its own read-only scope: Gmail every
|
|
479
|
-
message but Spam and Trash, Calendar the account's primary calendar, and Drive
|
|
480
|
-
the files the account owns.
|
|
481
|
-
|
|
482
|
-
The resident reads the list from `/kit/sources/desired` each time its Kit
|
|
483
|
-
control stream connects and each time House signals `sources` on it, then
|
|
484
|
-
reconciles:
|
|
485
|
-
|
|
486
|
-
- a new entry gets its local state and is captured once its account is signed
|
|
487
|
-
in for its service;
|
|
488
|
-
- a changed entry's Rooms take effect on its next capture;
|
|
489
|
-
- an entry House no longer lists stops, and its checkpoint, health, gaps,
|
|
490
|
-
dropped items and unsaved originals are deleted;
|
|
491
|
-
- an account left with no entry has each of its grants revoked at Google's
|
|
492
|
-
revocation endpoint and its credentials, its OAuth client included, deleted.
|
|
493
|
-
Google's revocation ends every grant the Google user gave the client's
|
|
494
|
-
project, so when another listed account is signed in to the same Google user,
|
|
495
|
-
the Kit deletes the local credentials and revokes nothing.
|
|
496
|
-
|
|
497
|
-
It then reports every entry to `/kit/sources/report`, and reports an entry
|
|
498
|
-
again after each sign-in and each capture: `signed_out` while its
|
|
499
|
-
account holds no grant for its service, `failed` with its cause code when its
|
|
500
|
-
last capture failed, and `ready` otherwise, each with the account's signed-in
|
|
501
|
-
email once the Kit knows it.
|
|
502
|
-
|
|
503
|
-
The Kit ships no Google OAuth client. Each account signs in with the User's
|
|
504
|
-
own: an OAuth client of type Desktop app in the User's Google Cloud project,
|
|
505
|
-
with the service's API enabled there. An entry whose account holds no grant for
|
|
506
|
-
its service opens a login renewal for integration `google`, instance the
|
|
507
|
-
account name and service the entry's service, and presents its ceremony on
|
|
508
|
-
House's owner-authenticated page, so the owner's browser never has to reach
|
|
509
|
-
this Environment. When the account has no client yet, the page first collects
|
|
510
|
-
the client id and secret. It then links to Google's consent for that service's
|
|
511
|
-
scope only; Google sends the browser to a `http://127.0.0.1:<port>/` address
|
|
512
|
-
that nothing answers, and the owner pastes that whole address into the page.
|
|
513
|
-
The Kit accepts it only when it carries the state its own consent issued,
|
|
514
|
-
exchanges the code with its PKCE verifier and the account's client, and stores
|
|
515
|
-
the grant beside the client in `state/google/credentials/<account>.json`. The
|
|
516
|
-
collected values pass through House's live relay only; neither they nor any
|
|
517
|
-
token enter a report, a log or anything House stores. Adding a
|
|
518
|
-
service to a signed-in account asks only for that service's consent, and each
|
|
519
|
-
service keeps its own grant. An address that answers another consent, or a code
|
|
520
|
-
Google refuses to exchange, stores nothing; a declined consent or a ceremony
|
|
521
|
-
that outlives its ten-minute bound cancels the sign-in, and the Kit tries again
|
|
522
|
-
at the next reconciliation. A capture that finds the grant revoked removes it,
|
|
523
|
-
reports the entry `signed_out` and opens a new sign-in at once.
|
|
524
|
-
|
|
525
|
-
The resident captures each signed-in entry every 15 minutes, with no command.
|
|
526
|
-
An occurrence never overlaps its entry's previous one, and one missed while the
|
|
527
|
-
resident was stopped is not replayed: the next resumes from the checkpoint. The
|
|
528
|
-
first capture fixes the initial choice's boundary, so `new` captures nothing
|
|
529
|
-
that existed then and everything that arrives after.
|
|
40
|
+
Append `kit` and the command to the connect command above:
|
|
530
41
|
|
|
531
42
|
```sh
|
|
532
|
-
|
|
533
|
-
kit source capture work gmail # one occurrence of one entry now
|
|
534
|
-
kit source status work
|
|
43
|
+
bash <(curl -fsSL …/connect-linux.sh) kit apply
|
|
535
44
|
```
|
|
536
45
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
as the account. While the resident runs, it performs the requested occurrence
|
|
540
|
-
itself, after any occurrence of that entry already running.
|
|
541
|
-
|
|
542
|
-
A failed occurrence records its cause. A Google 403 for an API disabled in the
|
|
543
|
-
client's project fails as `api_disabled`, and `kit source status` names the API
|
|
544
|
-
to enable; a revoked scope (`google_scope_revoked`), an expired or revoked grant
|
|
545
|
-
(`google_authority_revoked`) and a provider failure
|
|
546
|
-
(`google_provider_unavailable`) keep their own codes.
|
|
46
|
+
The command runs inside the installed container and ends with its own output
|
|
47
|
+
and exit status.
|
|
547
48
|
|
|
548
|
-
##
|
|
549
|
-
|
|
550
|
-
House requests one setup when it creates a managed Environment and again when
|
|
551
|
-
its owner asks it to renew what the Environment holds. The Kit acts on the
|
|
552
|
-
request it acquires, never on its own:
|
|
49
|
+
## Sign in again
|
|
553
50
|
|
|
554
51
|
```sh
|
|
555
|
-
kit
|
|
556
|
-
```
|
|
557
|
-
|
|
558
|
-
`kit worker run` performs the same step at the start of a pass, before it
|
|
559
|
-
hands out any work, so a managed Environment needs no terminal; the resident
|
|
560
|
-
Kit performs it each time its control stream connects and on each `runs`
|
|
561
|
-
signal, which House also sends for every setup request. The walk first
|
|
562
|
-
prepares the absolute `/agents/house` base, then visits every declared agent
|
|
563
|
-
CLI and then every connector instance, in configuration order:
|
|
564
|
-
|
|
565
|
-
- an agent CLI is installed or kept at its served release as
|
|
566
|
-
[the declared agent CLIs](#the-declared-agent-clis) describe; a kind no
|
|
567
|
-
release could be installed for needs attention as `install_failed`;
|
|
568
|
-
- a module whose own status check already answers signed in reports
|
|
569
|
-
`authenticated` to `/kit/logins/report`, which closes any renewal episode an
|
|
570
|
-
earlier attempt left open, and is `skipped`;
|
|
571
|
-
- otherwise the Kit runs the module's guided login. It reports the login to
|
|
572
|
-
House as `renewal_required` for `/kit/logins/report`, which opens or returns
|
|
573
|
-
one renewal episode, and presents the provider's steps on House's
|
|
574
|
-
owner-authenticated page by holding `/kit/secret-input/<episode>` until the
|
|
575
|
-
owner submits. Collected values go only to the waiting sign-in process; the
|
|
576
|
-
Kit sends none of them, and none of the sign-in's output, to any other act. A
|
|
577
|
-
sign-in whose status check then answers signed in reports `authenticated`
|
|
578
|
-
and is `ready`;
|
|
579
|
-
- a module without a guided route in this Kit, or one House does not advertise
|
|
580
|
-
(`kit_login_renewal_unsupported`), needs attention as `login_unavailable`
|
|
581
|
-
and opens no ceremony; a sign-in that exits without authenticating needs
|
|
582
|
-
attention as `login_failed`, and one that outlives its ten-minute bound as
|
|
583
|
-
`login_cancelled`. Either leaves its renewal episode open.
|
|
584
|
-
|
|
585
|
-
One module's failure never stops the walk. The Kit reports what it now holds
|
|
586
|
-
for every declared agent CLI and then the whole summary, one entry per module
|
|
587
|
-
with its `category`, `name`, `status` and `reason`, to `/kit/setup/report`
|
|
588
|
-
under the request it served, and releases its control lease. A cancelled
|
|
589
|
-
command reports no partial summary and releases its lease once the walk has
|
|
590
|
-
stopped.
|
|
591
|
-
|
|
592
|
-
Each compiled adapter contract carries the guided route its provider's own
|
|
593
|
-
headless sign-in offers, read from that command's output:
|
|
594
|
-
|
|
595
|
-
- `codex-acp` runs Codex's device sign-in. The page links to the provider's
|
|
596
|
-
device page and shows the one-time code the command printed; the owner signs
|
|
597
|
-
in with ChatGPT there and enters the code, and the command completes on its
|
|
598
|
-
own. Nothing is written back to it.
|
|
599
|
-
- `claude-agent-acp` runs Claude Code's sign-in. The page links to the authorize
|
|
600
|
-
page the command printed and collects the authentication code Claude shows
|
|
601
|
-
afterwards, which the Kit writes to that command alone. Claude Code answers a
|
|
602
|
-
code it refuses by asking for another, so the Kit ends that sign-in as soon as
|
|
603
|
-
the command prints `Invalid code.` and it needs attention as `login_failed`
|
|
604
|
-
rather than waiting out its ten-minute bound.
|
|
605
|
-
- `grok-build` runs Grok Build's device sign-in. The page links to the Grok
|
|
606
|
-
device URL and shows the anti-phishing one-time code printed by the command;
|
|
607
|
-
authorization completes in the browser and no secret is written back to the
|
|
608
|
-
process.
|
|
609
|
-
|
|
610
|
-
House offers only a route its provider evidence qualifies; one it does not
|
|
611
|
-
advertise stays `login_unavailable`.
|
|
612
|
-
|
|
613
|
-
## The custom connector
|
|
614
|
-
|
|
615
|
-
Each custom service names one User-owned command. The Kit invokes it through an
|
|
616
|
-
argument vector, never a shell, with an environment holding only `PATH`, the
|
|
617
|
-
declared `environment` entries, and the resolved secrets. No House credential
|
|
618
|
-
and no Kit User credential is ever placed there. The request carries no service
|
|
619
|
-
name because the command is the service.
|
|
620
|
-
|
|
621
|
-
Stdin carries one JSON request line:
|
|
622
|
-
|
|
623
|
-
```json
|
|
624
|
-
{ "contract": "2026-08-22", "instance": "acme", "cursor": null, "limit": 100 }
|
|
625
|
-
```
|
|
626
|
-
|
|
627
|
-
`cursor` is the opaque cursor the connector last returned, or `null` on the
|
|
628
|
-
first capture and after a reset. Stdout carries NDJSON: zero or more `item`
|
|
629
|
-
lines and at most one terminal `cursor` line.
|
|
630
|
-
|
|
631
|
-
```
|
|
632
|
-
{"type":"item","namespace":"acme/notes","native_object_id":"note-91","native_version":"7","observed_at":"2026-08-20T09:15:00.000Z","body":"the note","author":"ada","url":"https://acme.example/notes/91","attachments":[{"url":"https://acme.example/files/1","name":"plan.pdf"}]}
|
|
633
|
-
{"type":"cursor","cursor":"page-2"}
|
|
634
|
-
```
|
|
635
|
-
|
|
636
|
-
`namespace`, `native_object_id`, `observed_at`, and `body` are required;
|
|
637
|
-
`native_version`, `author`, `url`, and `attachments` are optional. There is no
|
|
638
|
-
metadata bag, no executable field, and no embedded credential. A malformed line
|
|
639
|
-
anywhere rejects the whole run: nothing is delivered and no checkpoint moves. A
|
|
640
|
-
non-zero exit status, a timeout, or output beyond the bound rejects the run the
|
|
641
|
-
same way.
|
|
642
|
-
|
|
643
|
-
An attachment names exactly one target. `url` is a verbatim reference House never
|
|
644
|
-
fetches. `file` names one work file the connector already acquired into this
|
|
645
|
-
Environment and declares its exact version:
|
|
646
|
-
|
|
647
|
-
```
|
|
648
|
-
{"file":{"path":"/srv/acme/files/plan.pdf","bytes":48213,"sha256":"<hex>","media_type":"application/pdf"},"name":"plan.pdf"}
|
|
52
|
+
bash <(curl -fsSL …/connect-linux.sh) kit login
|
|
649
53
|
```
|
|
650
54
|
|
|
651
|
-
`
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
original on the Capture item, under an accepted version derived from the
|
|
655
|
-
delivery key, the attachment position and the descriptor, so reoffering the same
|
|
656
|
-
item always names the same version and a different descriptor under the same
|
|
657
|
-
delivery key is House's delivery-key conflict.
|
|
55
|
+
`kit login` reconnects this computer's Environment with a fresh credential.
|
|
56
|
+
Add `--manual` when the browser cannot reach this computer, or
|
|
57
|
+
`--environment <uuid>` to reconnect a different Environment of yours.
|
|
658
58
|
|
|
659
|
-
|
|
660
|
-
declared namespace, the native object id, and the native version when present.
|
|
661
|
-
An object whose content changes without its native version changing keeps its
|
|
662
|
-
key and is refused by House as a delivery-key conflict rather than silently
|
|
663
|
-
overwriting the earlier Record.
|
|
59
|
+
## Guides
|
|
664
60
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
## The Kit control stream
|
|
673
|
-
|
|
674
|
-
The resident Kit holds one `/kit/stream` WebSocket for its Environment,
|
|
675
|
-
authenticated with its Environment control lease and the Kit contract header,
|
|
676
|
-
for as long as it holds that lease; `kit worker run` holds one for the length
|
|
677
|
-
of its pass to receive its owner's commands, and `kit telegram serve` holds one
|
|
678
|
-
while it serves the gateway under a lease of its own. The Kit sends no
|
|
679
|
-
subscribe frame and accepts every frame House sends. What it writes there is
|
|
680
|
-
the live view of the turns it runs, as draft and plan frames the
|
|
681
|
-
[Worker runtime](#the-worker-runtime) describes, and a `gap` frame when the
|
|
682
|
-
[Telegram gateway](#the-telegram-gateway) needs a turn's whole view again.
|
|
683
|
-
|
|
684
|
-
Each time the socket connects, the resident acquires everything a signal could
|
|
685
|
-
have named, because a signal House sent while the socket was down is lost: its
|
|
686
|
-
Runs and resumptions with their setup and conversation files, the owner's
|
|
687
|
-
pending commands, the agent CLI list, the saved working copy choice and its
|
|
688
|
-
Google Sources. After that it acquires only what House names. A
|
|
689
|
-
`work_available` frame for `agents`, `working_copy` or `sources` acquires that
|
|
690
|
-
subject alone. `runs` takes a resumption or else a waiting Run, readying the
|
|
691
|
-
agent CLIs, a requested setup and conversation files first. `commands` hands
|
|
692
|
-
the supervised Session its stops, interrupts and answers and takes a
|
|
693
|
-
resumption the same way, without acquiring a Run. An `entries` frame carries
|
|
694
|
-
the Change entries of a Room the lease admits, which the resident applies to
|
|
695
|
-
that Room's [working copy](#working-copies) in the order the frames arrive. A
|
|
696
|
-
pass that took a Run acquires again once that Run ends, and a pass that found
|
|
697
|
-
nothing waits for the next signal. A Run outcome House neither took nor finally
|
|
698
|
-
refused is reported again every 30 seconds. The Kit has no idle acquisition
|
|
699
|
-
cycle, no command poll and no Session heartbeat: a Session is observed when its
|
|
700
|
-
process starts and when its supervisor sees it stop.
|
|
701
|
-
|
|
702
|
-
A socket that closes or never opens reconnects after a delay drawn between half
|
|
703
|
-
and all of one second, doubled for each consecutive failure up to 30 seconds.
|
|
704
|
-
An `authority_changed` close reconnects after a delay drawn between 0 and 2
|
|
705
|
-
seconds, with no error backoff. A `replaced` close, which means a newer
|
|
706
|
-
connection holds the Environment, ends the resident with `kit_stream_replaced`,
|
|
707
|
-
and an upgrade House refuses with `kit_upgrade_required` stops every House call
|
|
708
|
-
as [above](#connect-a-linux-computer) describes; neither is retried.
|
|
709
|
-
|
|
710
|
-
## The Worker runtime
|
|
711
|
-
|
|
712
|
-
```sh
|
|
713
|
-
kit worker run
|
|
714
|
-
```
|
|
715
|
-
|
|
716
|
-
Worker-runtime readiness validates the tmux this Kit declares support for,
|
|
717
|
-
reopens any already-bound live WorkerSession, brings every declared agent CLI
|
|
718
|
-
to its current release, starts each installed declared kind's command adapter
|
|
719
|
-
long enough to verify its compiled contract and read the options its own
|
|
720
|
-
session offers, reports what it holds to House, and only then acquires another
|
|
721
|
-
WorkerRun. This Kit compiles three MVP adapter kinds: `codex-acp` contract `2` selects
|
|
722
|
-
`@agentclientprotocol/codex-acp` `1.12.0` or a later `1.x` release, and
|
|
723
|
-
`claude-agent-acp` contract `1` selects
|
|
724
|
-
`@agentclientprotocol/claude-agent-acp` `0.79.0` or a later `0.x` release, while
|
|
725
|
-
`grok-build` contract `1` selects `@xai-official/grok` `1.0.41` or a later
|
|
726
|
-
`1.x` release and launches its native supervised ACP stdio mode. Each
|
|
727
|
-
contract names the adapter package, the minimum release the `initialize` answer
|
|
728
|
-
must reach, the executable it installs, the install recipe's own settings, the
|
|
729
|
-
CLI's headless sign-in command and the command that answers whether it is
|
|
730
|
-
signed in, the Agent Client Protocol version it speaks, its capability
|
|
731
|
-
descriptor - the admitted `elicitation/create` field kinds, whether the adapter
|
|
732
|
-
admits a declined form, and the stop-reason table of that adapter - whether the
|
|
733
|
-
adapter loads a session, and the identifiers of the session configuration
|
|
734
|
-
options the route's model and effort reach, or `null` for a field whose
|
|
735
|
-
adapter offers no such option. An acquired route carrying any other descriptor for
|
|
736
|
-
that kind refuses as `kit_adapter_contract_mismatch` before any spawn
|
|
737
|
-
commitment.
|
|
738
|
-
The `codex-acp` and `grok-build` contracts pin session loading and the
|
|
739
|
-
`claude-agent-acp` contract does not. The same readiness asserts that the initialize exchange Kit
|
|
740
|
-
wrote carried its elicitation declaration, because without it Claude Code
|
|
741
|
-
disables its question tool and Codex answers nothing.
|
|
742
|
-
An adapter whose running release is older than the contract's minimum, of
|
|
743
|
-
another major, mismatched, or unresponsive refuses `kit worker run` within 10
|
|
744
|
-
seconds per installed kind and names what it found, after the pass has reported
|
|
745
|
-
what it holds, and every other Kit command keeps working.
|
|
746
|
-
|
|
747
|
-
Once `initialize` passes, the Kit opens one Agent Client Protocol session with
|
|
748
|
-
`session/new` in its own home directory and no MCP server, reads that session's
|
|
749
|
-
configuration options, and stops the adapter; no prompt is sent. For each field
|
|
750
|
-
the contract names, it takes the `select` option carrying that identifier and
|
|
751
|
-
reports every value it offers with the display name the adapter gave it,
|
|
752
|
-
flattening option groups and keeping nothing else. A field whose identifier the
|
|
753
|
-
contract declares `null`, or whose option the session does not expose as a
|
|
754
|
-
`select`, reports none. An adapter that refuses the session with the Agent
|
|
755
|
-
Client Protocol's `auth_required` error, as `codex-acp` does before it is signed
|
|
756
|
-
in, reports no options at all and its kind stays served. A session that fails
|
|
757
|
-
for any other reason, does not open within 30 seconds, answers without a session
|
|
758
|
-
id, or offers options the Kit cannot read as values and names reports no options
|
|
759
|
-
either and refuses `kit worker run` as `kit_adapter_contract_mismatch`, like any
|
|
760
|
-
other contract failure. The Kit keeps no catalogue of these values and no copy
|
|
761
|
-
of them between passes: every report reads them from the adapter again.
|
|
762
|
-
|
|
763
|
-
## Agent working directories
|
|
764
|
-
|
|
765
|
-
Connecting an installation creates `/agents/house`, the absolute base every
|
|
766
|
-
default Agent working directory lives under. Connecting again keeps whatever is
|
|
767
|
-
already under it. An installation that cannot create that directory refuses
|
|
768
|
-
with `kit_launch_base_unprepared` and names it, rather than substituting a
|
|
769
|
-
home-relative location: the Environment owner creates it once and gives the
|
|
770
|
-
installation write access to it.
|
|
771
|
-
|
|
772
|
-
Every acquired WorkerRun carries its Agent's absolute working directory and its
|
|
773
|
-
Agent id in its execution plan, and Kit launches the supervised session and its
|
|
774
|
-
adapter in exactly that directory. Kit recognises the Agent's own default
|
|
775
|
-
directory by exact equality with `/agents/house/<agent id>`, never by a
|
|
776
|
-
directory's mere position beneath the base: a sibling directory one level
|
|
777
|
-
inside `/agents/house` is somebody else's default or an owner's own workspace,
|
|
778
|
-
never this Agent's. Every path that is not this exact equality, one deeper
|
|
779
|
-
inside `/agents/house` included, must
|
|
780
|
-
already exist in this Environment as a directory the installation can enter;
|
|
781
|
-
Kit never creates it.
|
|
782
|
-
|
|
783
|
-
When the Agent's own default directory is absent, Kit creates it empty; two
|
|
784
|
-
concurrent first launches create the same directory. A present default
|
|
785
|
-
directory launches exactly as it is, and a deleted one is created empty again
|
|
786
|
-
on its next launch. Kit writes no file into any working directory.
|
|
787
|
-
|
|
788
|
-
A relative, missing, inaccessible, or non-directory working directory refuses
|
|
789
|
-
with `kit_launch_directory_unavailable` before any provider process starts. Kit falls back to no other location - not the
|
|
790
|
-
base, not `$HOUSE_KIT_HOME`, and not its own current directory - and neither
|
|
791
|
-
connecting an installation nor launching a session moves, copies, overwrites,
|
|
792
|
-
or removes anything a working directory holds.
|
|
793
|
-
|
|
794
|
-
## The Skill set
|
|
795
|
-
|
|
796
|
-
The Skill set is every skill the `mattpocock/skills` plugin manifest names at
|
|
797
|
-
one pinned commit but `setup-matt-pocock-skills`, `triage` and `wizard`. Each
|
|
798
|
-
is vendored under `src/skill-set/<name>/` with every file it ships, the
|
|
799
|
-
upstream MIT `LICENSE` and a provenance header in `SKILL.md` naming the commit
|
|
800
|
-
and upstream path. Nothing else is edited but two lines: a
|
|
801
|
-
`disable-model-invocation: true` frontmatter line in `SKILL.md` and an
|
|
802
|
-
`allow_implicit_invocation: false` line in `agents/openai.yaml` are removed, so
|
|
803
|
-
an agent reaches every skill without being told its name.
|
|
804
|
-
|
|
805
|
-
Kit installs the Skill set once for the Environment, for every Agent whatever
|
|
806
|
-
its working directory, under the home it launches agent CLIs with: each skill
|
|
807
|
-
as the directory `$HOME/.agents/skills/<name>`, which Codex and Grok Build
|
|
808
|
-
read, and a relative symbolic link `$HOME/.claude/skills/<name>` to it, which
|
|
809
|
-
Claude Code reads; when `$HOME/.claude/skills` already resolves to
|
|
810
|
-
`$HOME/.agents/skills`, Claude Code reads the directory there and Kit makes no
|
|
811
|
-
link. In a container that home is Kit home, so the set survives
|
|
812
|
-
container recreation; on a native host it is the host user's home.
|
|
813
|
-
|
|
814
|
-
`$HOUSE_KIT_HOME/state/skill-set.json` records the names Kit installed and the
|
|
815
|
-
Kit version that installed them, and Kit owns exactly those names: the
|
|
816
|
-
directory and the link of each. At start and on every resident pass, when
|
|
817
|
-
the set is accepted and the record names another version or none, Kit deletes
|
|
818
|
-
every recorded name, installs the current Skill set and rewrites the record. A
|
|
819
|
-
name whose directory or link exists without being recorded is the owner's: Kit
|
|
820
|
-
leaves it untouched and installs nothing under it. Kit never compares, merges
|
|
821
|
-
or restores a file, so an edit to an installed skill is lost at the next
|
|
822
|
-
release and a copy under another name is kept. When `skills` is `false`, Kit
|
|
823
|
-
deletes every recorded name and the record and installs nothing.
|
|
824
|
-
|
|
825
|
-
After Kit first installs the Skill set, the first Session it launches whose
|
|
826
|
-
Profile ceiling lets it write the owner's How-we-work document
|
|
827
|
-
`/private/library/how-we-work.md` creates it, when it is absent, with the text
|
|
828
|
-
Kit ships in `src/how-we-work.md`, over the Session's own bridge before its
|
|
829
|
-
first prompt is built. Kit then notes in `$HOUSE_KIT_HOME/state/how-we-work`,
|
|
830
|
-
apart from the record, that the document exists, and a present document is
|
|
831
|
-
noted the same way without being touched. Nothing ever creates it again: a
|
|
832
|
-
release, a declined Skill set and a later acceptance leave the note, so an
|
|
833
|
-
owner's edit stands and a deleted document stays deleted. A Session whose
|
|
834
|
-
ceiling cannot write the document launches without it and leaves it for the
|
|
835
|
-
next one; any other refused creation refuses the attempt as
|
|
836
|
-
`kit_how_we_work_unwritable` with House's answer before a prompt is sent. A
|
|
837
|
-
Skill set Kit cannot install or remove refuses the pass as
|
|
838
|
-
`kit_skill_set_unavailable` and names the path.
|
|
839
|
-
|
|
840
|
-
## The declared agent CLIs
|
|
841
|
-
|
|
842
|
-
The Environment owner installs no adapter binary by hand. `kit agents update`
|
|
843
|
-
and every `kit worker run` bring each declared kind to the latest release its
|
|
844
|
-
contract serves - no older than the contract's minimum and inside that
|
|
845
|
-
minimum's major - under
|
|
846
|
-
`$HOUSE_KIT_HOME/agents/<kind>/releases/<release>/`, never touching what the
|
|
847
|
-
owner installed globally, and an acquired route's command is that installed
|
|
848
|
-
executable rather than a path anyone writes into `kit.json`.
|
|
849
|
-
The resident Kit also listens for an `agents` availability pointer on its
|
|
850
|
-
control stream. It reads the whole wanted list from House over HTTPS, removes
|
|
851
|
-
Kit-owned CLI directories that are no longer wanted, installs newly wanted
|
|
852
|
-
kinds, and reports its held state immediately, including while another Agent
|
|
853
|
-
run remains active. A newly wanted kind that needs provider authorization uses
|
|
854
|
-
the existing guided House login ceremony; Kit reports readiness again when it
|
|
855
|
-
ends. A reconnect reads the list again, so a pointer missed during a stream
|
|
856
|
-
outage changes no authority.
|
|
857
|
-
Removal waits for an active worker pass or any unresolved live WorkerSession
|
|
858
|
-
binding to stop using a CLI directory; the Kit keeps reporting that kind until
|
|
859
|
-
it removes the directory.
|
|
860
|
-
|
|
861
|
-
Every candidate release is proved before it serves: the Kit installs it beside
|
|
862
|
-
the current release, runs the contract's own conformance check - the protocol's
|
|
863
|
-
`initialize` against the minimum release, the protocol version and the
|
|
864
|
-
capability descriptor the contract requires, and no provider-billed turn - and
|
|
865
|
-
switches to it only on green. On red it keeps the release it already has,
|
|
866
|
-
removes the candidate, and holds that kind with the refused release named in
|
|
867
|
-
its report until a newer candidate appears. Non-major releases advance this way
|
|
868
|
-
on their own; a major release waits for a contract revision. The candidate
|
|
869
|
-
check runs at most every six hours per kind, and `kit agents update` runs it at
|
|
870
|
-
once. A kind whose release lookup or installation fails keeps the release it
|
|
871
|
-
already has, or none, and is named with its failure; the other kinds go on,
|
|
872
|
-
`kit agents update` then exits non-zero, and a worker pass verifies and serves only the kinds with an installed release, so
|
|
873
|
-
one CLI the Kit could not install leaves only that kind's work unavailable.
|
|
874
|
-
|
|
875
|
-
The install recipe carries what the CLI needs beside its binary: for
|
|
876
|
-
`codex-acp` the Kit enables user-input questions in Codex's own configuration
|
|
877
|
-
document, because a Codex that disables them raises no `elicitation/create` and
|
|
878
|
-
answers nothing, and the Kit adds that setting only where the owner declared
|
|
879
|
-
none.
|
|
880
|
-
|
|
881
|
-
`kit agents sign-in <kind>` runs that CLI's own headless sign-in command in the
|
|
882
|
-
terminal it was started from, so the owner only completes it; House opens that
|
|
883
|
-
terminal and runs nothing itself. Both commands hold one control lease, release
|
|
884
|
-
it once they are done, and report to House afterwards what
|
|
885
|
-
this Environment now holds: per declared kind its compiled contract version,
|
|
886
|
-
the installed release or none, whether the contract's native readiness evidence
|
|
887
|
-
shows a signed-in provider, the candidate release a red conformance check refused, and the model and
|
|
888
|
-
effort options the installed adapter's own session offered, or `null` when no
|
|
889
|
-
release is installed or its session did not open. Every report, the worker pass's
|
|
890
|
-
and `kit setup`'s included, reads those options the same way and goes to
|
|
891
|
-
`POST /kit/agents/report` with one entry per declared kind:
|
|
892
|
-
|
|
893
|
-
```json
|
|
894
|
-
{"agents": [{"kind": "claude-agent-acp", "contract_version": "1", "release": "0.81.2",
|
|
895
|
-
"signed_in": true, "hold": null,
|
|
896
|
-
"options": {"model": [{"value": "haiku", "name": "Haiku 4.5"}], "effort": []}}]}
|
|
897
|
-
```
|
|
898
|
-
|
|
899
|
-
House knows about an agent CLI only
|
|
900
|
-
what this Kit reported: it probes no binary, version or login of its own, and
|
|
901
|
-
it refuses an Agent, an Agent Session and Agent-runtime acquisition for a kind
|
|
902
|
-
this Environment does not declare or this Kit has not reported installed and
|
|
903
|
-
signed in.
|
|
904
|
-
|
|
905
|
-
This installation owns one private tmux server namespace derived from
|
|
906
|
-
`$HOUSE_KIT_HOME`, so House never joins, lists, or destroys an ordinary tmux
|
|
907
|
-
session. Each acquired WorkerRun becomes one WorkerSession in one opaque exact
|
|
908
|
-
tmux session of that namespace, created from a Kit-owned configuration that
|
|
909
|
-
disables terminal retention. tmux owns the terminal lifecycle and nothing else:
|
|
910
|
-
the pane, its scrollback, and anything the adapter writes to its own stderr are
|
|
911
|
-
never read, stored, parsed, or answered, and no key is ever injected into a
|
|
912
|
-
pane.
|
|
913
|
-
|
|
914
|
-
Inside that session the Kit starts the launcher for the acquired route's
|
|
915
|
-
declared command adapter and hands it, over a Kit-private local channel, the
|
|
916
|
-
canonical request House gave the Kit and the adapter argument vector. Routine assignments carry their Routine Request.
|
|
917
|
-
Direct conversation assignments carry the sealed turn and its ordered messages,
|
|
918
|
-
and the Kit seals the reply itself on its own ingress.
|
|
919
|
-
An Agent resolves exactly one Execution route, so the acquisition envelope's
|
|
920
|
-
execution plan carries one `route` object and this Kit selects, orders, and
|
|
921
|
-
hands off nothing. The Kit launches that route as House admitted it and keeps
|
|
922
|
-
no local copy of it: an acquired route naming a kind `agents` does not declare
|
|
923
|
-
refuses as `kit_agent_undeclared`, and one whose kind has no installed release
|
|
924
|
-
as `kit_agent_not_installed`. The route names `adapter.kind` and
|
|
925
|
-
`adapter.contract_version`. The installed release of that kind plus the route's
|
|
926
|
-
`arguments` are the exact argument vector, so no route names a binary path.
|
|
927
|
-
The route's `model` and its `effort` or `null` are House-visible route facts in
|
|
928
|
-
the CLI's own vocabulary, which House stores as given and never validates; the
|
|
929
|
-
Kit hands them to the adapter at session start as its contract names, and a
|
|
930
|
-
value the CLI refuses fails the attempt with the CLI's own cause as
|
|
931
|
-
`kit_adapter_failed`. A route whose `effort` is `null` sets no effort, so the
|
|
932
|
-
CLI's own default effort applies and a model that offers no effort option
|
|
933
|
-
starts.
|
|
934
|
-
The adapter inherits only `PATH` and `HOME`, so the provider's own local
|
|
935
|
-
authorization stays in the Environment where its CLI already keeps it. The Kit
|
|
936
|
-
adds the Session's `house` CLI in front of that `PATH` and names the Session's
|
|
937
|
-
bridge socket and nonce in `HOUSE_KIT_BRIDGE` and `HOUSE_KIT_BRIDGE_NONCE`.
|
|
938
|
-
|
|
939
|
-
The launcher speaks the Agent Client Protocol through the protocol's official
|
|
940
|
-
TypeScript SDK, `@agentclientprotocol/sdk`, as this Kit's one protocol client;
|
|
941
|
-
the Kit frames no protocol JSON-RPC of its own. It opens the session with `session/new`
|
|
942
|
-
in the Environment's working directory with no MCP server, or reloads the
|
|
943
|
-
retained one with `session/load`, then sets the
|
|
944
|
-
route's model, and its effort when the route names one, through the protocol's
|
|
945
|
-
`session/set_config_option` with the option identifiers the adapter contract
|
|
946
|
-
names: `model` and
|
|
947
|
-
`reasoning_effort` for `codex-acp`, `model` and `effort` for
|
|
948
|
-
`claude-agent-acp`. Neither pinned adapter reads a model or effort flag of its
|
|
949
|
-
own. It carries the turn with one `session/prompt`, and maps that
|
|
950
|
-
prompt's stop reason through the contract's own table to one of four turn
|
|
951
|
-
outcomes: completed, interrupted, refused by the provider, or exhausted. A stop
|
|
952
|
-
reason the contract does not name, including a vendor-specific one a different
|
|
953
|
-
contract names, fails the turn as `kit_adapter_contract_mismatch` rather than
|
|
954
|
-
being guessed. After committing the Run, and again on every resumption, the
|
|
955
|
-
Kit reads what the Session was started for from House's
|
|
956
|
-
`/kit/runs/<run>/request` with the Session credential: the Routine Request with
|
|
957
|
-
its input, references, requester and the pinned revision's instruction, or the
|
|
958
|
-
sealed conversation turn. It sends that as the first prompt block, followed by
|
|
959
|
-
current House guidance fetched through the Run connection, so the Agent calls
|
|
960
|
-
nothing to learn it. An unreadable request or guidance refuses the turn.
|
|
961
|
-
A session opened with `session/new` gets one more text block ahead of those,
|
|
962
|
-
the same for every adapter kind: the line
|
|
963
|
-
`The agents.house house CLI is available: run house --help.`, the base
|
|
964
|
-
instructions of the Execution attempt plan House acquired, and the current text
|
|
965
|
-
of the owner's How-we-work document `/private/library/how-we-work.md`, which the
|
|
966
|
-
launcher reads with the `shell` tool over the Session's own bridge, running
|
|
967
|
-
the continuation each cut reply's `output_cut` line prints until a reply comes
|
|
968
|
-
back uncut. A document that is absent, or that the Session's ceiling cannot
|
|
969
|
-
read, at the first read adds nothing, and so do empty base instructions. Any other failed read
|
|
970
|
-
refuses the attempt as `kit_how_we_work_unreadable` with the bridge's cause
|
|
971
|
-
before a prompt is sent. A session reloaded with `session/load` already holds
|
|
972
|
-
the block and gets none. It forwards only adapter-classified User-visible assistant
|
|
973
|
-
text back over the local channel, and leaves reasoning, tool traces, and
|
|
974
|
-
terminal output where they are. The supervised process's own House calls travel
|
|
975
|
-
that same channel through the `house` CLI. The Kit holds the WorkerRunToken, replaces it in place when
|
|
976
|
-
it nears expiry without the child noticing, attaches a bridge-generated UUIDv7
|
|
977
|
-
to every Session mutation it forwards, and never places a House
|
|
978
|
-
credential in any child environment.
|
|
979
|
-
|
|
980
|
-
The Kit is the single source of assistant text: it observes the adapter, streams
|
|
981
|
-
what it has seen so far, and seals the turn itself. No Agent is asked to report
|
|
982
|
-
text a deterministic observer already sees, and no agent-facing tool admits final
|
|
983
|
-
text.
|
|
984
|
-
|
|
985
|
-
While the adapter generates, the supervisor writes the whole User-visible
|
|
986
|
-
assistant text of the turn so far as `draft` frames on the Environment's
|
|
987
|
-
[control stream](#the-kit-control-stream); it calls no draft route. Only
|
|
988
|
-
adapter-classified assistant message text enters that stream; reasoning, tool
|
|
989
|
-
calls, and terminal output never do. The Kit maps the structure it can read in
|
|
990
|
-
that text - paragraphs, headings, fenced code, ordered and unordered lists, pipe
|
|
991
|
-
tables, and `$$` formulas - into House's blocks, and leaves anything it did not
|
|
992
|
-
parse as the paragraph it observed. It invents no structure, renders nothing,
|
|
993
|
-
and holds back or refuses no draft for its length: a frame carries every block
|
|
994
|
-
of the text however long it is.
|
|
995
|
-
|
|
996
|
-
A frame names the turn it belongs to, as House answered the Session's `started`
|
|
997
|
-
observation with the turn's `conversation_id` and `turn_id`, together with the
|
|
998
|
-
Execution attempt as `attempt_id`. It carries a `sequence` that grows by one
|
|
999
|
-
with each paced view of the turn, and replaces the turn's view from the block
|
|
1000
|
-
index `from` onward with the `blocks` it carries, so a frame for text that only
|
|
1001
|
-
grew carries the blocks from the first one that changed, and a view that
|
|
1002
|
-
changed no block is a frame that replaces nothing. The launcher numbers the
|
|
1003
|
-
views, so a Kit that restarts mid-turn continues the turn's sequence. The Kit
|
|
1004
|
-
coalesces and paces that stream rather than mirroring every chunk: it writes
|
|
1005
|
-
the first observed view at once and then at most one view in each
|
|
1006
|
-
500-millisecond pacing window, the latest one, even when the adapter completes
|
|
1007
|
-
a message inside a window. A frame written while the socket is down is lost,
|
|
1008
|
-
so the first frame after each reconnect, and after any frame the socket could not carry, replaces the view
|
|
1009
|
-
from block 0. A Session House names no conversation turn for gets no frames. A
|
|
1010
|
-
draft persists nowhere - not in this Kit's local state, not as House history -
|
|
1011
|
-
is never retried, and can neither delay nor prove turn completion.
|
|
1012
|
-
|
|
1013
|
-
A `plan` session update is one snapshot of the agent's steps. The Kit writes
|
|
1014
|
-
each snapshot as a `plan` frame paced by the same rule, carrying every step's
|
|
1015
|
-
`label`, `priority` and `status` exactly as the adapter gave them, its own
|
|
1016
|
-
`sequence`, and the step index `from` it replaces the plan onward. The act that
|
|
1017
|
-
ends the turn carries the last snapshot the Kit saw as its final `plan`: the
|
|
1018
|
-
seal, the observed stop, the Run outcome and an `interrupted` interrupt outcome
|
|
1019
|
-
each name the provider turn and every step's `label` and `status`, however many
|
|
1020
|
-
steps and however long their text, and a turn that never planned carries none.
|
|
1021
|
-
There is no per-snapshot plan route.
|
|
1022
|
-
|
|
1023
|
-
When the adapter completes the turn, the Kit seals it with one
|
|
1024
|
-
`sessions/<session>/assistant-messages` call carrying the same blocks, the final
|
|
1025
|
-
plan, and a UUIDv7 `operation_id`. The exact request - that identity, those
|
|
1026
|
-
blocks and that plan - is written into the WorkerSession binding before the
|
|
1027
|
-
call leaves, so a recovered call returns the outcome House recorded and never
|
|
1028
|
-
seals a turn twice, and House's answer names the turn it closed and the
|
|
1029
|
-
delivery it queued. An answer the Kit never received leaves the Run unsettled
|
|
1030
|
-
with the request retained: the next `kit worker run` replays exactly that
|
|
1031
|
-
operation, those blocks and that plan before settling anything, rather than
|
|
1032
|
-
reporting a failure it cannot prove. A refusal House did decide is the Run's
|
|
1033
|
-
reported failure.
|
|
1034
|
-
|
|
1035
|
-
That seal is the one durable record of the turn, and only a seal House accepted
|
|
1036
|
-
completes a turn: the Kit reports whole-turn completion to House after the seal
|
|
1037
|
-
succeeds and never otherwise. A turn whose adapter produced no User-visible text
|
|
1038
|
-
seals nothing and claims no completed turn; the Run's own terminal outcome still
|
|
1039
|
-
follows what the adapter reported.
|
|
1040
|
-
|
|
1041
|
-
The Agent reaches House only through the `house` CLI. Its verbs are the Tools
|
|
1042
|
-
House lists to the Session, and its help is House's own: `house --help` prints
|
|
1043
|
-
each listed Tool's name and description, and `house <verb> --help` prints that
|
|
1044
|
-
Tool's description and input schema, all read from House's `tools/list` at the
|
|
1045
|
-
call, so a catalogue change needs no Kit release. `house <verb>
|
|
1046
|
-
'<arguments as one JSON object>'` calls that Tool, or `house <verb> -` with the
|
|
1047
|
-
object on standard input for arguments longer than one command-line argument
|
|
1048
|
-
holds. It prints House's text answer, or a refusal on standard error with a
|
|
1049
|
-
nonzero exit status. The CLI holds
|
|
1050
|
-
no credential: it calls over its Session's bridge, which attaches the Session
|
|
1051
|
-
credential and refuses a caller without that Session's nonce as
|
|
1052
|
-
`kit_bridge_unauthorized`. House network calls use only the selected
|
|
1053
|
-
`2026-07-28` profile. `house git push` is the one local verb House does not list;
|
|
1054
|
-
[working copies](#working-copies) describe it, and `house --help` and
|
|
1055
|
-
`house git push --help` explain it.
|
|
1056
|
-
|
|
1057
|
-
Two verbs name files on the Session's own computer: `upload_attachment` takes
|
|
1058
|
-
one `path`, and `append_record` takes `attachments` as local paths. The CLI
|
|
1059
|
-
resolves each path against its working directory. The bridge reads each regular
|
|
1060
|
-
file, computes its size and SHA-256 and the media type an image's leading bytes
|
|
1061
|
-
show, declares them beside the call, and uploads the exact bytes through the
|
|
1062
|
-
one-use grant House answers before the call completes, so the answer carries the
|
|
1063
|
-
attachment's `at_` reference. `append_record` uploads every file into its Room
|
|
1064
|
-
first and names their references in the Record; a file that cannot be read or
|
|
1065
|
-
saved appends nothing. The bridge reads House's YAML answers to
|
|
1066
|
-
`upload_attachment`. A Room or `/private` upload ends at the byte upload rather
|
|
1067
|
-
than at a House answer, so the bridge prints that saved answer in the same YAML;
|
|
1068
|
-
every other answer passes through as House wrote it. With `conversation: true`
|
|
1069
|
-
the file becomes the owner's conversation original, and the bridge calls again
|
|
1070
|
-
and waits for House's delivery outcome. House derives the presentation from the
|
|
1071
|
-
declared media type: an image is uploaded inline beside a link to the unmodified
|
|
1072
|
-
original, and every other file is offered as that link alone.
|
|
1073
|
-
|
|
1074
|
-
The bridge retains the exact pending House request in memory before transfer.
|
|
1075
|
-
Within the same live Kit and supervised WorkerSession, a lost response or
|
|
1076
|
-
infrastructure failure resends that request with the same identifier. A local
|
|
1077
|
-
socket reconnect delivers the retained outcome to the process.
|
|
1078
|
-
A second deliberate call gets a new identifier; read-only calls carry none.
|
|
1079
|
-
House refusals pass through to the CLI without automatic resend. [R193](https://github.com/agentshouse/core/blob/main/docs/concept/implementation/14-agents-profiles-sessions-and-runs.md#r193)
|
|
1080
|
-
owns recovery authority, lifetime, and refusal semantics.
|
|
1081
|
-
|
|
1082
|
-
The launcher speaks only what the adapter contract fixes for this build. Every
|
|
1083
|
-
session initializes with the same fixed client capabilities: no file-system
|
|
1084
|
-
methods, no terminal methods, and elicitation in form mode and not in URL mode.
|
|
1085
|
-
It offers the adapter no filesystem or terminal surface of its own, so an adapter
|
|
1086
|
-
that needs a decision the Kit cannot carry fails its turn instead of receiving a
|
|
1087
|
-
guessed answer. Execution permission is the one decision the Environment already
|
|
1088
|
-
settled: `kit.json` declares the Environment's `execution_authority` as `allow`
|
|
1089
|
-
or `refuse`, and the launcher answers every adapter permission request with that
|
|
1090
|
-
declared outcome and nothing else. The authority is a local Environment fact - it never reaches
|
|
1091
|
-
House, and no permission request is ever projected to a human.
|
|
1092
|
-
|
|
1093
|
-
A task question is the decision the Environment cannot settle, so the launcher
|
|
1094
|
-
carries it to the owner instead of answering it. The Interaction is the
|
|
1095
|
-
protocol's own form end to end: an adapter's `elicitation/create` request in
|
|
1096
|
-
form mode reaches House's interaction act as its `message` and its
|
|
1097
|
-
`requested_schema`, unchanged in meaning, with the exact correlation of the
|
|
1098
|
-
provider turn and of the adapter request that raised it. House alone judges
|
|
1099
|
-
whether the schema is inside the flat subset it can ask; a form it admits as
|
|
1100
|
-
unsupported, and a URL-mode request the Kit never forwards, cancel the
|
|
1101
|
-
adapter's request with the protocol's `cancel` and ask the owner nothing.
|
|
1102
|
-
|
|
1103
|
-
Nothing of an adapter's native wire crosses: the Kit strips every native
|
|
1104
|
-
`_meta` from the request, the schema, its properties and their options, and
|
|
1105
|
-
gives a property the adapter left untitled its own name as title. Two facts
|
|
1106
|
-
cross as House-namespaced marks instead. The batch carries
|
|
1107
|
-
`_meta["agents.house/secret"]` when the adapter classifies the request as
|
|
1108
|
-
secret in its own meta, as `codex-acp` does with `isSecret`; House records it
|
|
1109
|
-
as unsupported and raises an interrupt rather than a card, so no secret prompt
|
|
1110
|
-
is projected to a human and no secret answer travels back through House. A
|
|
1111
|
-
choice carries `_meta["agents.house/free-text"]` when the adapter's own
|
|
1112
|
-
convention admits the owner's words beside the listed options:
|
|
1113
|
-
`claude-agent-acp` pairs a question with a `_askUserQuestionCustomAnswer`
|
|
1114
|
-
companion field, and `codex-acp` pairs it with a `"None of the above"` option
|
|
1115
|
-
and a `<id>_note` field tagged `user_note`. The companion field and the
|
|
1116
|
-
synthetic option never reach House.
|
|
1117
|
-
|
|
1118
|
-
The owner's answer arrives as the `answer` command carrying the protocol's own
|
|
1119
|
-
response, `accept` with content keyed by property name or `decline` for the
|
|
1120
|
-
whole form, and is applied to that exact pending request; an answer naming
|
|
1121
|
-
another turn or another correlation has no effect, so a stale or cross-Session
|
|
1122
|
-
answer cannot reach a live adapter. A typed answer to a free-text choice goes
|
|
1123
|
-
back through the adapter's own convention: into the companion field for
|
|
1124
|
-
`claude-agent-acp`, and as `"None of the above"` with the typed text in the note
|
|
1125
|
-
field for `codex-acp`. The turn waits on the adapter's own request while the
|
|
1126
|
-
owner decides, and the Kit reports what became of each Interaction, so a
|
|
1127
|
-
Session that ends with one outstanding expires it instead of leaving it live.
|
|
1128
|
-
|
|
1129
|
-
Before new execution, `kit worker run` reconciles House-authorized inbound cache
|
|
1130
|
-
cleanup and materializes every pending conversation file House offers, ten at a
|
|
1131
|
-
time, under `$HOUSE_KIT_HOME/conversation-inbound/<run>/<attachment>/<name>`.
|
|
1132
|
-
House seals due input itself. The Kit uses the existing Environment lease and
|
|
1133
|
-
byte endpoint, limits each file to 32 MiB, and reports materialization after
|
|
1134
|
-
receiving a complete byte stream, validating any reported size, and writing the
|
|
1135
|
-
file. A lost acknowledgement preserves the completed local file for House's
|
|
1136
|
-
idempotent admission and later cleanup. Failed files remain pending; House's
|
|
1137
|
-
all-files-ready gate keeps their turn unprocessed.
|
|
1138
|
-
The provider receives absolute local paths beside the owning messages in its
|
|
1139
|
-
first prompt. The cache persists across
|
|
1140
|
-
turns; only House-authorized terminal Run cleanup removes its Run directory.
|
|
1141
|
-
|
|
1142
|
-
The accepted attachment's retained original is separate from that cache. The Kit
|
|
1143
|
-
records a local note for the attachment before it acknowledges materialization,
|
|
1144
|
-
then saves the exact bytes it received through House's conversation-original
|
|
1145
|
-
door, naming only the attachment; a download that fails reports
|
|
1146
|
-
`source_unavailable` for the same version.
|
|
1147
|
-
|
|
1148
|
-
A save that fails leaves one note in `state/conversation-originals/` naming the
|
|
1149
|
-
attachment, its Run, the local file and the descriptor it declared. Each
|
|
1150
|
-
later `kit worker run` retries it from that file without materializing or
|
|
1151
|
-
acknowledging anything again, and reports `source_unavailable` when
|
|
1152
|
-
the file is gone or changed rather than saving different bytes. When a
|
|
1153
|
-
supervised WorkerSession ends, the same retry runs once more before its Run is
|
|
1154
|
-
reported. The note leaves once
|
|
1155
|
-
House records the save, once House answers that the Run is over, or when House
|
|
1156
|
-
authorizes that Run's cache cleanup; House accepts no save for a Run that has
|
|
1157
|
-
settled, so an unsaved original keeps its failed outcome from then on. The owner
|
|
1158
|
-
reads every saved original through House afterwards, whether or not this
|
|
1159
|
-
Environment still exists. `kit worker run` prints every conversation original
|
|
1160
|
-
outcome it handled, and exits non-zero while any of them is unsaved.
|
|
1161
|
-
|
|
1162
|
-
One `kit worker run` then takes one House-authorized resumption, or otherwise
|
|
1163
|
-
acquires one waiting Run, and supervises exactly that WorkerSession. A resumed
|
|
1164
|
-
conversation loads its retained native provider session through ACP
|
|
1165
|
-
`session/load`, receives the current input House answers for the Run in its
|
|
1166
|
-
first prompt, and executes one turn. Historical output replayed during
|
|
1167
|
-
loading is not emitted as new assistant output. Missing or rejected provider
|
|
1168
|
-
continuation never starts a replacement provider conversation in the same Run.
|
|
1169
|
-
|
|
1170
|
-
Continuation is per adapter. A contract that pins session loading retains the
|
|
1171
|
-
adapter's opaque session handle between turns and resumes through it, as
|
|
1172
|
-
`codex-acp` and `grok-build` do. A contract without it, as `claude-agent-acp`, retains nothing:
|
|
1173
|
-
its turn reports no provider continuation, the Agent Session ends with the
|
|
1174
|
-
reported cause `kit_provider_continuation_unavailable`, and the owner starts the
|
|
1175
|
-
next one.
|
|
1176
|
-
|
|
1177
|
-
Between turns, Kit retains the opaque provider session identifier and the Run's
|
|
1178
|
-
Worker, working directory, and route references in an owner-only local record.
|
|
1179
|
-
The record carries no transcript or Session credential and grants no authority:
|
|
1180
|
-
House must authorize each successor WorkerSession. A reported terminal state and
|
|
1181
|
-
owner stops remove the record. [R199](https://github.com/agentshouse/core/blob/main/docs/concept/implementation/14-agents-profiles-sessions-and-runs.md#r199)
|
|
1182
|
-
owns conversation continuity.
|
|
1183
|
-
|
|
1184
|
-
Launcher exit is the terminal process observation. The Kit closes the local
|
|
1185
|
-
channel, grants the launcher a fixed local exit grace, destroys the exact tmux
|
|
1186
|
-
session if it is still there, and then reports one stopped Session at
|
|
1187
|
-
`POST /kit/sessions/:session/stopped` and the adapter's own continuation facts at
|
|
1188
|
-
`POST /kit/sessions/:session/continuation`, whose body is exactly
|
|
1189
|
-
`{"provider_continuation": <bool>, "provider_capacity_exhausted": <bool>}`. No
|
|
1190
|
-
adapter-supplied reset instant and no handoff availability is sent, and nothing
|
|
1191
|
-
schedules a wake.
|
|
1192
|
-
|
|
1193
|
-
Kit, not the Agent, then reports the terminal state the execution reached at
|
|
1194
|
-
`POST /kit/runs/:run/outcome`. No agent-facing tool posts it, and settling writes
|
|
1195
|
-
no Record, Posting or Capture by itself: whatever the instruction wanted written
|
|
1196
|
-
is written through the ordinary authorized Run mutations first. The body is
|
|
1197
|
-
`{"operation_id": <uuidv7>, "outcome": "completed"}` or
|
|
1198
|
-
`{"operation_id": <uuidv7>, "outcome": "failed", "cause": "<1-200 chars>"}`, and
|
|
1199
|
-
the reported cause is exactly what the adapter gave. A finished turn that needed
|
|
1200
|
-
no further Provider continuation is `completed`. A known execution failure, a
|
|
1201
|
-
provider capacity exhaustion, a positively lost Provider continuation, and a
|
|
1202
|
-
rejected native resume command are each `failed` with their own cause; House
|
|
1203
|
-
notifies the owner and nothing is retried. A confirmed-stopped Session whose
|
|
1204
|
-
Provider continuation remains available reports no terminal state at all and
|
|
1205
|
-
leaves the Run active for an owner-originated resumption.
|
|
1206
|
-
|
|
1207
|
-
The Kit decides that terminal state once, records it beside its stable operation
|
|
1208
|
-
identity before it reports it, and reports it from that record. It decides and
|
|
1209
|
-
records it as soon as the stop is recorded and the final text sealed, before it
|
|
1210
|
-
reports the stopped Session and its continuation facts: a refusal of either of
|
|
1211
|
-
those does not change the terminal state, so the Kit reports that recorded state
|
|
1212
|
-
whatever House answered to them. A Run with a
|
|
1213
|
-
supervised session records it in that session's durable local binding; a Run
|
|
1214
|
-
whose terminal state was decided without one — a resume command this Kit rejects
|
|
1215
|
-
because it retains no Provider continuation for the Run, or an acquired Run it
|
|
1216
|
-
refuses before any provider process starts, which it commits and fails with the
|
|
1217
|
-
refusal as the cause so no Run stays acquired behind it — records it in a
|
|
1218
|
-
durable local settlement note of its own, named by the Run. A lost response is recovered by reporting that
|
|
1219
|
-
identical body under the same operation identity on the next `kit worker run`;
|
|
1220
|
-
House answers the committed settlement verbatim and the Agent is never executed
|
|
1221
|
-
again. A Run House has already settled answers `worker_run_settled`, which the
|
|
1222
|
-
Kit accepts as settled and drops the record for.
|
|
1223
|
-
|
|
1224
|
-
The Kit keeps that record until House answers it. The record stays exactly as it
|
|
1225
|
-
is while House is unavailable, never answers or answers in a form the Kit cannot
|
|
1226
|
-
read, declares its refusal retryable, answers 429, 502 or 503 without declaring
|
|
1227
|
-
otherwise, refuses the Kit contract or the Kit's lease, including a lease
|
|
1228
|
-
renewal the report waited on, refuses the operation identity as issued ahead of
|
|
1229
|
-
its clock, or answers `conversation_file_send_pending` because a conversation
|
|
1230
|
-
file the Run sent is still being delivered. A report whose operation identity
|
|
1231
|
-
House no longer holds, answered `operation_expired` or `position_expired`, keeps
|
|
1232
|
-
the same body under a new operation identity. Either way the Run stays bound to
|
|
1233
|
-
this installation, `kit worker run` names the refusal House answered with, if
|
|
1234
|
-
any, and exits non-zero, and the same decided body is reported again before
|
|
1235
|
-
anything is acquired. Every other refusal is House's final answer for that
|
|
1236
|
-
report, such as `worker_run_not_found` or `worker_run_not_acquired`: the Kit
|
|
1237
|
-
writes `kit: run <run> outcome refused: <code>` to standard error once, drops
|
|
1238
|
-
the record, and acquires as if the report had been taken. For the Run it
|
|
1239
|
-
supervised in that pass, `kit worker run` also names that refusal and exits
|
|
1240
|
-
non-zero. A refused report is never presented to the owner as a delivered one.
|
|
1241
|
-
|
|
1242
|
-
While a WorkerSession is supervised the Kit keeps one durable local binding
|
|
1243
|
-
naming this installation, the WorkerRun, the WorkerSession, the exact tmux
|
|
1244
|
-
session, and the local channel that reaches it. The binding carries no terminal
|
|
1245
|
-
content, no prompt, and no resolved secret, and Session authority is always
|
|
1246
|
-
reissued by House rather than recovered from disk. It is evidence for
|
|
1247
|
-
reconciliation, never permission to resume or recreate work, and nothing keeps
|
|
1248
|
-
it once House has answered for the Run: a settled terminal state, a confirmed
|
|
1249
|
-
owner stop, and a retained Provider continuation each drop it. When the
|
|
1250
|
-
supervised process ends, the binding first records what the Kit observed: the
|
|
1251
|
-
sealed final text, whether the turn completed, the capacity or provider refusal,
|
|
1252
|
-
the execution failure and whether a Provider continuation is retained. The Kit
|
|
1253
|
-
then reports that stop, and a restarted Kit reports the same recorded stop
|
|
1254
|
-
instead of treating the session as lost.
|
|
1255
|
-
|
|
1256
|
-
A restarted `kit worker run` reconciles the records it kept before it acquires
|
|
1257
|
-
anything new. It reports every retained settlement note first, because those
|
|
1258
|
-
Runs have no session left to reconcile, and drops each note House accepts,
|
|
1259
|
-
already settled or finally refused. It next consumes the durable commands House
|
|
1260
|
-
holds for this Environment, so a requested owner stop ends the exact tmux
|
|
1261
|
-
session before any local channel reopens. It then considers only sessions of its
|
|
1262
|
-
own namespace whose binding is complete, and never lists unknown sessions, scans
|
|
1263
|
-
host processes, searches another namespace, or adopts an arbitrary process. A
|
|
1264
|
-
session that is already gone is confirmed gone, reported as one stopped Session
|
|
1265
|
-
with no Provider continuation, and settled as a terminal execution failure. A
|
|
1266
|
-
session that is still running is restored only after House positively replaces
|
|
1267
|
-
the Session authority for the same Run and Session; the Kit then reconnects the
|
|
1268
|
-
typed adapter channel, refreshes the started observation, and supervises that
|
|
1269
|
-
session to settlement instead of acquiring another Run. An explicit House
|
|
1270
|
-
refusal destroys the stale tmux session and exposes no channel. House that
|
|
1271
|
-
cannot be reached leaves the binding and the session exactly as they are,
|
|
1272
|
-
invents no stopped, active, or authorized state, and acquires nothing.
|
|
1273
|
-
|
|
1274
|
-
A restore of House's database starts a new log epoch. Until this Environment
|
|
1275
|
-
has reported in it, House refuses every act that touches a Run, acquisition and
|
|
1276
|
-
lease release included, as `epoch_report_required` naming the epoch. The first
|
|
1277
|
-
such refusal ends whatever the pass was doing. One raised inside a supervised
|
|
1278
|
-
Run's bridge, while it admits or settles a question, reports an interrupt, or
|
|
1279
|
-
replaces Session authority for a forwarded call, never becomes an answer to the
|
|
1280
|
-
Agent: it ends the bridge and the pass at once. The Kit then reports, under the
|
|
1281
|
-
lease it holds and at `POST /kit/sessions/report`, every Run it holds a spawn
|
|
1282
|
-
commitment or a terminal outcome for: each retained binding, settlement note,
|
|
1283
|
-
and Provider continuation, as `{"session": "<run>", "outcome": null}` while its
|
|
1284
|
-
process runs, while it holds only its continuation, or while its outcome is
|
|
1285
|
-
unknown, or with the held outcome it would have reported at
|
|
1286
|
-
`POST /kit/runs/:run/outcome`, `completed` or `failed` with its cause. One
|
|
1287
|
-
report carries at most 256 Runs, so the Kit sends as many as it needs, all
|
|
1288
|
-
before it acquires anything. House settles every reported Run and never offers
|
|
1289
|
-
it again, so once it has answered every report the Kit destroys each reported
|
|
1290
|
-
tmux session still running, drops each reported Run's binding, settlement note,
|
|
1291
|
-
and retained Provider continuation, and runs the pass again from its start. A
|
|
1292
|
-
refused act is never a Run failure, nothing is acquired in the new epoch before
|
|
1293
|
-
the report, and a refusal that follows the report ends the command naming it.
|
|
1294
|
-
|
|
1295
|
-
The launcher inside the supervised session outlives the Kit process that started
|
|
1296
|
-
it. Its local channel reconnects to the same socket with a bounded backoff and
|
|
1297
|
-
announces that its adapter session already exists, so a restored bridge
|
|
1298
|
-
acknowledges the resumption instead of starting a second turn and the supervised
|
|
1299
|
-
process never sees a second request. A call already transferred when the Kit
|
|
1300
|
-
process ends is never automatically replayed by its successor, even when the
|
|
1301
|
-
same local channel reopens. Pending mutation requests and identifiers are not
|
|
1302
|
-
written into the durable binding. Reconciliation must obtain current House
|
|
1303
|
-
authority before the live process can make further calls.
|
|
1304
|
-
|
|
1305
|
-
An owner stop requested through House reaches this Kit as a durable command,
|
|
1306
|
-
both at startup and while a Session is supervised: House signals `commands` on
|
|
1307
|
-
the Kit control stream and the supervisor acquires the pending stops,
|
|
1308
|
-
interrupts and answers at once, so a stop reaches the process within a second.
|
|
1309
|
-
The Kit destroys the exact
|
|
1310
|
-
tmux session, confirms that it is gone, and reports the stopped Session. It
|
|
1311
|
-
reports no continuation for a Run stopped by its owner and never reopens the
|
|
1312
|
-
local channel for that Session.
|
|
1313
|
-
|
|
1314
|
-
```sh
|
|
1315
|
-
kit worker attach # the one supervised session
|
|
1316
|
-
kit worker attach <session> # the exact session
|
|
1317
|
-
```
|
|
1318
|
-
|
|
1319
|
-
Attachment is a read-only diagnostic for the Environment owner. It reaches only
|
|
1320
|
-
the exact tmux session of a live binding in this installation's namespace, gives
|
|
1321
|
-
the Worker no input, makes no House call, causes no lifecycle transition, and
|
|
1322
|
-
leaves the namespace configuration and its disabled terminal retention as they
|
|
1323
|
-
are. Once the Session has settled or its tmux session is gone, attaching is
|
|
1324
|
-
refused and names why.
|
|
1325
|
-
|
|
1326
|
-
## Paired proof
|
|
1327
|
-
|
|
1328
|
-
Paired installed-CLI proof against the merged House producer is a labelled
|
|
1329
|
-
validation build, not an npm release. From Linux Compose:
|
|
1330
|
-
|
|
1331
|
-
```sh
|
|
1332
|
-
docker build -f test/pair/producer.dockerfile -t kit-original-pair:latest .
|
|
1333
|
-
HOUSE_CORE_ROOT=<agents.house checkout> \
|
|
1334
|
-
docker compose -f test/pair/compose.yaml up --abort-on-container-exit --force-recreate producer
|
|
1335
|
-
```
|
|
1336
|
-
|
|
1337
|
-
It boots the real producer against a real PostgreSQL, installs the exact
|
|
1338
|
-
candidate package, and runs `test/pair/*.vitest.ts`: the attachment doors,
|
|
1339
|
-
a Source adapter retaining a declared file into Rooms of two owners, and an
|
|
1340
|
-
installed Worker retaining an incoming attachment through failure, retry, Run
|
|
1341
|
-
settlement and cache disposal. `KIT_PAIR_SPECS`
|
|
1342
|
-
names the specs to run, and `HOUSE_CORE_REVISION` and `KIT_REVISION` are copied
|
|
1343
|
-
into the evidence each spec writes under `tmp/delivery-evidence/`.
|
|
1344
|
-
|
|
1345
|
-
## Capturing
|
|
1346
|
-
|
|
1347
|
-
```sh
|
|
1348
|
-
kit source capture acme # every declared service
|
|
1349
|
-
kit source capture acme notes # one service
|
|
1350
|
-
```
|
|
1351
|
-
|
|
1352
|
-
Each run reads the checkpoint, acquires one bounded page, submits one batch,
|
|
1353
|
-
and advances the checkpoint only once every item in that batch is accepted or
|
|
1354
|
-
an idempotent duplicate at the House door. Every batch names the service's exact
|
|
1355
|
-
Source reference and its complete Room destination set, so House admits all of
|
|
1356
|
-
them or none: one destination the delivering User cannot currently write refuses
|
|
1357
|
-
the whole publication before any content changes. A refused item leaves the
|
|
1358
|
-
checkpoint where it was, so the next run reoffers the whole batch and converges
|
|
1359
|
-
on the door's own deduplication. When House stops a batch early, its answer
|
|
1360
|
-
settles only the prefix it reached, and the unsettled tail is reoffered with the
|
|
1361
|
-
rest. The loop repeats while the connector still returns items and no Room
|
|
1362
|
-
destination is blocked, and every run ends by writing the service's health into
|
|
1363
|
-
this installation's own state. House holds no custom connector's service, route,
|
|
1364
|
-
checkpoint or health, and no Google entry's checkpoint or health.
|
|
1365
|
-
|
|
1366
|
-
A Capture item that declares a `file` attachment answers one Record per Room
|
|
1367
|
-
destination: Rooms of one owner share that owner's Record, and a Room of another
|
|
1368
|
-
owner holds its own Record of the same delivery. The Kit records one retained
|
|
1369
|
-
reference for every destination Room and attachment in
|
|
1370
|
-
`state/<instance>/originals/<service>.json` before it saves anything, then
|
|
1371
|
-
saves that batch's references through House's captured-original door before it
|
|
1372
|
-
asks the connector for the next page: it reads the declared path, and uploads
|
|
1373
|
-
the bytes only when they still match the declared descriptor. References still
|
|
1374
|
-
outstanding from earlier runs are retried once the run's pages are delivered.
|
|
1375
|
-
A missing or changed file reports `source_unavailable` instead, so House never
|
|
1376
|
-
receives a substitute version. House, not the Kit, chooses where each Room
|
|
1377
|
-
owner's copy is kept. Every run prints each reference's save outcome beside its
|
|
1378
|
-
Room and Record and exits non-zero while any stays unsaved; acquisition health
|
|
1379
|
-
stays about acquisition, so a failed save never changes it.
|
|
1380
|
-
|
|
1381
|
-
```sh
|
|
1382
|
-
kit source retry-files acme # every declared service
|
|
1383
|
-
kit source retry-files acme notes # one service
|
|
1384
|
-
```
|
|
1385
|
-
|
|
1386
|
-
`retry-files` saves only the references still outstanding. It runs no connector,
|
|
1387
|
-
submits no Capture, moves no checkpoint, and keeps each reference's Record and
|
|
1388
|
-
accepted version; with nothing outstanding it contacts no House at all. A saved
|
|
1389
|
-
reference, or one whose Posting House no longer holds, leaves the local state. A
|
|
1390
|
-
Room whose write authority is gone answers `room_not_found` and stays outstanding
|
|
1391
|
-
until that authority returns.
|
|
1392
|
-
|
|
1393
|
-
```sh
|
|
1394
|
-
kit source status acme # every declared service
|
|
1395
|
-
kit source status acme notes # one service
|
|
1396
|
-
```
|
|
1397
|
-
|
|
1398
|
-
`kit source status` reads only local state: the current health and its exact
|
|
1399
|
-
category, the stored cursor, every open coverage gap with the recovery reference
|
|
1400
|
-
House returned for it, the items this installation dropped permanently, and the
|
|
1401
|
-
retained references still waiting to be saved.
|
|
1402
|
-
|
|
1403
|
-
Every command that changes or reads installation state takes this
|
|
1404
|
-
installation's exclusive Environment control lease for the length of its run
|
|
1405
|
-
and renews it before it expires, so two Kit commands never work against the
|
|
1406
|
-
same installation at once. A command that ends, is refused, or is interrupted
|
|
1407
|
-
once its work has stopped releases that exact lease so the next command acquires
|
|
1408
|
-
at once. A `kit login` credential is presented only to take that lease.
|
|
1409
|
-
|
|
1410
|
-
While `kit resident` holds that lease, a command runs through it instead: the
|
|
1411
|
-
command presents no credential, the resident confirms its current lease with
|
|
1412
|
-
House and sends the command's House requests under it, an interrupted command
|
|
1413
|
-
cancels its request there, and the lease stays held when the command ends. The
|
|
1414
|
-
resident serves one such command at a time and refuses another with
|
|
1415
|
-
`environment_control_lease_active`, so two commands still never work at once.
|
|
1416
|
-
A resident whose lease House refuses answers the command with that refusal; the
|
|
1417
|
-
command never acquires a lease of its own while a resident serves it. While an
|
|
1418
|
-
update drains, `state/update-drain` refuses every such command, `kit login` and
|
|
1419
|
-
`kit worker run` with `kit_update_draining` before it enrolls, acquires or
|
|
1420
|
-
delegates anything. A command already served keeps the resident's lease until
|
|
1421
|
-
it ends, while the resident refuses its acquisition of new work and its
|
|
1422
|
-
Telegram updates with `kit_update_draining`, which ends a served
|
|
1423
|
-
`kit telegram serve` once each of its activities reports the deliveries and
|
|
1424
|
-
files it already acquired; until it ends, the drain answers that it waits for
|
|
1425
|
-
that command and the bootstrap leaves the container as it is. The bootstrap
|
|
1426
|
-
removes the file when it replaces the container.
|
|
1427
|
-
|
|
1428
|
-
Interrupting the command cancels the running connector without advancing the
|
|
1429
|
-
checkpoint.
|
|
1430
|
-
|
|
1431
|
-
## Repository capture
|
|
1432
|
-
|
|
1433
|
-
Publish one local Git checkout as a Repository Source without giving House any
|
|
1434
|
-
Git credential:
|
|
1435
|
-
|
|
1436
|
-
```sh
|
|
1437
|
-
kit repository capture /srv/acme \
|
|
1438
|
-
--room work-room \
|
|
1439
|
-
--source-handle acme-repository
|
|
1440
|
-
```
|
|
1441
|
-
|
|
1442
|
-
The command runs ordinary `git pull` in the checkout with the local User's Git
|
|
1443
|
-
configuration, credential helper, and SSH agent. After pull succeeds, Kit asks
|
|
1444
|
-
Git for tracked files plus untracked non-ignored files, hashes each selected
|
|
1445
|
-
file, and opens a Publication by posting that manifest - every selected path
|
|
1446
|
-
with its content fingerprint and byte length - to the authenticated Kit door.
|
|
1447
|
-
Git remains the only ignore owner.
|
|
1448
|
-
|
|
1449
|
-
House answers the Publication reference, the paths it needs, and the chunk
|
|
1450
|
-
bound it accepts. Kit delivers only those paths, in House's order, in chunks
|
|
1451
|
-
under that bound, streaming each file from disk at delivery time; it carries no
|
|
1452
|
-
bound, no delta, no counter and no retained archive of its own. House owns
|
|
1453
|
-
every tombstone, orders the Publication, and completes it in the request that
|
|
1454
|
-
leaves no needed path outstanding.
|
|
1455
|
-
|
|
1456
|
-
A file whose bytes disagree with the manifest House holds is refused as that
|
|
1457
|
-
item and the rest of the chunk stands. When a delivery round ends with refused
|
|
1458
|
-
items and the Publication is still active, Kit opens a new manifest of the
|
|
1459
|
-
current checkout and delivers again, at most three manifests per run. An
|
|
1460
|
-
interrupted run is never resumed: the next run opens a new manifest and House
|
|
1461
|
-
answers with what is still needed. The command reports the Publication
|
|
1462
|
-
reference, the paths delivered, the items refused and the ending.
|
|
1463
|
-
|
|
1464
|
-
## Working copies
|
|
1465
|
-
|
|
1466
|
-
The Environment owner chooses in House which Rooms this Environment keeps
|
|
1467
|
-
locally, and whether it keeps `/private`. An Environment whose owner never chose
|
|
1468
|
-
copies nothing, and a Room joined later stays uncopied until the owner selects
|
|
1469
|
-
it. The resident Kit reads the saved choice through
|
|
1470
|
-
`POST /kit/working-copy/selection` whenever its control stream connects or
|
|
1471
|
-
reconnects and whenever House points it there after a save;
|
|
1472
|
-
`kit worker run` reads it once before its pass.
|
|
1473
|
-
|
|
1474
|
-
Each selected Room becomes one ordinary Git repository at
|
|
1475
|
-
`/agents/house/working-copies/rooms/<room_ref>`, and `/private` one at
|
|
1476
|
-
`/agents/house/working-copies/private`. The Room reference names the location,
|
|
1477
|
-
so a Room handle change moves nothing. Every Agent launched afterwards receives
|
|
1478
|
-
the House path and local repository of each active copy in its context, one
|
|
1479
|
-
line each, such as `/rooms/work: /agents/house/working-copies/rooms/r_1x.q`.
|
|
1480
|
-
|
|
1481
|
-
A Room repository holds the Room's complete readable content: `ROOM.md`,
|
|
1482
|
-
`library/`, `capture/` with its provenance sidecars, and `collaboration/`. The
|
|
1483
|
-
`/private` repository holds its Library, collaboration, each Room's private
|
|
1484
|
-
`AGENTS.md`, the `agents/` cards and `house/`. Attached settings and attachment
|
|
1485
|
-
bytes are not copied. The branch `main` starts at the commit House served, which
|
|
1486
|
-
tracks exactly the files House declares writable under the Environment's
|
|
1487
|
-
current grant. Every other file is read-only House content: the generated
|
|
1488
|
-
`.gitignore` names and explains it, so Git never tracks it, and the matching
|
|
1489
|
-
`.ignore` keeps it visible to ripgrep and other native search. Neither file
|
|
1490
|
-
grants anything, and House refuses a change to a read-only path however it is
|
|
1491
|
-
committed.
|
|
1492
|
-
|
|
1493
|
-
Local edits, staging and commits change nothing in House until a commit is
|
|
1494
|
-
pushed. `refs/house/received` records the House content last received; each
|
|
1495
|
-
commit Kit wrote there, and each commit it acknowledged for a push, is a House
|
|
1496
|
-
state. The House revision of every file content Kit received, keyed by path,
|
|
1497
|
-
Git blob and the House state that received it, stays in Kit home under
|
|
1498
|
-
`state/working-copies/`, outside the repository. A local commit is a local tree, never a House revision.
|
|
1499
|
-
|
|
1500
|
-
The resident Kit keeps every active copy current. An `entries` frame on its
|
|
1501
|
-
control stream names a copied Room, the Room's position and log epoch, and the
|
|
1502
|
-
Change entries that brought it there: each changed path with its revision and
|
|
1503
|
-
text, or its removal. The resident applies the entries under the copy's own
|
|
1504
|
-
homes directly, without reading House, when the frame follows on from the copy:
|
|
1505
|
-
the same socket already carried an earlier frame for that Room, the copy's
|
|
1506
|
-
recorded position has reached that earlier frame's position, the log epoch is
|
|
1507
|
-
the copy's, and every changed path carries its revision and text. A frame the
|
|
1508
|
-
copy already holds changes nothing. Any other frame, and every reading of the
|
|
1509
|
-
saved choice, reads House instead, with the frames that arrive during one
|
|
1510
|
-
reading served by the next: one `POST /kit/door/discover` tells it where each
|
|
1511
|
-
copied Room stands, and for a Room that moved on it reads the changes after the
|
|
1512
|
-
copy's recorded position through `POST /kit/door/enumerate`. A changed, new or
|
|
1513
|
-
removed authored file becomes a commit on `refs/house/received`, its House
|
|
1514
|
-
revision kept in Kit home; changed, new or removed read-only content, such as
|
|
1515
|
-
Capture with its provenance sidecar or a House projection, is written into or
|
|
1516
|
-
removed from the ignored local tree, with each directory a removal leaves empty,
|
|
1517
|
-
and `.gitignore` and `.ignore` follow it.
|
|
1518
|
-
Delivery never touches the branch, the index or a tracked file, and a push
|
|
1519
|
-
waiting for its outcome or its local integration keeps its record. A push takes
|
|
1520
|
-
each file's revision from the newest House state in its own base's history that
|
|
1521
|
-
received that content, so a local commit keeps the base it was made on even when
|
|
1522
|
-
House returns to the same text under a newer revision. A file new to the copy is tracked when a write scope
|
|
1523
|
-
House declares for the Room covers it more closely than a declared protected
|
|
1524
|
-
path, and a local file already sitting where new read-only content arrives
|
|
1525
|
-
stays yours: Kit neither overwrites nor later removes it. When the write scopes
|
|
1526
|
-
or protected paths House declares for the Room change, Kit reads the Room again
|
|
1527
|
-
as a reselection does. A position older than House
|
|
1528
|
-
retains, or from before House's database was restored, makes Kit read the Room
|
|
1529
|
-
again as a reselection does; while House's replica has not reached the position
|
|
1530
|
-
the copy waits, and the resident delivers again 30 seconds later. A Room House
|
|
1531
|
-
no longer answers under its
|
|
1532
|
-
recorded handle makes Kit read the saved choice again, which follows a handle
|
|
1533
|
-
change or marks the copy inactive, and a Room that lost sync is named on
|
|
1534
|
-
standard error as `operation_denied` and keeps what it holds. `kit worker run`
|
|
1535
|
-
delivers once, after reading the saved choice.
|
|
1536
|
-
|
|
1537
|
-
`house git push [commit]`, run anywhere inside a copy or one of its linked
|
|
1538
|
-
worktrees, submits one commit, `HEAD` by default, resolved once. Kit compares
|
|
1539
|
-
that commit's tree with the newest House state in its history, so every linked
|
|
1540
|
-
worktree keeps its own base, and sends the difference as one ordinary House edit:
|
|
1541
|
-
a changed field, section or preamble against its own prior value, any other
|
|
1542
|
-
change as the whole file against its exact revision, a new file as a create and
|
|
1543
|
-
a committed deletion as a removal. Staged and unstaged work, and files absent
|
|
1544
|
-
only from the working tree, are never sent. Native `git push` reaches no House
|
|
1545
|
-
remote. Inside an Agent Session the push goes through that Session's bridge,
|
|
1546
|
-
under its own authority and provenance; outside one it needs
|
|
1547
|
-
`house git push [commit] --owner`, which the resident Kit carries out under its
|
|
1548
|
-
control lease when a resident runs, and which otherwise acquires and releases
|
|
1549
|
-
its own. A Session refuses `--owner`, and a missing bridge is never replaced by
|
|
1550
|
-
owner authority.
|
|
1551
|
-
|
|
1552
|
-
A Session's edit too large for one MCP request of 4 MiB goes through House's
|
|
1553
|
-
`prepare_edit_upload` Command on that Session's bridge, naming every changed
|
|
1554
|
-
path, the byte count and the SHA-256 of the bytes to upload under the push's
|
|
1555
|
-
operation. Those bytes are the serialized changes, padded with trailing spaces
|
|
1556
|
-
to one byte over 4 MiB when only the request around them passed the bound,
|
|
1557
|
-
because House takes no smaller upload. Kit sends them to the upload address
|
|
1558
|
-
House returns and then calls `edit` with the paths and the returned upload
|
|
1559
|
-
instead of the changes, so House applies them as the same one edit. The upload
|
|
1560
|
-
is recorded in Kit home before the edit is sent, so a push whose answer was lost
|
|
1561
|
-
resends that same edit; when House no longer holds the uploaded bytes and has
|
|
1562
|
-
not applied the edit, the push uploads them again once under the same operation.
|
|
1563
|
-
A change set beyond House's batch bounds is refused whole and never split.
|
|
1564
|
-
|
|
1565
|
-
Inside an Agent Session `git` is Kit's shim in front of the installed Git, and
|
|
1566
|
-
every command runs exactly as that Git runs it. In a copy or one of its linked
|
|
1567
|
-
worktrees the shim compares that worktree's index before and after each
|
|
1568
|
-
command. When `git mv` succeeds, Git's own dry run of the same command names
|
|
1569
|
-
which path went where, and the shim hands each move to the resident Kit, which
|
|
1570
|
-
keeps every moved document's successive paths in Kit home. A staged move
|
|
1571
|
-
belongs to its worktree's index: a command there that takes its path out of the
|
|
1572
|
-
index or brings it in anew, such as `git reset`, `git restore --staged`,
|
|
1573
|
-
`git rm`, `git stash` or `git switch`, takes it back, and so does removing the
|
|
1574
|
-
worktree. A `git commit` whose commit carries the move ties it to that commit.
|
|
1575
|
-
A tied move counts for every pushed commit whose history holds one of its
|
|
1576
|
-
commits, whatever the index does meanwhile. `git commit --amend` and
|
|
1577
|
-
`git rebase`, Kit's own rebase onto House's result included, also tie it to
|
|
1578
|
-
their result when the rewritten branch still carries it, so aborting the rebase
|
|
1579
|
-
or resetting back to the original commit keeps it as well. A `git reset` that
|
|
1580
|
-
leaves every commit of the move behind without touching its latest path in the
|
|
1581
|
-
index returns it to that worktree's index, where the next commit ties it again.
|
|
1582
|
-
Any other rewrite, such as a cherry-pick, leaves the move tied to its earlier
|
|
1583
|
-
commits. A push sends House's rename of the same document from the path its
|
|
1584
|
-
House base holds to the latest of its counted paths the pushed commit holds,
|
|
1585
|
-
when the commit no longer has the first and the base did not have the second;
|
|
1586
|
-
a moved file whose content also changed follows as a
|
|
1587
|
-
whole-file replacement at its destination. House keeps the document's identity
|
|
1588
|
-
and rewrites the links and references that name it; Kit reads those rewrites
|
|
1589
|
-
through `POST /kit/door/enumerate` and records them in the acknowledgment.
|
|
1590
|
-
Anything else records no move and the push sends an ordinary removal and
|
|
1591
|
-
creation, which House refuses while another document still refers to the
|
|
1592
|
-
removed one: a failed `git mv`, one undone or never committed through the
|
|
1593
|
-
shim, and a move made with Git outside the shim such as in the owner's own
|
|
1594
|
-
shell. An accepted push drops the moves its commit's history holds.
|
|
1595
|
-
|
|
1596
|
-
House decides before any local Git step. A refusal changes nothing in House;
|
|
1597
|
-
after `edit_conflict` Kit records the current content House names for the
|
|
1598
|
-
refused paths in `refs/house/received`, reading by revision any content the
|
|
1599
|
-
refusal does not carry, so the agent merges or rebases onto it with native Git,
|
|
1600
|
-
commits, and pushes again. After acceptance Kit records House's result for the
|
|
1601
|
-
pushed paths, the contents an owner push receives or each accepted revision
|
|
1602
|
-
read by name, in a commit that covers the pushed one and in
|
|
1603
|
-
`refs/house/received`; a read-only path House regenerates, such as the Wiki
|
|
1604
|
-
index, stays out of Git. The copy's recorded position goes back to where it
|
|
1605
|
-
stood when the push was sent, so the next delivery brings again anything House
|
|
1606
|
-
changed after the push's result, including what delivery received while the
|
|
1607
|
-
push awaited its answer. Kit then
|
|
1608
|
-
fast-forwards a clean branch still at the pushed commit, or rebases only its
|
|
1609
|
-
later commits. A result not yet readable, uncommitted tracked changes,
|
|
1610
|
-
a moved branch or a rebase conflict leave the branch as it was, with no stash or
|
|
1611
|
-
reset, and the push answers that House accepted it while local integration is
|
|
1612
|
-
pending; the next `house git push` finishes that integration without another
|
|
1613
|
-
House edit. A push whose answer was lost is recorded in Kit home before it is
|
|
1614
|
-
sent, and the next push by the same Session, or by the owner for an owner push,
|
|
1615
|
-
resends it under its original operation. Another caller cannot
|
|
1616
|
-
recover it: while the pushing Session lives it is refused as
|
|
1617
|
-
`kit_working_copy_push_pending`, and once that Session has ended Kit reads House
|
|
1618
|
-
again and drops it. When a retained push's operation has expired, Kit reads the
|
|
1619
|
-
Room again: if House holds the pushed content the push finishes as accepted,
|
|
1620
|
-
otherwise it is dropped and the next push submits it as a new operation.
|
|
1621
|
-
|
|
1622
|
-
Deselecting a Room, or losing read access to it, marks its copy inactive:
|
|
1623
|
-
launches omit it, delivery stops, a new push is refused as
|
|
1624
|
-
`kit_working_copy_inactive`, and nothing local is deleted; a push already sent still settles and integrates. Selecting it again reads the Room
|
|
1625
|
-
again, advances `refs/house/received`, and replaces only the read-only content
|
|
1626
|
-
Kit wrote, never the branch, the index or a tracked file. A Room the owner may
|
|
1627
|
-
read without sync is refused as `operation_denied` and stays uncopied, while its
|
|
1628
|
-
ordinary remote reads are unaffected. Kit names each refused copy on its
|
|
1629
|
-
standard error.
|
|
1630
|
-
|
|
1631
|
-
## Explicit synchronization
|
|
1632
|
-
|
|
1633
|
-
Beside [working copies](#working-copies), Kit has one explicit synchronization mode.
|
|
1634
|
-
It selects prefixes over the User root, materializes them into a named local
|
|
1635
|
-
directory, enumerates whole content and removals after the recorded positions,
|
|
1636
|
-
and submits a resolved exact-base change set the caller prepared. Kit does no
|
|
1637
|
-
local reconciliation. The mode shares no Working Copy manifest, never infers a
|
|
1638
|
-
removal from a locally absent file, and writes authored mutations only within
|
|
1639
|
-
House's declared write scopes: a selected Room's Library and, where granted,
|
|
1640
|
-
`ROOM.md`. `/private` selects the Private Room by its Room reference, records an independent Room
|
|
1641
|
-
position, and keeps its `agents/` cards and Room-specific private `AGENTS.md`
|
|
1642
|
-
beside `library/**`.
|
|
1643
|
-
|
|
1644
|
-
```sh
|
|
1645
|
-
kit sync bootstrap ./replica \
|
|
1646
|
-
--prefix /rooms/work/library \
|
|
1647
|
-
--prefix /rooms/work/capture \
|
|
1648
|
-
--prefix /private/library
|
|
1649
|
-
kit sync pull ./replica
|
|
1650
|
-
kit sync submit ./replica --changes ./changes.json
|
|
1651
|
-
kit sync rebootstrap ./replica
|
|
1652
|
-
```
|
|
1653
|
-
|
|
1654
|
-
`bootstrap` writes `ROOM.md`, Library text, House-derived projections, and
|
|
1655
|
-
Capture text, with the provenance House serves on a file's manifest entry as
|
|
1656
|
-
its `_provenance` sidecar, and records one position per selected authority from
|
|
1657
|
-
the bundle, with the log epoch House issued it in, together with
|
|
1658
|
-
the positive operation scopes House declared for it: `read`, `sync`, `write`,
|
|
1659
|
-
and `delete`, each a list of paths relative to the authority under which that
|
|
1660
|
-
operation is admitted. Managed replication needs `read` plus `sync` on a Room;
|
|
1661
|
-
a Room the User can only read answers `bootstrap`, `pull`, and `rebootstrap`
|
|
1662
|
-
with `operation_denied`. Generated `_provenance` files delivered beside authored
|
|
1663
|
-
Library paths are House metadata, not authored Library documents. `pull` presents
|
|
1664
|
-
each recorded position with its log epoch, applies each enumerated path's whole
|
|
1665
|
-
content or removal, and advances those positions. A removal, pulled or accepted
|
|
1666
|
-
from `submit`, also removes the file's `_provenance` sidecar and each directory
|
|
1667
|
-
it leaves empty up to the authority, so a later file can take that path; a
|
|
1668
|
-
directory still holding a file, such as a local draft, stays. A position older
|
|
1669
|
-
than House's declared retention, or one of a log epoch before House's database
|
|
1670
|
-
was restored, is reported as `position_expired`; only `rebootstrap` rebuilds the directory.
|
|
1671
|
-
|
|
1672
|
-
`submit` sends field, section, and preamble operations with that value's prior
|
|
1673
|
-
content as base, and whole-file, rename, and removal operations with the file
|
|
1674
|
-
revision. A rename of a populated directory instead carries a `base` map from
|
|
1675
|
-
every relative member path to its exact revision, such as
|
|
1676
|
-
`{"plan.md": "<revision>", "deep/log.md": "<revision>"}`, and House refuses it
|
|
1677
|
-
unless the map names exactly the directory's current members at their current
|
|
1678
|
-
revisions. Kit admits the rename at its source and destination and each named
|
|
1679
|
-
member at both, as House does.
|
|
1680
|
-
House alone validates the resulting Library: a batch it refuses answers with
|
|
1681
|
-
House's own file path, field, rule, and version. A refusal is reported with the
|
|
1682
|
-
current material House returned and the local directory is left for the caller.
|
|
1683
|
-
An acceptance updates the recorded position, its log epoch, and the affected
|
|
1684
|
-
files from the returned final content. It also reads House's own provenance
|
|
1685
|
-
for those files by enumerating from the position the batch started at, and
|
|
1686
|
-
writes each served sidecar whose revision is the accepted one, so a submit,
|
|
1687
|
-
answered or recovered after a lost answer, leaves each file's `_provenance`
|
|
1688
|
-
sidecar at the revision House recorded. Kit never composes that metadata
|
|
1689
|
-
itself.
|
|
1690
|
-
|
|
1691
|
-
Pending submissions are written before transfer. A lost response, reconnect, or
|
|
1692
|
-
lease renewal recovers that exact request under the same identifier. A domain
|
|
1693
|
-
refusal is reported without automatic retry and without minting a replacement
|
|
1694
|
-
identifier. A second deliberate submit is a new identifier. After a restore of
|
|
1695
|
-
House's database, a request House holds no receipt for and whose identifier
|
|
1696
|
-
dates earlier than five minutes after the new log epoch began is answered
|
|
1697
|
-
`position_expired`: it wrote nothing and never will under that identifier, so
|
|
1698
|
-
the Kit drops it like any other refusal, `rebootstrap` rebuilds the directory,
|
|
1699
|
-
and a change still wanted is a new deliberate submit against the rebuilt bases.
|
|
1700
|
-
Once House has answered any request for a replica `position_expired`, the
|
|
1701
|
-
replica refuses `submit` as `position_expired` until `rebootstrap` rebuilds it.
|
|
1702
|
-
A pending submission House answers `operation_expired` is uncertain, so the
|
|
1703
|
-
Kit first pulls confirmed state. If House already holds the pending change,
|
|
1704
|
-
the Kit drops the pending copy and sends nothing more; only if it does not
|
|
1705
|
-
does the Kit send that change again under a new identifier.
|
|
1706
|
-
`rebootstrap` drops a submission still pending against the directory it
|
|
1707
|
-
replaces.
|
|
1708
|
-
|
|
1709
|
-
`--changes` names a JSON array of exact-base operations, or an object with a
|
|
1710
|
-
`changes` array. Omitting it derives field, section, preamble, create, and whole-file
|
|
1711
|
-
operations from local writable files against the last confirmed snapshot and
|
|
1712
|
-
still never infers a removal. A local edit of a selected protected path is
|
|
1713
|
-
refused before House is contacted as `protected_path`; a change outside the
|
|
1714
|
-
authority's `write` scope, or a removal outside its `delete` scope, is refused
|
|
1715
|
-
before House is contacted as `operation_denied`, the same code House answers
|
|
1716
|
-
for a batch that reaches it.
|
|
1717
|
-
|
|
1718
|
-
## Attachments
|
|
1719
|
-
|
|
1720
|
-
Upload one local file as an attachment to a Room or to your Private Room. Name
|
|
1721
|
-
the place explicitly. Kit sends the descriptor and the selected bytes; it does
|
|
1722
|
-
not scan a directory, convert a URL, or attach sibling files, credentials, or
|
|
1723
|
-
absolute paths.
|
|
1724
|
-
|
|
1725
|
-
```sh
|
|
1726
|
-
kit attachment upload ./notes.bin \
|
|
1727
|
-
--room room:work \
|
|
1728
|
-
--media-type application/pdf
|
|
1729
|
-
|
|
1730
|
-
kit attachment upload ./notes.bin \
|
|
1731
|
-
--private
|
|
1732
|
-
|
|
1733
|
-
kit attachment get \
|
|
1734
|
-
--attachment at_1x.q \
|
|
1735
|
-
--room room:work \
|
|
1736
|
-
--output ./notes.bin
|
|
1737
|
-
```
|
|
1738
|
-
|
|
1739
|
-
`--room` and `--private` are mutually exclusive and one is required; `--name`
|
|
1740
|
-
defaults to the file's base name. The upload prints the attachment reference
|
|
1741
|
-
(`at_…`) House answered. That reference is what text links to: an attachment is
|
|
1742
|
-
meaningful only because a document links it, and House records those links from
|
|
1743
|
-
the accepted text. Retrying the same file after a lost response or failed upload
|
|
1744
|
-
reuses the stored version under the identical descriptor, so it completes the
|
|
1745
|
-
same attachment instead of creating a second one; uploading the same file to the
|
|
1746
|
-
same place again answers that same attachment. A changed file is a new
|
|
1747
|
-
attachment. HTTP 200 is not retention: only `save.status: saved` plus an
|
|
1748
|
-
independent download whose length and SHA-256 match counts. A failed local read
|
|
1749
|
-
or failed House save is not reported as saved. Transfer uses the current control
|
|
1750
|
-
lease and the one reusable byte client; the durable credential is presented only
|
|
1751
|
-
to `POST /kit/lease`. Sync commands do not fetch attachment bytes; Source and
|
|
1752
|
-
Worker retention save through the same byte client.
|
|
1753
|
-
|
|
1754
|
-
The same paired installed-CLI proof covers these doors; see
|
|
1755
|
-
[Paired proof](#paired-proof) for the command that runs it.
|
|
1756
|
-
|
|
1757
|
-
## Persistent container Environments
|
|
1758
|
-
|
|
1759
|
-
An Always-on Linux host can run the persistent container Environments its owner
|
|
1760
|
-
creates in House. House owns the target: which host, the workspace name, and the
|
|
1761
|
-
CPU, memory and persistent-disk ceilings. This Kit owns the local lifecycle and
|
|
1762
|
-
reports only what it proved.
|
|
1763
|
-
|
|
1764
|
-
The supported configuration is one Linux host with
|
|
1765
|
-
|
|
1766
|
-
- cgroup v2 delegating the `cpu` and `memory` controllers, read from
|
|
1767
|
-
`/sys/fs/cgroup/cgroup.controllers`;
|
|
1768
|
-
- a reachable Docker Engine daemon;
|
|
1769
|
-
- `/dev/loop-control` present, so a workspace disk can carry an enforced size;
|
|
1770
|
-
- `host.workspace` pointing at a directory this Kit may create workspaces under;
|
|
1771
|
-
- `host.image` naming an image that carries Node, this Kit, `mkfs.ext4` and
|
|
1772
|
-
`nsenter`, and whose own entrypoint starts `kit worker run` from
|
|
1773
|
-
`HOUSE_KIT_HOME`.
|
|
1774
|
-
|
|
1775
|
-
Any missing prerequisite refuses the create before House ever presents the
|
|
1776
|
-
workspace as constrained, naming the exact missing piece.
|
|
1777
|
-
|
|
1778
|
-
```sh
|
|
1779
|
-
kit workspace host
|
|
1780
|
-
```
|
|
1781
|
-
|
|
1782
|
-
The command acquires this host's workspace work under its Environment control
|
|
1783
|
-
lease and settles each operation with what it observed:
|
|
1784
|
-
|
|
1785
|
-
- **create** formats one ext4 image of exactly the configured disk ceiling,
|
|
1786
|
-
mounts it as the workspace's only writable filesystem, writes that
|
|
1787
|
-
Environment's own Kit credential and a `kit.json` declaring the selected agent
|
|
1788
|
-
CLIs with `execution_authority` `allow` inside it, since nobody holds a
|
|
1789
|
-
terminal there to answer otherwise, and creates the container with
|
|
1790
|
-
`--cpus`, `--memory` and `--memory-swap` at the configured ceilings. The
|
|
1791
|
-
workspace is reported `stopped` with the runtime and the enforcement it
|
|
1792
|
-
proved. This host's own credential never enters the workspace: the only
|
|
1793
|
-
credential written there belongs to the Environment running inside it.
|
|
1794
|
-
- **start** mounts the workspace disk if the host rebooted, refuses when
|
|
1795
|
-
starting would leave less than the host reserve, starts the container, and
|
|
1796
|
-
reports `ready` only after the exact workspace proved it accepts execution.
|
|
1797
|
-
- **stop** stops the container and keeps the disk mounted, so ordinary files,
|
|
1798
|
-
installed tools and login state survive to the next start.
|
|
1799
|
-
- **delete** removes that exact container, unmounts that exact workspace disk and
|
|
1800
|
-
removes that exact directory. No other workspace, and nothing else on this
|
|
1801
|
-
host, is touched.
|
|
1802
|
-
|
|
1803
|
-
Every operation names one workspace this Kit created. A workspace the runtime no
|
|
1804
|
-
longer has is reported `absent` rather than recreated, so House ends the
|
|
1805
|
-
Environment visibly instead of inventing a process observation. On every pass
|
|
1806
|
-
this Kit reconciles its own labelled containers against the exact inventory
|
|
1807
|
-
House authorizes: a managed workspace House no longer authorizes to run is
|
|
1808
|
-
stopped, and a container this Kit did not create is never adopted, started or
|
|
1809
|
-
removed.
|
|
1810
|
-
|
|
1811
|
-
A pass House does not answer, such as one during a House restart, ends that
|
|
1812
|
-
pass only, and the host tries again on the next one; a pass House refuses ends
|
|
1813
|
-
the host. Without `--passes` the host serves until SIGINT or SIGTERM. It
|
|
1814
|
-
asks House to release its control lease when it ends, after its passes, a
|
|
1815
|
-
refusal, SIGINT or SIGTERM; once House has released it, a restarted host is
|
|
1816
|
-
admitted at once.
|
|
1817
|
-
|
|
1818
|
-
Ceilings are shared ceilings. The ceilings configured across one host may exceed
|
|
1819
|
-
that host, nothing is reserved for any workspace, and no workspace is promised
|
|
1820
|
-
its maximum while another one runs.
|
|
1821
|
-
|
|
1822
|
-
## The Telegram gateway
|
|
1823
|
-
|
|
1824
|
-
House can project one owner's conversations through that owner's own Telegram
|
|
1825
|
-
bot. In Kit mode the BotFather token stays in one installed Environment, and
|
|
1826
|
-
this Kit is the only thing that calls the Bot API. House never receives the
|
|
1827
|
-
token: it appears in no request body, no refusal and no printed summary.
|
|
1828
|
-
|
|
1829
|
-
Exactly one Environment may be the gateway for one bot. House records that
|
|
1830
|
-
binding when the gateway registers, and refuses a second Environment claiming
|
|
1831
|
-
the same bot. Worker Environments stay unchanged: they still acquire Runs and
|
|
1832
|
-
materialize inbound conversation files.
|
|
1833
|
-
The gateway never runs a Worker's provider and a Worker never holds the token;
|
|
1834
|
-
the two exchange bytes only through House, which relays an inbound attachment
|
|
1835
|
-
from the gateway to the Worker that needs it and serves the gateway an outbound
|
|
1836
|
-
image from the original the Session stored.
|
|
1837
|
-
|
|
1838
|
-
Bind the bot to this Environment:
|
|
1839
|
-
|
|
1840
|
-
```sh
|
|
1841
|
-
kit telegram connect
|
|
1842
|
-
```
|
|
1843
|
-
|
|
1844
|
-
The command asks for the token on a hidden terminal prompt, or reads one line
|
|
1845
|
-
from standard input when it is not attached to a terminal. It asks Telegram `getMe`, and
|
|
1846
|
-
refuses a bot whose Threaded Mode is off, because House projects each
|
|
1847
|
-
conversation into its own forum topic. It then asks House whether this
|
|
1848
|
-
Environment may become the gateway, before it writes anything locally. Once
|
|
1849
|
-
House admits the preflight it deletes any webhook, so long polling is the only
|
|
1850
|
-
receiver, and proves the owner by reading one ordinary private message outside
|
|
1851
|
-
any topic through `getUpdates`, advancing the stored offset past it. Only then
|
|
1852
|
-
is the token written, and only then is the gateway registered with House. A
|
|
1853
|
-
setup House refuses removes the written token again, so a refused connect leaves
|
|
1854
|
-
no custody behind. The command prints the bot username, the bot user id and the
|
|
1855
|
-
proven owner's Telegram user id.
|
|
1856
|
-
|
|
1857
|
-
Run the gateway:
|
|
1858
|
-
|
|
1859
|
-
```sh
|
|
1860
|
-
kit telegram serve
|
|
1861
|
-
```
|
|
1862
|
-
|
|
1863
|
-
The command holds one Environment control lease for the whole run and performs
|
|
1864
|
-
three activities inside it, each paced on its own so none of them blocks
|
|
1865
|
-
another, beside the live drafts described below:
|
|
1866
|
-
|
|
1867
|
-
- **ingress** reads updates from the stored offset, normalizes each one into the
|
|
1868
|
-
update House accepts, submits it, and advances the stored offset only after
|
|
1869
|
-
House has taken it. A submission that fails leaves the offset where it was, so
|
|
1870
|
-
the same update is offered again rather than lost.
|
|
1871
|
-
- **inbound files** acquires the attachments House still needs, refuses anything
|
|
1872
|
-
reported above the 20 MiB download ceiling before calling `getFile`, downloads
|
|
1873
|
-
the file with the local token, and pushes the bytes to House for the Worker
|
|
1874
|
-
Environment that will materialize them. That push waits for the Worker to pull
|
|
1875
|
-
and a single failed fetch never ends the pass.
|
|
1876
|
-
- **outbound queue** acquires the per-bot queue in its exact order, marks each
|
|
1877
|
-
delivery attempted before it sends, and reports what Telegram answered:
|
|
1878
|
-
`delivered` with the exact message or topic Telegram returned, `failed` when
|
|
1879
|
-
Telegram refuses outright, and `delivery_unknown` when
|
|
1880
|
-
the answer is absent or unreadable. File items are pulled back from House,
|
|
1881
|
-
refused as `file_too_large` above the 10 MiB image ceiling, uploaded as
|
|
1882
|
-
`sendPhoto` with the caption House composed around the link to the retained
|
|
1883
|
-
original, and reported the same way. A caption past Telegram's
|
|
1884
|
-
1,024-character caption limit keeps its head under the image, cut at its last
|
|
1885
|
-
line break that fits or else at the limit, and the rest follows in the topic
|
|
1886
|
-
as ordinary messages; the upload is reported with the image's answer when
|
|
1887
|
-
every part was delivered and otherwise with the answer of the first part that
|
|
1888
|
-
was not. A pull that fails reports
|
|
1889
|
-
`transfer_failed` and an ambiguous upload answer reports `delivery_unknown`.
|
|
1890
|
-
Only an image ever becomes a file item, because House offers every other file
|
|
1891
|
-
as a `conversation_file_link` message this queue delivers like any other. The
|
|
1892
|
-
queue carries no draft: an answer holding a draft item is refused as
|
|
1893
|
-
`kit_telegram_response_invalid`. House caps no message, so the gateway alone
|
|
1894
|
-
fits one to Telegram's rich message limits, 32,768 characters and 500 blocks:
|
|
1895
|
-
a rich message past them is sent as consecutive messages in order, split
|
|
1896
|
-
between blocks and inside a block only when that block alone exceeds them,
|
|
1897
|
-
and reported once, `delivered` with the first message's id when every part
|
|
1898
|
-
was delivered and otherwise with the answer of the first part that was not.
|
|
1899
|
-
A new plain-text message past Telegram's 4,096-character message limit, such
|
|
1900
|
-
as a `conversation_file_link` text, is sent and reported the same way, cut
|
|
1901
|
-
at the last line break that fits or else at the limit, so the first message
|
|
1902
|
-
keeps its reply and keyboard and a link on its own line stays whole.
|
|
1903
|
-
Each answer also names the `threads` the bot's topics carry, a
|
|
1904
|
-
`conversation_id` with its `chat_id` and `topic_id`, which is where live
|
|
1905
|
-
drafts go.
|
|
1906
|
-
|
|
1907
|
-
Live drafts come from the control stream, not from House's queue. Run on its
|
|
1908
|
-
own, the command holds the Environment's [control stream](#the-kit-control-stream)
|
|
1909
|
-
for its lease; served by `kit resident`, it receives the draft and plan frames
|
|
1910
|
-
the resident's socket receives, through the resident, and never opens a second
|
|
1911
|
-
socket. The gateway Environment's socket carries every conversation bound to a
|
|
1912
|
-
topic of the bot, with no subscribe frame. The gateway keeps the latest view of
|
|
1913
|
-
each such conversation's turn: a frame that starts at block 0 replaces a view,
|
|
1914
|
-
the next frame in sequence applies from its `from`, and a frame after a gap in
|
|
1915
|
-
the sequence, or one for a turn it holds no view of, is applied nowhere and
|
|
1916
|
-
answered with one `gap` frame naming the conversation and turn, so House sends
|
|
1917
|
-
the whole view again. It renders each view as one Telegram draft per turn with
|
|
1918
|
-
`sendRichMessageDraft` on the bound topic, the live plan above the assistant
|
|
1919
|
-
text, under one draft id derived from the turn, because Telegram shows one bot
|
|
1920
|
-
draft per topic and a second id would replace the first. A view past the rich
|
|
1921
|
-
message limits is shortened inside that one draft, never split into a second.
|
|
1922
|
-
A live plan is part of the draft and never a sent message; the frozen plan
|
|
1923
|
-
House queues at the turn's end is the turn's one plan message. Drafts coalesce
|
|
1924
|
-
to the latest view, one Bot API call at a time, and are never reported back:
|
|
1925
|
-
the same bot-wide `retry_after` gate drops a draft while it is closed, and a
|
|
1926
|
-
draft answered with `retry_after` closes it. A closed gate drops a preview,
|
|
1927
|
-
never a durable delivery.
|
|
1928
|
-
|
|
1929
|
-
Each serve run first asks House to settle the attempts an earlier run abandoned,
|
|
1930
|
-
so a restarted gateway never leaves a delivery attempted forever. A Telegram
|
|
1931
|
-
`retry_after` opens one bot-wide gate: nothing is sent, fetched or polled until
|
|
1932
|
-
the delay Telegram supplied has passed. `--passes <count>` bounds each activity
|
|
1933
|
-
to that many passes and is what tests and one-shot runs use; without it the
|
|
1934
|
-
command is resident and stops on SIGINT or SIGTERM, printing what it settled.
|
|
1935
|
-
Each refused pass is printed as it happens, naming its activity, item and
|
|
1936
|
-
refusal code, so a gateway refused on every pass says so while it runs; the
|
|
1937
|
-
summary printed when it stops still lists each of them.
|
|
1938
|
-
|
|
1939
|
-
Release the bot:
|
|
1940
|
-
|
|
1941
|
-
```sh
|
|
1942
|
-
kit telegram disconnect
|
|
1943
|
-
```
|
|
1944
|
-
|
|
1945
|
-
The command writes a local disconnect marker first, deletes the webhook, removes
|
|
1946
|
-
the token and the stored offset, tells House this Environment is no longer the
|
|
1947
|
-
gateway, and removes the marker last. Local custody is therefore always gone
|
|
1948
|
-
before House admits the change. A disconnect interrupted after the marker was
|
|
1949
|
-
written finishes on a later run even though the token is already gone, and a
|
|
1950
|
-
`kit telegram serve` that finds the marker refuses to act.
|
|
1951
|
-
|
|
1952
|
-
## Local state
|
|
1953
|
-
|
|
1954
|
-
```
|
|
1955
|
-
$HOUSE_KIT_HOME/
|
|
1956
|
-
credential
|
|
1957
|
-
kit.json
|
|
1958
|
-
conversation-inbound/<run>/<attachment>/<name>
|
|
1959
|
-
secrets/personal-telegram-bot.token
|
|
1960
|
-
state/telegram/get-updates-offset.json
|
|
1961
|
-
state/telegram/disconnecting.json
|
|
1962
|
-
state/hooks.json
|
|
1963
|
-
state/hooks/<hook id>.txt
|
|
1964
|
-
state/repositories/<repository identity>.json
|
|
1965
|
-
state/sync/<replica identity>.json
|
|
1966
|
-
state/sync/<replica identity>.pending.json
|
|
1967
|
-
state/working-copies/rooms/<room ref>.json
|
|
1968
|
-
state/working-copies/private.json
|
|
1969
|
-
state/attachments/<upload identity>.json
|
|
1970
|
-
state/conversation-originals/<attachment identity>.json
|
|
1971
|
-
state/sessions/<tmux session>.json
|
|
1972
|
-
state/update-drain
|
|
1973
|
-
state/skill-set.json
|
|
1974
|
-
state/how-we-work
|
|
1975
|
-
state/continuations/<run identity>.json
|
|
1976
|
-
state/google/entries/<source>.json
|
|
1977
|
-
state/google/credentials/<account>.json
|
|
1978
|
-
state/google/checkpoints/<source>.json
|
|
1979
|
-
state/google/health/<source>.json
|
|
1980
|
-
state/google/gaps/<source>.json
|
|
1981
|
-
state/google/dropped/<source>.json
|
|
1982
|
-
state/google/originals/<source>.json
|
|
1983
|
-
state/<instance>/checkpoints/<service>.json
|
|
1984
|
-
state/<instance>/health/<service>.json
|
|
1985
|
-
state/<instance>/gaps/<service>.json
|
|
1986
|
-
state/<instance>/dropped/<service>.json
|
|
1987
|
-
state/<instance>/originals/<service>.json
|
|
1988
|
-
```
|
|
1989
|
-
|
|
1990
|
-
Every file is written atomically and readable only by its owner. A file the Kit
|
|
1991
|
-
cannot read is refused rather than treated as a service that never captured.
|
|
1992
|
-
A Google entry's state is keyed by its Source reference and lives only while
|
|
1993
|
-
House lists the entry. Disabling a custom service or instance leaves its state
|
|
1994
|
-
in place, so re-enabling resumes from the stored cursor instead of importing
|
|
1995
|
-
again.
|
|
1996
|
-
|
|
1997
|
-
```sh
|
|
1998
|
-
kit source reset acme # every service
|
|
1999
|
-
kit source reset acme notes # one service
|
|
2000
|
-
```
|
|
2001
|
-
|
|
2002
|
-
Reset serves custom connectors, and is the only deletion of their captured
|
|
2003
|
-
state. It removes the named checkpoints
|
|
2004
|
-
and their outstanding retained references so the next capture restarts exactly at
|
|
2005
|
-
the configured backfill boundary. It takes the same
|
|
2006
|
-
Environment control lease a capture takes, so it cannot delete a checkpoint a
|
|
2007
|
-
running capture is about to write back.
|
|
2008
|
-
|
|
2009
|
-
## Host capacity
|
|
2010
|
-
|
|
2011
|
-
An owned Linux host can report what it actually has, so its owner reads real
|
|
2012
|
-
capacity instead of guessing from Run counts. Declare the host section:
|
|
2013
|
-
|
|
2014
|
-
```json
|
|
2015
|
-
"host": {
|
|
2016
|
-
"workspace": "/var/lib/house-kit/workspaces",
|
|
2017
|
-
"maxima": {
|
|
2018
|
-
"cpu_millicores": 2000,
|
|
2019
|
-
"memory_bytes": 4294967296,
|
|
2020
|
-
"disk_bytes": 53687091200
|
|
2021
|
-
},
|
|
2022
|
-
"image": null
|
|
2023
|
-
}
|
|
2024
|
-
```
|
|
2025
|
-
|
|
2026
|
-
`image` is the workspace image this host runs persistent container Environments
|
|
2027
|
-
from, and is only read by `kit workspace host` below; leave it `null` on a host
|
|
2028
|
-
that reports capacity and hosts no container. `workspace` is the absolute path of the persistent workspace directory; the
|
|
2029
|
-
report measures the filesystem holding it, not the root filesystem by
|
|
2030
|
-
assumption. `maxima` are the ceilings this host is configured to apply to
|
|
2031
|
-
Kit-managed workloads, and House keeps them apart from what it measured. Declare
|
|
2032
|
-
`"maxima": null` when this host configures no ceiling; an empty object is
|
|
2033
|
-
refused rather than read as no limit. A ceiling is a shared ceiling, never a
|
|
2034
|
-
reservation, and House infers no supported Agent count from any of these values.
|
|
2035
|
-
|
|
2036
|
-
```sh
|
|
2037
|
-
kit host report
|
|
2038
|
-
```
|
|
2039
|
-
|
|
2040
|
-
The command reads `/proc/stat` twice a second apart, reads `/proc/meminfo`, and
|
|
2041
|
-
asks the kernel for the workspace filesystem's block counts. It sends the host
|
|
2042
|
-
totals, what is in use, and what is available for each of processors, memory and
|
|
2043
|
-
that filesystem, with the instant it measured them. Nothing else is collected:
|
|
2044
|
-
no process list, no command line, no environment value, and no credential. A
|
|
2045
|
-
native source this Kit cannot read or parse refuses as
|
|
2046
|
-
`kit_host_unmeasurable` and sends nothing, because a host that cannot be
|
|
2047
|
-
measured is unknown to its owner rather than empty.
|
|
2048
|
-
|
|
2049
|
-
The report is one observation, not a stream. Run it on a timer the host owner
|
|
2050
|
-
owns:
|
|
2051
|
-
|
|
2052
|
-
```
|
|
2053
|
-
[Unit]
|
|
2054
|
-
Description=Report measured host capacity to House
|
|
2055
|
-
|
|
2056
|
-
[Service]
|
|
2057
|
-
Type=oneshot
|
|
2058
|
-
User=house-kit
|
|
2059
|
-
ExecStart=/usr/bin/kit host report
|
|
2060
|
-
```
|
|
2061
|
-
|
|
2062
|
-
```
|
|
2063
|
-
[Timer]
|
|
2064
|
-
OnBootSec=1min
|
|
2065
|
-
OnUnitActiveSec=5min
|
|
2066
|
-
```
|
|
2067
|
-
|
|
2068
|
-
House reads an observation as fresh only while three instants - the one this
|
|
2069
|
-
Kit says it measured, the one House received the report, and now - all fall
|
|
2070
|
-
inside one fifteen-minute window. A host that stops reporting reads as aged from
|
|
2071
|
-
then on, a report that was already stale when it arrived reads as aged
|
|
2072
|
-
immediately, and a host that never reported reads as unknown; none of them ever
|
|
2073
|
-
reads as a measured zero. Keep this host's clock in step with real time - a
|
|
2074
|
-
clock more than fifteen minutes out of step in either direction makes every
|
|
2075
|
-
report it sends read as aged, which is the honest answer House can give about a
|
|
2076
|
-
reading it cannot date, and dating a reading into the future buys no freshness
|
|
2077
|
-
because it widens the same window. A smaller skew is not free either: it is
|
|
2078
|
-
spent from the same fifteen minutes, so a clock a few minutes out of step leaves
|
|
2079
|
-
the timer above correspondingly less room to miss a run before House reads this
|
|
2080
|
-
host as aged.
|
|
2081
|
-
|
|
2082
|
-
## Migrating authored text and linked originals
|
|
2083
|
-
|
|
2084
|
-
`kit migrate` runs under the Environment User's control lease. Its module
|
|
2085
|
-
category is `migrator`, declared in the required `migrators` array in `kit.json`
|
|
2086
|
-
(use `"migrators": []` when none are installed):
|
|
2087
|
-
|
|
2088
|
-
```json
|
|
2089
|
-
{
|
|
2090
|
-
"id": "my-import",
|
|
2091
|
-
"category": "migrator",
|
|
2092
|
-
"command": ["/home/ada/tools/my-import"],
|
|
2093
|
-
"timeout_ms": 600000,
|
|
2094
|
-
"environment": { "EXPORT_REGION": "eu" },
|
|
2095
|
-
"secrets": { "EXPORT_TOKEN": { "file": "/run/secrets/export-token" } }
|
|
2096
|
-
}
|
|
2097
|
-
```
|
|
2098
|
-
|
|
2099
|
-
The example is a user-installed module. Kit ships no concrete external-system
|
|
2100
|
-
migrator. Secret references resolve only in the Environment and are passed only
|
|
2101
|
-
to that module's process environment. The module receives neither House's
|
|
2102
|
-
credential nor its lease. Module output and stderr are not published or echoed;
|
|
2103
|
-
a module must never write an external credential into its emitted content,
|
|
2104
|
-
manifest, URLs, or attachments.
|
|
2105
|
-
|
|
2106
|
-
```sh
|
|
2107
|
-
kit migrate emit my-import --package /home/ada/exports/project
|
|
2108
|
-
kit migrate mount /home/ada/exports/project --subtree /rooms/work/library/project
|
|
2109
|
-
# Or emit and mount in one maintained mount session:
|
|
2110
|
-
kit migrate run my-import --package /home/ada/exports/another-project \
|
|
2111
|
-
--subtree /private/library/another-project
|
|
2112
|
-
```
|
|
2113
|
-
|
|
2114
|
-
Emission refuses an existing output directory. The module reads one JSON line
|
|
2115
|
-
on stdin: `{"contract":"kit-migrator/1","migrator":"my-import","package":"/absolute/output"}`.
|
|
2116
|
-
It must finish writing this tree before exiting successfully:
|
|
2117
|
-
|
|
2118
|
-
```text
|
|
2119
|
-
project/
|
|
2120
|
-
migrator.json
|
|
2121
|
-
model/note.md # optional independently prepared type definition
|
|
2122
|
-
content/page.md # UTF-8 authored Markdown
|
|
2123
|
-
attachments/image.png # exact downloaded original bytes
|
|
2124
|
-
attachments/notes.txt
|
|
2125
|
-
```
|
|
2126
|
-
|
|
2127
|
-
The manifest is closed and versioned:
|
|
2128
|
-
|
|
2129
|
-
```json
|
|
2130
|
-
{
|
|
2131
|
-
"contract": "kit-migrator/1",
|
|
2132
|
-
"category": "migrator",
|
|
2133
|
-
"migrator": "my-import",
|
|
2134
|
-
"attachments": [
|
|
2135
|
-
{
|
|
2136
|
-
"path": "image.png",
|
|
2137
|
-
"source": "https://export.example/image.png",
|
|
2138
|
-
"media_type": "image/png",
|
|
2139
|
-
"download": "downloaded",
|
|
2140
|
-
"referrers": ["page.md"]
|
|
2141
|
-
},
|
|
2142
|
-
{
|
|
2143
|
-
"source": "https://export.example/unavailable",
|
|
2144
|
-
"download": "unavailable",
|
|
2145
|
-
"failure": "not_found",
|
|
2146
|
-
"referrers": ["page.md"]
|
|
2147
|
-
}
|
|
2148
|
-
]
|
|
2149
|
-
}
|
|
2150
|
-
```
|
|
2151
|
-
|
|
2152
|
-
`path` is relative to `attachments/`; `referrers` are relative to `content/`.
|
|
2153
|
-
`name` optionally supplies the attachment's file name. Downloaded entries require a
|
|
2154
|
-
local path and forbid `failure`. Unavailable or skipped entries require
|
|
2155
|
-
`failure` and forbid a local path. Every downloaded attachment must be linked
|
|
2156
|
-
from a declared referring document, for example
|
|
2157
|
-
`` in `content/page.md`. Unavailable ordinary
|
|
2158
|
-
Markdown URLs remain visible; they do not satisfy required typed references.
|
|
2159
|
-
Only Markdown enters the Library. Symlinks, invalid UTF-8, binary content, type
|
|
2160
|
-
aliases under `content/types/`, and generated `_provenance` are refused.
|
|
2161
|
-
`content/MIGRATION.md` is reserved for Kit's report.
|
|
2162
|
-
|
|
2163
|
-
Before creating the text subtree, Kit checks the House-declared MD model version
|
|
2164
|
-
against its exact `@agentshouse/mdmodel` dependency, which rewrites the
|
|
2165
|
-
package's links. House alone validates the resulting Library: a mount it
|
|
2166
|
-
refuses answers with House's own file path, field, rule and version, and no
|
|
2167
|
-
content lands. Definitions in `model/` require their own write
|
|
2168
|
-
grant at `/rooms/<handle>/settings/model/<type>.md` or
|
|
2169
|
-
`/private/settings/model/<type>.md`; a content write grant cannot prepare them.
|
|
2170
|
-
Kit adopts an identical existing definition and refuses a different one.
|
|
2171
|
-
Preparation uses the existing authored batch producer before the empty-subtree
|
|
2172
|
-
mount, so a missing model grant leaves no partial content tree. Independently
|
|
2173
|
-
prepared model definitions survive a later content-mount refusal or unmount.
|
|
2174
|
-
|
|
2175
|
-
The atomic text mount includes a report with pending transfer rows. Kit then
|
|
2176
|
-
uploads each downloaded file once as an attachment to the mount's Room or
|
|
2177
|
-
Private Room, independently retrieves and compares the saved bytes, and only
|
|
2178
|
-
after that proof succeeds rewrites every link to it, in every referring
|
|
2179
|
-
document, to its attachment reference, such as `at_1x.q`. The reference is an
|
|
2180
|
-
identity House resolves, not an HTTP download URL. A link's query and fragment
|
|
2181
|
-
are kept after the reference, as in `at_1x.q#page=3`. Failed or unverified
|
|
2182
|
-
uploads remain source links in the authored text; their failure details appear
|
|
2183
|
-
only in the report. Use `kit attachment get --attachment <at_ref> --room
|
|
2184
|
-
<room-ref> --output <file>` (or `--private`) to retrieve the exact bytes. Binary
|
|
2185
|
-
bytes never enter the text replica. Migrators create no Capture, Source
|
|
2186
|
-
instance, or provenance.
|
|
2187
|
-
|
|
2188
|
-
The mounted `MIGRATION.md` separates local downloads from saved, pending,
|
|
2189
|
-
failed, and skipped attachment uploads, with source URLs, referring documents
|
|
2190
|
-
and the attachment reference of each saved upload.
|
|
2191
|
-
`MIGRATION.json` beside the emitted package preserves local pending and final
|
|
2192
|
-
outcomes. A failed upload leaves usable mounted text and returns a nonzero
|
|
2193
|
-
exit status. A failure to publish the final report also returns nonzero and
|
|
2194
|
-
leaves the pending mounted report plus the local outcomes. Re-migration is
|
|
2195
|
-
explicit unmount followed by mount.
|
|
2196
|
-
|
|
2197
|
-
```sh
|
|
2198
|
-
kit migrate unmount /rooms/work/library/project
|
|
2199
|
-
kit migrate unmount /rooms/work/library/project \
|
|
2200
|
-
--confirm /rooms/work/library/project
|
|
2201
|
-
```
|
|
2202
|
-
|
|
2203
|
-
The first command shows the subtree destruction summary and removes nothing.
|
|
2204
|
-
Confirmation must repeat the exact subtree. Kit checks discovered write/delete
|
|
2205
|
-
authority and protected paths for every selected target, then makes House's one
|
|
2206
|
-
unmount call. House rechecks authority and subtree state at commit. Each
|
|
2207
|
-
command requires the ordinary available Kit control lease; an already held lease
|
|
2208
|
-
is refused by the existing Kit rail.
|
|
2209
|
-
|
|
2210
|
-
### Linux Compose validation
|
|
2211
|
-
|
|
2212
|
-
The test service uses the host Docker daemon, loop device availability and a
|
|
2213
|
-
shared `/tmp` mount because `npm test` includes Kit's real workspace-volume
|
|
2214
|
-
proof. It runs with an init process, because the supervised-path proofs kill a
|
|
2215
|
-
tmux pane whose adapter is then orphaned and has to be reaped rather than left
|
|
2216
|
-
as a zombie. Set `KIT_TEST_UID`, `KIT_TEST_GID`, and `KIT_TEST_DOCKER_GID` for
|
|
2217
|
-
the host when they differ from 1001, 1001, and 112. It starts only test-owned
|
|
2218
|
-
workspaces.
|
|
2219
|
-
|
|
2220
|
-
```sh
|
|
2221
|
-
docker compose -f test/compose.yaml build kit
|
|
2222
|
-
docker compose -f test/compose.yaml run --rm kit npm test
|
|
2223
|
-
docker compose -f test/compose.yaml run --rm kit npm run build
|
|
2224
|
-
```
|
|
2225
|
-
|
|
2226
|
-
`test/pair/run.sh` boots real Core, Auth and PostgreSQL and runs the installed
|
|
2227
|
-
package against them. `HOUSE_CORE_ROOT` names the Core checkout under proof and
|
|
2228
|
-
`HOUSE_AUTH_ROOT` names an Auth checkout; the run exports the exact Auth revision
|
|
2229
|
-
Core's own `scripts/auth-pair-source.mjs` pins into `tmp/auth-pinned` and pairs
|
|
2230
|
-
that revision rather than whatever the checkout's working tree carries. Set
|
|
2231
|
-
`HOUSE_AUTH_REVISION` to name another revision when Core's pin trails the Auth
|
|
2232
|
-
contract that Core revision already requires. Both producer databases are
|
|
2233
|
-
dropped and made again before either migrates, so a run never inherits the
|
|
2234
|
-
schema another Core revision left behind. Every run prints the three revisions
|
|
2235
|
-
it paired and writes them into each evidence document.
|
|
2236
|
-
|
|
2237
|
-
The installed fixture in `test/fixtures/migrator.mjs` uses only local material
|
|
2238
|
-
and is excluded from the published package. Its paired proof runs from Kit:
|
|
2239
|
-
|
|
2240
|
-
```sh
|
|
2241
|
-
HOUSE_CORE_ROOT=<committed-core-snapshot> HOUSE_AUTH_ROOT=<committed-auth-snapshot> \
|
|
2242
|
-
KIT_PAIR_SPECS=/kit/test/pair/migrator.vitest.ts bash test/pair/run.sh
|
|
2243
|
-
```
|
|
2244
|
-
|
|
2245
|
-
The pair boots real Core, Auth, and PostgreSQL. It covers Room and private
|
|
2246
|
-
mounts, separately authorized models, House's typed refusal, exact
|
|
2247
|
-
attachment retrieval, failed storage, mounted reports, occupied mounts and the
|
|
2248
|
-
confirmed unmount. As in the other installed-CLI pair tests, the fixture expires
|
|
2249
|
-
the previous control lease before starting the next isolated command. Evidence
|
|
2250
|
-
is written to `tmp/delivery-evidence/migrator-pair.json` with producer revisions
|
|
2251
|
-
and the installed package digest.
|
|
2252
|
-
|
|
2253
|
-
Explicit sync has its own paired proof:
|
|
2254
|
-
|
|
2255
|
-
```sh
|
|
2256
|
-
HOUSE_CORE_ROOT=<core checkout> HOUSE_AUTH_ROOT=<auth checkout> \
|
|
2257
|
-
KIT_PAIR_SPECS=/kit/test/pair/sync.vitest.ts bash test/pair/run.sh
|
|
2258
|
-
```
|
|
2259
|
-
|
|
2260
|
-
It proves that an accepted `submit`, answered or recovered after a lost answer,
|
|
2261
|
-
leaves each file's `_provenance` sidecar equal to the provenance House serves,
|
|
2262
|
-
and that a pending submission House answers `operation_expired` is pulled first,
|
|
2263
|
-
then sent again under a new identifier only if it never landed. Evidence is
|
|
2264
|
-
written to `tmp/delivery-evidence/sync-pair.json`.
|
|
2265
|
-
|
|
2266
|
-
The How-we-work reader has its own paired proof:
|
|
2267
|
-
|
|
2268
|
-
```sh
|
|
2269
|
-
HOUSE_CORE_ROOT=<core checkout> HOUSE_AUTH_ROOT=<auth checkout> \
|
|
2270
|
-
KIT_PAIR_SPECS=/kit/test/pair/how-we-work.vitest.ts bash test/pair/run.sh
|
|
2271
|
-
```
|
|
2272
|
-
|
|
2273
|
-
It writes a document longer than two of House's cut shell replies and proves
|
|
2274
|
-
the installed reader returns its whole text by running each printed
|
|
2275
|
-
continuation. Evidence is written to `tmp/delivery-evidence/how-we-work-pair.json`.
|
|
2276
|
-
|
|
2277
|
-
The retained-original consumers have their own paired proofs:
|
|
2278
|
-
|
|
2279
|
-
```sh
|
|
2280
|
-
HOUSE_CORE_ROOT=<core checkout> HOUSE_AUTH_ROOT=<auth checkout> \
|
|
2281
|
-
KIT_PAIR_SPECS="/kit/test/pair/source-originals.vitest.ts \
|
|
2282
|
-
/kit/test/pair/conversation-originals.vitest.ts \
|
|
2283
|
-
/kit/test/pair/conversation-house-storage.vitest.ts" bash test/pair/run.sh
|
|
2284
|
-
```
|
|
61
|
+
- [Linux](docs/linux.md)
|
|
62
|
+
- [macOS](docs/macos.md)
|
|
63
|
+
- [Windows](docs/windows.md)
|
|
64
|
+
- [Configuration](docs/configuration.md)
|
|
65
|
+
- [Google sources](docs/google.md)
|
|
66
|
+
- [Telegram bot](docs/telegram.md)
|
|
2285
67
|
|
|
2286
|
-
|
|
2287
|
-
installed Kit: it proves that the connector receives its own authorization,
|
|
2288
|
-
selection and cursor and never a House credential, that an unreachable House
|
|
2289
|
-
leaves the checkpoint where it was and converges on reconnect, that two Room
|
|
2290
|
-
owners hold independently owned retained copies of one delivery while one
|
|
2291
|
-
owner's two Rooms share a Record, that a failed save is truthful and a later
|
|
2292
|
-
byte retry publishes no second Capture and no substitute version, that an
|
|
2293
|
-
unauthorized Room destination stays outstanding while acquisition health stays
|
|
2294
|
-
its own, that an explicit backfill bound still holds in the installed package,
|
|
2295
|
-
and that interrupting the Environment stops the receiver without advancing the
|
|
2296
|
-
checkpoint or being woken again. After the Environment's authority is revoked
|
|
2297
|
-
its next capture is refused.
|
|
68
|
+
## License
|
|
2298
69
|
|
|
2299
|
-
|
|
2300
|
-
installed personal-bot gateway and Worker over House storage. Both prove an
|
|
2301
|
-
incoming accepted attachment retained through the shared byte client
|
|
2302
|
-
independently of transport, a failed save and its retry leaving every accepted
|
|
2303
|
-
message identity unchanged, the Run settling and its temporary media cache being
|
|
2304
|
-
disposed, the owner downloading the exact original through House afterwards, and
|
|
2305
|
-
the Environment's next protected act being refused once its authority is gone.
|
|
2306
|
-
A file a Session sends to its owner's conversation through `house
|
|
2307
|
-
upload_attachment` is proven by Core's pair lane. Evidence is written
|
|
2308
|
-
to `tmp/delivery-evidence/source-originals-pair.json`,
|
|
2309
|
-
`conversation-originals-pair.json` and `conversation-house-storage-pair.json`.
|
|
70
|
+
[MIT](LICENSE)
|