@agentshouse/kit 0.1.0-alpha.16 → 0.1.0-alpha.18

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
@@ -10,7 +10,7 @@ On Linux `amd64` or `arm64` with a working Docker Engine, run the published,
10
10
  versioned bootstrap once:
11
11
 
12
12
  ```sh
13
- bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.16/connect-linux.sh)
13
+ bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.18/connect-linux.sh)
14
14
  ```
15
15
 
16
16
  The release asset pins one immutable multi-architecture image digest. It creates
@@ -105,6 +105,14 @@ Forwarding installs no host-native Kit and gives the container no Docker socket
105
105
  or further mount; it refuses when no Kit is installed or the installed one
106
106
  differs from this bootstrap's.
107
107
 
108
+ The owner's working copy push is forwarded the same way: run
109
+ `bash <(curl -fsSL …/connect-linux.sh) house git push [commit] --owner` from a
110
+ directory beneath the workspace root, such as a Room copy under
111
+ `working-copies/rooms/`. The bootstrap runs the container's `house` in the
112
+ matching directory beneath `/agents/house`, through the resident's lease when
113
+ the container runs, and refuses a directory outside the workspace before
114
+ anything reaches House.
115
+
108
116
  ## Building the package
109
117
 
110
118
  The repository checkout is the development source. Build the package
@@ -760,11 +768,13 @@ prompt's stop reason through the contract's own table to one of four turn
760
768
  outcomes: completed, interrupted, refused by the provider, or exhausted. A stop
761
769
  reason the contract does not name, including a vendor-specific one a different
762
770
  contract names, fails the turn as `kit_adapter_contract_mismatch` rather than
763
- being guessed. It sends the request
764
- as the first prompt block, followed by current House guidance fetched through
765
- the Run connection. Missing or unreadable guidance refuses the turn; the
766
- launcher does not copy the private Routine instruction into the acquisition
767
- request. It forwards only adapter-classified User-visible assistant
771
+ being guessed. After committing the Run, and again on every resumption, the
772
+ Kit reads what the Session was started for from House's
773
+ `/kit/runs/<run>/request` with the Session credential: the Routine Request with
774
+ its input, references, requester and the pinned revision's instruction, or the
775
+ sealed conversation turn. It sends that as the first prompt block, followed by
776
+ current House guidance fetched through the Run connection, so the Agent calls
777
+ nothing to learn it. An unreadable request or guidance refuses the turn. It forwards only adapter-classified User-visible assistant
768
778
  text back over the local channel, and leaves reasoning, tool traces, and
769
779
  terminal output where they are. The supervised process's own House calls travel
770
780
  that same channel through the `house` CLI. The Kit holds the WorkerRunToken, replaces it in place when
@@ -827,7 +837,9 @@ nonzero exit status. The CLI holds
827
837
  no credential: it calls over its Session's bridge, which attaches the Session
828
838
  credential and refuses a caller without that Session's nonce as
829
839
  `kit_bridge_unauthorized`. House network calls use only the selected
