@agentshouse/kit 0.1.0-alpha.35 → 0.1.0-alpha.37

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