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

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.17/connect-linux.sh)
14
14
  ```
15
15
 
16
16
  The release asset pins one immutable multi-architecture image digest. It creates
@@ -760,11 +760,13 @@ prompt's stop reason through the contract's own table to one of four turn
760
760
  outcomes: completed, interrupted, refused by the provider, or exhausted. A stop
761
761
  reason the contract does not name, including a vendor-specific one a different
762
762
  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
763
+ being guessed. After committing the Run, and again on every resumption, the
764
+ Kit reads what the Session was started for from House's
765
+ `/kit/runs/<run>/request` with the Session credential: the Routine Request with
766
+ its input, references, requester and the pinned revision's instruction, or the
767
+ sealed conversation turn. It sends that as the first prompt block, followed by
768
+ current House guidance fetched through the Run connection, so the Agent calls
769
+ nothing to learn it. An unreadable request or guidance refuses the turn. It forwards only adapter-classified User-visible assistant
768
770
  text back over the local channel, and leaves reasoning, tool traces, and
769
771
  terminal output where they are. The supervised process's own House calls travel
770
772
  that same channel through the `house` CLI. The Kit holds the WorkerRunToken, replaces it in place when
@@ -910,8 +912,8 @@ receiving a complete byte stream, validating any reported size, and writing the
910
912
  file. A lost acknowledgement preserves the completed local file for House's
911
913
  idempotent admission and later cleanup. Failed files remain pending; House's
912
914
  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
915
+ The provider receives absolute local paths beside the owning messages in its
916
+ first prompt. The cache persists across
915
917
  turns; only House-authorized terminal Run cleanup removes its Run directory.
916
918
 
917
919
  The accepted attachment's retained original is separate from that cache. The Kit
@@ -936,8 +938,8 @@ outcome it handled, and exits non-zero while any of them is unsaved.
936
938
  One `kit worker run` then takes one House-authorized resumption, or otherwise
937
939
  acquires one waiting Run, and supervises exactly that WorkerSession. A resumed
938
940
  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
941
+ `session/load`, receives the current input House answers for the Run in its
942
+ first prompt, and executes one turn. Historical output replayed during
941
943
  loading is not emitted as new assistant output. Missing or rejected provider
942
944
  continuation never starts a replacement provider conversation in the same Run.
943
945
 
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.17",
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.17/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.17/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.17",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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/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
 
package/src/house.js CHANGED
@@ -29,6 +29,7 @@ import {
29
29
  MCP_PROTOCOL_VERSION,
30
30
  WORKER_RUN_TOKEN_MARGIN_MS,
31
31
  runOutcomePath,
32
+ runRequestPath,
32
33
  secretInputPath,
33
34
  SECRET_VALUE_MAX,
34
35
  sessionInteractionsPath,
@@ -734,11 +735,7 @@ export async function reportLogin(session, login, outcome, signal = session.sign
734
735
  }
735
736
 
736
737
  const envelope = declarationReader("kit_acquire_rejected");
737
-
738
- function routineInput(value, declaration) {
739
- if (typeof value !== "string" || Array.from(value).length > 20_000) envelope.refuse(declaration);
740
- return value;
741
- }
738
+ const input = declarationReader("kit_run_input_unreadable");
742
739
 
743
740
  function acquiredRoute(value, declaration) {
744
741
  const route = envelope.plain(value, declaration);
@@ -756,79 +753,82 @@ function acquiredRoute(value, declaration) {
756
753
 
757
754
  function conversationText(value, declaration) {
758
755
  if (value === null) return null;
759
- if (typeof value !== "string" || value.length > 4096) envelope.refuse(declaration);
756
+ if (typeof value !== "string" || value.length > 4096) input.refuse(declaration);
760
757
  return value;
761
758
  }
762
759
 
763
760
  function conversationMessage(value, declaration) {
764
- const message = envelope.plain(value, declaration);
761
+ const message = input.plain(value, declaration);
765
762
  const text = conversationText(message.text, `${declaration}.text`);
766
763
  const caption = conversationText(message.caption, `${declaration}.caption`);
767
- if (!Array.isArray(message.files)) envelope.refuse(`${declaration}.files`);
764
+ if (!Array.isArray(message.files)) input.refuse(`${declaration}.files`);
768
765
  const files = message.files.map((value, index) => {
769
766
  const name = `${declaration}.files[${index}]`;
770
- const file = envelope.plain(value, name);
767
+ const file = input.plain(value, name);
771
768
  if (file.bytes !== null && (!Number.isSafeInteger(file.bytes) || file.bytes < 0)) {
772
- envelope.refuse(`${name}.bytes`);
769
+ input.refuse(`${name}.bytes`);
773
770
  }
774
771
  return {
775
- path: envelope.text(file.path, `${name}.path`, 2048),
776
- media_kind: envelope.text(file.media_kind, `${name}.media_kind`, 128),
777
- media_type: file.media_type === null ? null : envelope.text(file.media_type, `${name}.media_type`, 255),
772
+ path: input.text(file.path, `${name}.path`, 2048),
773
+ media_kind: input.text(file.media_kind, `${name}.media_kind`, 128),
774
+ media_type: file.media_type === null ? null : input.text(file.media_type, `${name}.media_type`, 255),
778
775
  bytes: file.bytes,
779
776
  };
780
777
  });
781
- if (!text && !caption && files.length === 0) envelope.refuse(declaration);
778
+ if (!text && !caption && files.length === 0) input.refuse(declaration);
782
779
  return {
783
- message_id: envelope.text(message.message_id, `${declaration}.message_id`, 128),
780
+ message_id: input.text(message.message_id, `${declaration}.message_id`, 128),
784
781
  text, caption, files,
785
782
  };
786
783
  }
787
784
 
788
- function acquiredRequest(acquired, declaration) {
789
- if (acquired.kind === "conversation") {
790
- if (acquired.origin !== "user") envelope.refuse(`${declaration}.origin`);
791
- const conversation = envelope.plain(acquired.conversation, `${declaration}.conversation`);
792
- const turn = envelope.plain(conversation.turn, `${declaration}.conversation.turn`);
785
+ function runRequest(answer) {
786
+ const request = input.plain(answer, "request");
787
+ if (request.kind === "conversation") {
788
+ if (request.origin !== "user") input.refuse("request.origin");
789
+ const turn = input.plain(request.turn, "request.turn");
793
790
  return {
791
+ kind: "conversation",
794
792
  origin: "user",
795
- conversation: {
796
- turn: {
797
- id: envelope.text(turn.id, `${declaration}.conversation.turn.id`, 128),
798
- sequence: envelope.positiveCount(turn.sequence, `${declaration}.conversation.turn.sequence`),
799
- messages: envelope.filled(turn.messages, `${declaration}.conversation.turn.messages`)
800
- .map((message, index) => conversationMessage(message, `${declaration}.conversation.turn.messages[${index}]`)),
801
- },
793
+ turn: {
794
+ id: input.text(turn.id, "request.turn.id", 128),
795
+ sequence: input.positiveCount(turn.sequence, "request.turn.sequence"),
796
+ messages: input.filled(turn.messages, "request.turn.messages")
797
+ .map((message, index) => conversationMessage(message, `request.turn.messages[${index}]`)),
802
798
  },
803
799
  };
804
800
  }
805
- if (acquired.kind !== "routine") envelope.refuse(`${declaration}.kind`);
806
- const request = envelope.plain(acquired.request, `${declaration}.request`);
807
- const routine = envelope.plain(acquired.routine, `${declaration}.routine`);
801
+ if (request.kind !== "routine") input.refuse("request.kind");
802
+ const routine = input.plain(request.routine, "request.routine");
803
+ if (typeof request.payload !== "string" || Array.from(request.payload).length > 20_000) input.refuse("request.payload");
804
+ if (!Array.isArray(request.references)) input.refuse("request.references");
808
805
  return {
809
- origin: envelope.text(acquired.origin, `${declaration}.origin`, 32),
806
+ kind: "routine",
807
+ origin: input.text(request.origin, "request.origin", 32),
810
808
  routine: {
811
- name: envelope.text(routine.name, `${declaration}.routine.name`, 256),
812
- version: envelope.positiveCount(routine.version, `${declaration}.routine.version`),
809
+ name: input.text(routine.name, "request.routine.name", 256),
810
+ version: input.positiveCount(routine.version, "request.routine.version"),
813
811
  },
814
- payload: routineInput(request.payload, `${declaration}.request.payload`),
815
- requested_by: envelope.text(
816
- request.requested_by,
817
- `${declaration}.request.requested_by`,
818
- 128,
819
- ),
820
- requested_at: envelope.text(
821
- request.requested_at,
822
- `${declaration}.request.requested_at`,
823
- 64,
824
- ),
825
- deliver_at:
826
- request.deliver_at === null
827
- ? null
828
- : envelope.text(request.deliver_at, `${declaration}.request.deliver_at`, 64),
812
+ instruction: input.text(request.instruction, "request.instruction", 256 * 1024),
813
+ payload: request.payload,
814
+ references: request.references.map((reference, index) => input.plain(reference, `request.references[${index}]`)),
815
+ requested_by: input.text(request.requested_by, "request.requested_by", 128),
816
+ requested_at: input.text(request.requested_at, "request.requested_at", 64),
817
+ deliver_at: request.deliver_at === null ? null : input.text(request.deliver_at, "request.deliver_at", 64),
829
818
  };
830
819
  }
831
820
 
821
+ export async function readRunRequest(session, run, token, signal = session.signal) {
822
+ return runRequest(await houseRequest(
823
+ session.house,
824
+ `Bearer ${token}`,
825
+ runRequestPath(run),
826
+ {},
827
+ session.dependencies,
828
+ signal,
829
+ ));
830
+ }
831
+
832
832
  function acquiredAssignment(value, declaration) {
833
833
  const acquired = envelope.plain(value, declaration);
834
834
  const bridge = envelope.plain(acquired.session, `${declaration}.session`);
@@ -857,7 +857,6 @@ function acquiredAssignment(value, declaration) {
857
857
  token: envelope.text(bridge.bridge_token, `${declaration}.session.bridge_token`, 512),
858
858
  expiresAt: Date.parse(expiresAt),
859
859
  },
860
- request: acquiredRequest(acquired, declaration),
861
860
  };
862
861
  }
863
862
 
package/src/inbound.js CHANGED
@@ -134,13 +134,9 @@ export async function reconcileInbound(directory, session) {
134
134
  }
135
135
 
136
136
  export async function localRequest(directory, run, request) {
137
- const turn = request?.conversation?.turn ?? (request?.kind === "conversation" ? request.turn : null);
138
- if (turn === null || turn === undefined) return request;
137
+ if (request.kind !== "conversation") return request;
139
138
  const result = structuredClone(request);
140
- const localTurn = result.conversation?.turn ?? result.turn;
141
- if (!Array.isArray(localTurn.messages)) throw new KitRefusal("kit_run_input_unreadable");
142
- for (const message of localTurn.messages) {
143
- if (!Array.isArray(message.files)) throw new KitRefusal("kit_run_input_unreadable");
139
+ for (const message of result.turn.messages) {
144
140
  for (const file of message.files) {
145
141
  const parts = typeof file.path === "string" ? file.path.split("/") : [];
146
142
  if (parts.length !== 4 || parts[0] !== ROOT || parts[1] !== run || !UUID.test(parts[1]) || !UUID.test(parts[2]) || !segment(parts[3])) {
@@ -161,15 +157,3 @@ export async function localRequest(directory, run, request) {
161
157
  }
162
158
  return result;
163
159
  }
164
-
165
- export async function localReply(directory, run, frame, reply) {
166
- if (frame.method !== "tools/call" || frame.params?.name !== "session_get_request" || reply.error || reply.result?.isError) return reply;
167
- const result = structuredClone(reply);
168
- for (const item of result.result?.content ?? []) {
169
- if (item.type !== "text") continue;
170
- let input;
171
- try { input = JSON.parse(item.text); } catch { throw new KitRefusal("kit_run_input_unreadable"); }
172
- item.text = JSON.stringify(await localRequest(directory, run, input));
173
- }
174
- return result;
175
- }
package/src/launcher.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { commandHouseInstructions, commandHouseRequest } from "./adapter-mcp.js";
2
+ import { commandHouseInstructions } from "./adapter-mcp.js";
3
3
  import {
4
4
  compiledContract,
5
5
  initializeCommandAdapter,
@@ -191,13 +191,12 @@ async function runTurn(channel, start, bridge, turn) {
191
191
  loading = false;
192
192
  }
193
193
  await configureSession(acp, contract, providerSessionId, start.adapter);
194
- const request = start.request === null ? await commandHouseRequest(bridge) : start.request;
195
194
  turn.adapterSession = true;
196
195
  turn.cancel = () => acp.cancel({ sessionId: providerSessionId });
197
196
  const prompted = await acp.request("session/prompt", {
198
197
  sessionId: providerSessionId,
199
198
  prompt: [
200
- { type: "text", text: JSON.stringify(request) },
199
+ { type: "text", text: JSON.stringify(start.request) },
201
200
  { type: "text", text: instructions },
202
201
  ...workingCopyContext(start.working_copies),
203
202
  ],
package/src/worker.js CHANGED
@@ -14,7 +14,7 @@ import { openBridge } from "./bridge.js";
14
14
  import { assistantBlocks } from "./blocks.js";
15
15
  import { retryConversationFiles } from "./conversation-originals.js";
16
16
  import { openAssistantDrafts } from "./draft.js";
17
- import { localReply, localRequest, reconcileInbound } from "./inbound.js";
17
+ import { localRequest, reconcileInbound } from "./inbound.js";
18
18
  import { localFiles } from "./local-files.js";
19
19
  import { inheritedEnvironment } from "./command.js";
20
20
  import { watchKitWork } from "./kit-work-stream.js";
@@ -52,6 +52,7 @@ import {
52
52
  observeSessionStart,
53
53
  observeSessionStop,
54
54
  openSession,
55
+ readRunRequest,
55
56
  replaceSessionAuthority,
56
57
  reportContinuation,
57
58
  reportInterruptOutcome,
@@ -292,12 +293,12 @@ async function openRunBridge(context, bound, authority, start) {
292
293
  settle: handed((interaction, outcome) => settleInteraction(context, interaction, outcome)),
293
294
  forward: handed((frame, signal) =>
294
295
  localFiles(async (sent) =>
295
- localReply(context.directory, bound.run, sent, await callHouseMcp(
296
+ callHouseMcp(
296
297
  context.session,
297
298
  await sessionAuthority(context.session, bound.session, authority, signal),
298
299
  sent,
299
300
  signal,
300
- )), context.session.dependencies, signal)(frame)),
301
+ ), context.session.dependencies, signal)(frame)),
301
302
  });
302
303
  return { bridge, drafts, ended };
303
304
  }
@@ -467,7 +468,7 @@ async function prepareAssignment(context, assignment, resumed) {
467
468
  reported: null,
468
469
  };
469
470
  const authority = { token: assignment.session.token, expiresAt: assignment.session.expiresAt };
470
- const opened = await openRunBridge(context, bound, authority, {
471
+ const start = {
471
472
  working_directory: assignment.workingDirectory,
472
473
  working_copies: await appliedWorkingCopies(context),
473
474
  execution_authority: context.configuration.executionAuthority,
@@ -479,10 +480,16 @@ async function prepareAssignment(context, assignment, resumed) {
479
480
  effort: route.effort,
480
481
  environment: {},
481
482
  },
482
- request: await localRequest(context.directory, assignment.run, assignment.request),
483
+ request: null,
483
484
  provider_session: assignment.providerSessionId ?? null,
484
- });
485
- return { bound, opened };
485
+ };
486
+ const opened = await openRunBridge(context, bound, authority, start);
487
+ const request = async () => {
488
+ const token = await sessionAuthority(context.session, bound.session, authority);
489
+ start.request = await localRequest(context.directory, bound.run,
490
+ await readRunRequest(context.session, bound.run, token));
491
+ };
492
+ return { bound, opened, request };
486
493
  }
487
494
 
488
495
  async function refuseContinuation(context, command, failure) {
@@ -523,11 +530,12 @@ async function superviseAssignment(context, assignment, resumed = false) {
523
530
  if (undecided(context, refusal)) throw refusal;
524
531
  return refuseAssignment(context, assignment, failure);
525
532
  }
526
- const { bound, opened } = prepared;
533
+ const { bound, opened, request } = prepared;
527
534
  return superviseBound(context, bound, opened, async (spawn) => {
528
535
  if (!resumed) await commitRun(context.session, assignment.run);
529
536
  spawn.committed = true;
530
537
  await writeBinding(context.directory, bound);
538
+ await request();
531
539
  await createSession(context.runtime, {
532
540
  session: bound.tmux,
533
541
  workingDirectory: assignment.workingDirectory,
@@ -549,7 +557,7 @@ async function superviseResumption(context, command) {
549
557
  }
550
558
  return superviseAssignment(context, {
551
559
  ...command, agentId: held.agentId, workingDirectory: held.workingDirectory,
552
- conversation: held.conversation, providerSessionId: held.providerSessionId, request: null,
560
+ conversation: held.conversation, providerSessionId: held.providerSessionId,
553
561
  }, true);
554
562
  }
555
563