830
- `2026-07-28` profile.
840
+ `2026-07-28` profile. `house git push` is the one local verb House does not list;
841
+ [working copies](#working-copies) describe it, and `house --help` and
842
+ `house git push --help` explain it.
831
843
 
832
844
  Two verbs name files on the Session's own computer: `upload_attachment` takes
833
845
  one `path`, and `append_record` takes `attachments` as local paths. The CLI
@@ -910,8 +922,8 @@ receiving a complete byte stream, validating any reported size, and writing the
910
922
  file. A lost acknowledgement preserves the completed local file for House's
911
923
  idempotent admission and later cleanup. Failed files remain pending; House's
912
924
  all-files-ready gate keeps their turn unprocessed.
913
- The provider receives absolute local paths beside the owning messages, both in
914
- its initial prompt and in `session_get_request`. The cache persists across
925
+ The provider receives absolute local paths beside the owning messages in its
926
+ first prompt. The cache persists across
915
927
  turns; only House-authorized terminal Run cleanup removes its Run directory.
916
928
 
917
929
  The accepted attachment's retained original is separate from that cache. The Kit
@@ -936,8 +948,8 @@ outcome it handled, and exits non-zero while any of them is unsaved.
936
948
  One `kit worker run` then takes one House-authorized resumption, or otherwise
937
949
  acquires one waiting Run, and supervises exactly that WorkerSession. A resumed
938
950
  conversation loads its retained native provider session through ACP
939
- `session/load`, receives the current input from House through
940
- `session_get_request`, and executes one turn. Historical output replayed during
951
+ `session/load`, receives the current input House answers for the Run in its
952
+ first prompt, and executes one turn. Historical output replayed during
941
953
  loading is not emitted as new assistant output. Missing or rejected provider
942
954
  continuation never starts a replacement provider conversation in the same Run.
943
955
 
@@ -1246,14 +1258,54 @@ current grant. Every other file is read-only House content: the generated
1246
1258
  grants anything, and House refuses a change to a read-only path however it is
1247
1259
  committed.
1248
1260
 
1249
- Local edits, staging and commits change nothing in House. `refs/house/received`
1250
- records the House content last received. The per-file manifest, with each
1251
- file's House revision, digest and writability, and the commit it corresponds to
1252
- stay in Kit home under `state/working-copies/`, outside the repository. A local
1253
- commit is a local tree, never a House revision.
1261
+ Local edits, staging and commits change nothing in House until a commit is
1262
+ pushed. `refs/house/received` records the House content last received; each
1263
+ commit Kit wrote there, and each commit it acknowledged for a push, is a House
1264
+ state. The House revision of every file content Kit received, keyed by path and
1265
+ Git blob, stays in Kit home under `state/working-copies/`, outside the
1266
+ repository. A local commit is a local tree, never a House revision.
1267
+
1268
+ `house git push [commit]`, run anywhere inside a copy or one of its linked
1269
+ worktrees, submits one commit, `HEAD` by default, resolved once. Kit compares
1270
+ that commit's tree with the newest House state in its history, so every linked
1271
+ worktree keeps its own base, and sends the difference as one ordinary House edit:
1272
+ a changed field, section or preamble against its own prior value, any other
1273
+ change as the whole file against its exact revision, a new file as a create and
1274
+ a committed deletion as a removal. Staged and unstaged work, and files absent
1275
+ only from the working tree, are never sent. Native `git push` reaches no House
1276
+ remote. Inside an Agent Session the push goes through that Session's bridge,
1277
+ under its own authority and provenance; outside one it needs
1278
+ `house git push [commit] --owner`, which the resident Kit carries out under its
1279
+ control lease when a resident runs, and which otherwise acquires and releases
1280
+ its own. A Session refuses `--owner`, and a missing bridge is never replaced by
1281
+ owner authority.
1282
+
1283
+ House decides before any local Git step. A refusal changes nothing in House;
1284
+ after `edit_conflict` Kit records the current content House names for the
1285
+ refused paths in `refs/house/received`, reading by revision any content the
1286
+ refusal does not carry, so the agent merges or rebases onto it with native Git,
1287
+ commits, and pushes again. After acceptance Kit records House's result for the
1288
+ pushed paths, the contents an owner push receives or each accepted revision
1289
+ read by name, in a commit that covers the pushed one and in
1290
+ `refs/house/received`; a read-only path House regenerates, such as the Wiki
1291
+ index, stays out of Git. Kit then
1292
+ fast-forwards a clean branch still at the pushed commit, or rebases only its
1293
+ later commits. A result not yet readable, uncommitted tracked changes,
1294
+ a moved branch or a rebase conflict leave the branch as it was, with no stash or
1295
+ reset, and the push answers that House accepted it while local integration is
1296
+ pending; the next `house git push` finishes that integration without another
1297
+ House edit. A push whose answer was lost is recorded in Kit home before it is
1298
+ sent, and the next push by the same Session, or by the owner for an owner push,
1299
+ resends it under its original operation. Another caller cannot
1300
+ recover it: while the pushing Session lives it is refused as
1301
+ `kit_working_copy_push_pending`, and once that Session has ended Kit reads House
1302
+ again and drops it. When a retained push's operation has expired, Kit reads the
1303
+ Room again: if House holds the pushed content the push finishes as accepted,
1304
+ otherwise it is dropped and the next push submits it as a new operation.
1254
1305
 
1255
1306
  Deselecting a Room, or losing read access to it, marks its copy inactive:
1256
- launches omit it and nothing local is deleted. Selecting it again reads the Room
1307
+ launches omit it, a new push is refused as `kit_working_copy_inactive`, and
1308
+ nothing local is deleted; a push already sent still settles and integrates. Selecting it again reads the Room
1257
1309
  again, advances `refs/house/received`, and replaces only the read-only content
1258
1310
  Kit wrote, never the branch, the index or a tracked file. A Room the owner may
1259
1311
  read without sync is refused as `operation_denied` and stays uncopied, while its
@@ -9,6 +9,7 @@ MANUAL=0
9
9
  WORKSPACE_SELECTED=0
10
10
  HOUSE_SELECTED=0
11
11
  FORWARDING=0
12
+ FORWARDED=kit
12
13
  FORWARD=()
13
14
 
14
15
  refuse() {
@@ -41,8 +42,9 @@ start_resident() {
41
42
  docker run -d --name "$NAME" --restart unless-stopped --user "$(id -u):$(id -g)" --mount "$KIT_HOME_MOUNT" --mount "$WORKSPACE_MOUNT" --label "agentshouse.house=$HOUSE" --env HOME=/kit-home --env HOUSE_KIT_HOME=/kit-home --env HOUSE_KIT_CONTAINER=1 "$IMAGE" resident --house "$HOUSE" >/dev/null
42
43
  }
43
44
 
44
- if [[ "${1:-}" == kit ]]; then
45
+ if [[ "${1:-}" == kit || "${1:-}" == house ]]; then
45
46
  FORWARDING=1
47
+ FORWARDED="$1"
46
48
  FORWARD=("${@:2}")
47
49
  set --
48
50
  fi
@@ -129,10 +131,18 @@ if docker container inspect "$NAME" >/dev/null 2>&1; then
129
131
  [[ "$(docker inspect --format '{{index .Config.Labels "agentshouse.house"}}' "$NAME")" == "$HOUSE" ]] || refuse 'the installed Kit uses another House origin'
130
132
  [[ -f "$KIT_HOME/credential" && -f "$KIT_HOME/environment" && -f "$KIT_HOME/house" ]] || refuse 'the installed Kit has no enrolled authority'
131
133
  if ((FORWARDING)); then
132
- if [[ "${FORWARD[0]:-}" == login ]]; then
134
+ if [[ "$FORWARDED" == kit && "${FORWARD[0]:-}" == login ]]; then
133
135
  login_kit "${FORWARD[@]:1}"
134
136
  exit 0
135
137
  fi
138
+ PLACED=()
139
+ ENTRY=()
140
+ if [[ "$FORWARDED" == house ]]; then
141
+ HERE=$(pwd -P)
142
+ [[ "$HERE" == "$WORKSPACE" || "$HERE" == "$WORKSPACE"/* ]] || refuse "the current directory is outside the workspace $WORKSPACE"
143
+ PLACED=(--workdir "/agents/house${HERE#"$WORKSPACE"}")
144
+ ENTRY=(--entrypoint house)
145
+ fi
136
146
  TERMINAL=()
137
147
  if [[ -t 0 && -t 1 ]]; then TERMINAL=(-t); fi
138
148
  if [[ -t 0 && ! -t 1 ]]; then
@@ -141,9 +151,9 @@ if docker container inspect "$NAME" >/dev/null 2>&1; then
141
151
  stty -echo
142
152
  fi
143
153
  if [[ "$(docker inspect --format '{{.State.Running}}' "$NAME")" == true ]]; then
144
- docker exec -i "${TERMINAL[@]}" "$NAME" kit "${FORWARD[@]}"
154
+ docker exec -i "${TERMINAL[@]}" "${PLACED[@]}" "$NAME" "$FORWARDED" "${FORWARD[@]}"
145
155
  else
146
- "${DOCKER_RUN[@]:0:2}" -i "${TERMINAL[@]}" "${DOCKER_RUN[@]:2}" "${FORWARD[@]}"
156
+ "${DOCKER_RUN[@]:0:2}" -i "${TERMINAL[@]}" "${PLACED[@]}" "${ENTRY[@]}" "${DOCKER_RUN[@]:2}" "${FORWARD[@]}"
147
157
  fi
148
158
  exit
149
159
  fi
package/bootstrap.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "0.1.0-alpha.16",
3
+ "version": "0.1.0-alpha.18",
4
4
  "hosts": {
5
5
  "linux": {
6
- "command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.16/connect-linux.sh)",
6
+ "command": "bash <(curl -fsSL https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.18/connect-linux.sh)",
7
7
  "asset": {
8
8
  "name": "connect-linux.sh",
9
- "url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.16/connect-linux.sh"
9
+ "url": "https://github.com/agentshouse/kit/releases/download/v0.1.0-alpha.18/connect-linux.sh"
10
10
  },
11
11
  "workspace": {
12
12
  "argument": "--workspace",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentshouse/kit",
3
- "version": "0.1.0-alpha.16",
3
+ "version": "0.1.0-alpha.18",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,6 +9,7 @@
9
9
  "private": false,
10
10
  "type": "module",
11
11
  "bin": {
12
+ "house": "src/session-bin/house",
12
13
  "kit": "bin/kit.js"
13
14
  },
14
15
  "exports": {
@@ -29,16 +29,3 @@ export async function commandHouseInstructions(bridge) {
29
29
  }
30
30
  return frame.result.instructions;
31
31
  }
32
-
33
- export async function commandHouseRequest(bridge) {
34
- const frame = await commandHouseReply(bridge, { method: "tools/call", params: { name: "session_get_request", arguments: {} } });
35
- if (frame.error !== undefined || frame.result?.isError === true) throw new KitRefusal("kit_run_input_unreadable");
36
- try {
37
- const content = frame.result.content;
38
- const input = JSON.parse(content.find((item) => item.type === "text").text);
39
- if (typeof input !== "object" || input === null || Array.isArray(input)) throw new Error();
40
- return input;
41
- } catch {
42
- throw new KitRefusal("kit_run_input_unreadable");
43
- }
44
- }
package/src/bridge.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  import { operationId } from "./operation.js";
19
19
  import { houseFailed } from "./refusal.js";
20
20
 
21
- function bridgedFrame(request, operation) {
21
+ export function bridgedFrame(request, operation) {
22
22
  if (typeof request.method !== "string") return request;
23
23
  const params = typeof request.params === "object" && request.params !== null ? request.params : {};
24
24
  const declared = typeof params._meta === "object" && params._meta !== null ? params._meta : {};
package/src/contract.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { CLAUDE_PASTED_CODE, CODEX_DEVICE_CODE, GROK_DEVICE_CODE } from "./agent-logins.js";
2
2
 
3
- export const KIT_CONTRACT_VERSION = "2026-09-28-working-copies";
3
+ export const KIT_CONTRACT_VERSION = "2026-09-28-first-prompt";
4
4
  export const KIT_CAPTURE_BATCH_MAX = 100;
5
5
  export const COMMAND_INPUT_MAX_BYTES = 4 * 1024;
6
6
  export const AUTHORIZATION_OUTPUT_MAX_BYTES = 4 * 1024;
@@ -40,6 +40,10 @@ export function runOutcomePath(run) {
40
40
  return `/kit/runs/${encodeURIComponent(run)}/outcome`;
41
41
  }
42
42
 
43
+ export function runRequestPath(run) {
44
+ return `/kit/runs/${encodeURIComponent(run)}/request`;
45
+ }
46
+
43
47
  export const KIT_SESSIONS_REPORT_PATH = "/kit/sessions/report";
44
48
  export const KIT_SESSIONS_REPORT_MAX = 256;
45
49
 
@@ -288,17 +292,21 @@ export const KIT_DOOR_DISCOVER_PATH = "/kit/door/discover";
288
292
  export const KIT_DOOR_BOOTSTRAP_PATH = "/kit/door/bootstrap";
289
293
  export const KIT_DOOR_ENUMERATE_PATH = "/kit/door/enumerate";
290
294
  export const KIT_DOOR_BATCH_PATH = "/kit/door/batch";
295
+ export const KIT_DOOR_REVISIONS_PATH = "/kit/door/revisions";
291
296
  export const EXPLICIT_SYNC_VERSION = "explicit-sync/2";
292
297
  export const KIT_DOOR_UNMOUNT_PATH = "/kit/door/unmount";
293
298
 
294
299
  export const KIT_WORKING_COPY_SELECTION_PATH = "/kit/working-copy/selection";
295
- export const WORKING_COPY_VERSION = "working-copy-state/1";
300
+ export const WORKING_COPY_VERSION = "working-copy-state/2";
296
301
  export const WORKING_COPIES_HOME = "working-copies";
297
302
  export const WORKING_COPY_ROOM_HOMES = ["capture", "library", "collaboration"];
298
303
  export const WORKING_COPY_PRIVATE_HOMES = ["library", "collaboration", "rooms", "agents", "house"];
299
304
  export const WORKING_COPY_RECEIVED_REF = "refs/house/received";
300
305
  export const WORKING_COPY_BRANCH = "main";
301
306
  export const ROOM_REFERENCE = /^r_[0-9a-z]+\.[0-9a-z]$/;
307
+ export const WORKING_COPY_PUSH_METHOD = "house/git/push";
308
+ export const WORKING_COPY_PUSH_HELP =
309
+ "Submit a Room commit to House; default HEAD. Use --owner only outside an Agent Session. Native git push is not a House remote.";
302
310
 
303
311
  export const MIGRATOR_CONTRACT = "kit-migrator/1";
304
312
  export const MIGRATOR_CATEGORY = "migrator";
package/src/house-main.js CHANGED
@@ -1,9 +1,19 @@
1
- import { resolve } from "node:path";
2
- import { BRIDGE_NONCE_ENVIRONMENT, BRIDGE_SOCKET_ENVIRONMENT } from "./contract.js";
3
- import { bridgeCall } from "./mcp.js";
4
- import { KitRefusal } from "./refusal.js";
1
+ import { homedir } from "node:os";
2
+ import { join, resolve } from "node:path";
3
+ import {
4
+ BRIDGE_NONCE_ENVIRONMENT,
5
+ BRIDGE_SOCKET_ENVIRONMENT,
6
+ WORKING_COPY_PUSH_HELP,
7
+ WORKING_COPY_PUSH_METHOD,
8
+ } from "./contract.js";
9
+ import { storedHouse } from "./login.js";
10
+ import { answerText, bridgeCall } from "./mcp.js";
11
+ import { formatCommandError, KitRefusal } from "./refusal.js";
12
+ import { ownerPush } from "./working-copy.js";
5
13
 
6
14
  const HELP = ["--help", "-h"];
15
+ const OWNER = "--owner";
16
+ const GIT_PUSH_USAGE = `usage: house git push [commit] [${OWNER}]`;
7
17
  const usage = (verb) => `usage: house ${verb} ['<arguments as one JSON object>' | - for standard input]`;
8
18
 
9
19
  async function answered(method, params) {
@@ -49,10 +59,59 @@ function located(verb, args) {
49
59
  return args;
50
60
  }
51
61
 
62
+ function printed(result) {
63
+ const text = answerText(result);
64
+ return result?.isError === true ? { err: text, status: 1 } : { out: text };
65
+ }
66
+
67
+ async function ownerPushed(request) {
68
+ const directory = process.env.HOUSE_KIT_HOME ?? join(homedir(), ".house-kit");
69
+ const cancellation = new AbortController();
70
+ const cancel = () => cancellation.abort();
71
+ process.once("SIGINT", cancel);
72
+ process.once("SIGTERM", cancel);
73
+ try {
74
+ const pushed = await ownerPush(
75
+ { house: await storedHouse(directory), directory, signal: cancellation.signal },
76
+ request,
77
+ );
78
+ return pushed.refused ? { err: pushed.text, status: 1 } : { out: pushed.text };
79
+ } catch (refusal) {
80
+ return { err: formatCommandError(refusal), status: 1 };
81
+ } finally {
82
+ process.removeListener("SIGINT", cancel);
83
+ process.removeListener("SIGTERM", cancel);
84
+ }
85
+ }
86
+
87
+ async function gitPush([action, ...rest]) {
88
+ if (action !== "push") throw new KitRefusal("kit_house_arguments_invalid", GIT_PUSH_USAGE);
89
+ if (rest.some((argument) => HELP.includes(argument))) return { out: `${GIT_PUSH_USAGE}\n\n${WORKING_COPY_PUSH_HELP}` };
90
+ const owner = rest.includes(OWNER);
91
+ const named = rest.filter((argument) => argument !== OWNER);
92
+ if (named.length > 1 || named.some((argument) => argument.startsWith("-"))) {
93
+ throw new KitRefusal("kit_house_arguments_invalid", GIT_PUSH_USAGE);
94
+ }
95
+ const request = { cwd: process.cwd(), commit: named[0] ?? "HEAD" };
96
+ if (process.env[BRIDGE_SOCKET_ENVIRONMENT] === undefined) {
97
+ if (!owner) throw new KitRefusal("kit_bridge_unavailable");
98
+ return ownerPushed(request);
99
+ }
100
+ if (owner) throw new KitRefusal("kit_house_arguments_invalid", `${OWNER} is only for use outside an Agent Session`);
101
+ return printed(await answered(WORKING_COPY_PUSH_METHOD, request));
102
+ }
103
+
52
104
  async function house([verb, argument, ...rest]) {
105
+ if (verb === "git") return gitPush([argument, ...rest].filter((part) => part !== undefined));
53
106
  if (verb === undefined || HELP.includes(verb)) {
54
107
  const tools = await listed();
55
- return { out: [usage("<verb>"), ...tools.map((tool) => `${tool.name}\n${tool.description}`)].join("\n\n") };
108
+ return {
109
+ out: [
110
+ usage("<verb>"),
111
+ ...tools.map((tool) => `${tool.name}\n${tool.description}`),
112
+ `git push [commit]\n${WORKING_COPY_PUSH_HELP}`,
113
+ ].join("\n\n"),
114
+ };
56
115
  }
57
116
  if (HELP.includes(argument)) {
58
117
  const tool = (await listed()).find((one) => one.name === verb);
@@ -61,9 +120,7 @@ async function house([verb, argument, ...rest]) {
61
120
  }
62
121
  if (rest.length > 0) throw new KitRefusal("kit_house_arguments_invalid", usage(verb));
63
122
  const args = located(verb, await argumentsOf(verb, argument));
64
- const result = await answered("tools/call", { name: verb, arguments: args });
65
- const text = (result?.content ?? []).filter((item) => item.type === "text").map((item) => item.text).join("\n");
66
- return result?.isError === true ? { err: text, status: 1 } : { out: text };
123
+ return printed(await answered("tools/call", { name: verb, arguments: args }));
67
124
  }
68
125
 
69
126
  house(process.argv.slice(2)).then(