@jr2/orchestrator 0.2.1 → 0.3.0
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/package.json +3 -4
- package/src/actor.ts +5 -5
- package/src/agent.ts +2 -2
- package/src/config.ts +119 -14
- package/src/custodian.lua +312 -0
- package/src/custodian.ts +899 -0
- package/src/harness-client.ts +2 -2
- package/src/held-secrets.ts +587 -0
- package/src/http.ts +19 -16
- package/src/images.ts +11 -10
- package/src/index.ts +6 -2
- package/src/instance.ts +1 -1
- package/src/model-keys.ts +92 -0
- package/src/names.ts +30 -6
- package/src/registration.ts +1 -1
- package/src/repo-fetch.ts +1 -1
- package/src/run-host.ts +15 -13
- package/src/sandbox-kubectl.ts +71 -107
- package/src/server.ts +4 -5
- package/src/tokens.ts +9 -8
- package/src/workspace.ts +1 -1
package/src/http.ts
CHANGED
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
// carries NO domain logic of its own: every handler delegates to a single `RunHost` method, so the
|
|
3
3
|
// wire shape and the in-process API stay one behavior.
|
|
4
4
|
//
|
|
5
|
-
// TWO dialects, one primitive (ADR-0013). The Orchestrator does not speak MCP —
|
|
6
|
-
//
|
|
7
|
-
// adapters over the same
|
|
5
|
+
// TWO dialects, one primitive (ADR-0013). The Orchestrator does not speak MCP — nothing does: the
|
|
6
|
+
// Harness presents the Menu to its model itself, and reaches these routes through the Custodian in
|
|
7
|
+
// its pod, which holds the Sandbox token. What is left here are two thin adapters over the same
|
|
8
|
+
// registration table:
|
|
8
9
|
//
|
|
9
10
|
// # human / webhook / CI — the Gate resource of ADR-0011
|
|
10
11
|
// GET /runs/:id open gates: accepts + schemas + meta [Instance token]
|
|
11
12
|
// POST /runs/:id/gates/:gate/events validate + deliver [Instance token]
|
|
12
13
|
//
|
|
13
|
-
// # the Agent's
|
|
14
|
+
// # the Agent's Harness, through its Custodian, and nothing else
|
|
14
15
|
// GET /agents/:iid/surface accepts + schemas + semantics [Sandbox token]
|
|
15
16
|
// POST /agents/:iid/events validate + deliver → the turn receipt [Sandbox token]
|
|
16
17
|
// POST /sandboxes/:name/fetch ask the node cache to fetch one Repo [Sandbox token]
|
|
@@ -180,7 +181,7 @@ async function consoleTsAsset(file: string): Promise<Response> {
|
|
|
180
181
|
// Content negotiation for the two page addresses (ADR-0032). JSON is the DEFAULT dialect: only a
|
|
181
182
|
// request whose `Accept` prefers `text/html` — a browser's navigation — gets the Console shell.
|
|
182
183
|
// No header, a bare wildcard, `application/json`, `text/event-stream` all fall through to JSON,
|
|
183
|
-
// so the CLI, the
|
|
184
|
+
// so the CLI, the Harness and EventSource never see HTML they did not ask for.
|
|
184
185
|
function prefersHtml(c: Context): boolean {
|
|
185
186
|
return (
|
|
186
187
|
accepts(c, { header: "Accept", supports: ["application/json", "text/html"], default: "application/json" }) ===
|
|
@@ -301,8 +302,8 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
|
|
|
301
302
|
|
|
302
303
|
/**
|
|
303
304
|
* Authenticate, AND require the INSTANCE token (ADR-0014). A Sandbox token is a token we minted,
|
|
304
|
-
* so `authenticated` alone lets it through — which on the run surface is too much: it would let
|
|
305
|
-
*
|
|
305
|
+
* so `authenticated` alone lets it through — which on the run surface is too much: it would let a
|
|
306
|
+
* Custodian's credential read every run's context (other features' branches, tickets, verdicts) and
|
|
306
307
|
* CANCEL any run. Neither is on the Agent's surface. Its scope is delivering to agent
|
|
307
308
|
* registrations recorded against its OWN Sandbox (ADR-0013), and the gate route already says so in
|
|
308
309
|
* the other direction.
|
|
@@ -358,8 +359,8 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
|
|
|
358
359
|
// A body failing the machine's declared input schema (ADR-0033) → 400 naming the accepted
|
|
359
360
|
// shape — the same error class, and the same wire mapping, as a gate delivery failing its
|
|
360
361
|
// schema (`POST /runs/:id/gates/:gate/events` below). Instance-band (ADR-0058): starting work is
|
|
361
|
-
// run control, and a Sandbox token's scope is its own agent surface —
|
|
362
|
-
//
|
|
362
|
+
// run control, and a Sandbox token's scope is its own agent surface — the Custodian's route
|
|
363
|
+
// allowlist not forwarding this route is a second layer, not the control (ADR-0059).
|
|
363
364
|
app.post("/workflows/:name/runs", instanceOnly, async (c) => {
|
|
364
365
|
const name = c.req.param("name");
|
|
365
366
|
const input = await readJson(c.req.text());
|
|
@@ -671,10 +672,12 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
|
|
|
671
672
|
});
|
|
672
673
|
|
|
673
674
|
// ---- The Agent's surface (`/agents/:iid/*` — ADR-0013) --------------------------------------
|
|
674
|
-
// Served to ONE caller: the
|
|
675
|
-
// and turns a
|
|
676
|
-
// dependency, no transport, no session handling — and the Agent keeps no route to this
|
|
677
|
-
// except through a
|
|
675
|
+
// Served to ONE caller: the Agent's Harness, through the Custodian in its pod. The Harness renders
|
|
676
|
+
// `surface` as the Menu and turns a pick into an `events` POST. The Orchestrator therefore keeps
|
|
677
|
+
// no MCP dependency, no transport, no session handling — and the Agent keeps no route to this
|
|
678
|
+
// API except through a container whose credential it cannot read. Everything a pick is judged by
|
|
679
|
+
// is judged HERE: the token's scope, the live registration, the event's name and payload — the
|
|
680
|
+
// Custodian forwards and never decides (ADR-0059).
|
|
678
681
|
//
|
|
679
682
|
// Both routes are adapters over the SAME registration table the gates ride: lookup, validation
|
|
680
683
|
// and delivery are implemented once, in `registration.ts`.
|
|
@@ -696,7 +699,7 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
|
|
|
696
699
|
};
|
|
697
700
|
|
|
698
701
|
// This turn's menu: the events the invoking state accepts, their input schemas, their semantics.
|
|
699
|
-
// A transition swaps the registration, which swaps this — so the
|
|
702
|
+
// A transition swaps the registration, which swaps this — so the Harness gets a state-scoped
|
|
700
703
|
// toolset for free, and needs no `list_changed` to know it (flue re-lists on every submission).
|
|
701
704
|
app.get("/agents/:instanceId/surface", authenticated, (c) => {
|
|
702
705
|
const { surface, error } = agentRegistration(c);
|
|
@@ -704,7 +707,7 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
|
|
|
704
707
|
});
|
|
705
708
|
|
|
706
709
|
// The Agent's pick, delivered into the state that invoked it. The receipt describes itself
|
|
707
|
-
// (ADR-0024): what was delivered, and whether that ended the turn — which the
|
|
710
|
+
// (ADR-0024): what was delivered, and whether that ended the turn — which the Harness renders as
|
|
708
711
|
// prose. Its `deliveryId` still makes an outcome addressable after the fact, the room a deferred
|
|
709
712
|
// result will need when it lands.
|
|
710
713
|
app.post("/agents/:instanceId/events", authenticated, async (c) => {
|
|
@@ -721,7 +724,7 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
|
|
|
721
724
|
});
|
|
722
725
|
|
|
723
726
|
// The pod's one route out (ADR-0053). A `git fetch` inside a Sandbox runs a program on the
|
|
724
|
-
// runtime volume, which asks the
|
|
727
|
+
// runtime volume, which asks the Custodian on localhost, which forwards to this. What lands here
|
|
725
728
|
// is one identity; what goes back is the landing, or the cache as it stands with git's own words
|
|
726
729
|
// — freshness degrades, absence does not, so this answers 200 either way and the program prints
|
|
727
730
|
// the warning. The caller waits on the ask the way an attach waits on Ready: same status, same
|
package/src/images.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// not folded into sandbox-kubectl.ts, because the CLI needs the type without dragging in the
|
|
5
5
|
// kubectl port.
|
|
6
6
|
//
|
|
7
|
-
// The map is NESTED, never flat. The kit's own `harness
|
|
7
|
+
// The map is NESTED, never flat. The kit's own `harness` and the `custodian` sit beside a `sandbox`
|
|
8
8
|
// sub-map, so a user image can never shadow them — and `default` is the one reserved key in that
|
|
9
9
|
// sub-map, because ADR-0037's fallback chain is built on it.
|
|
10
10
|
//
|
|
@@ -26,8 +26,9 @@ import { join, relative } from "node:path";
|
|
|
26
26
|
import { fileURLToPath } from "node:url";
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* Every image ref one converge resolved. `harness
|
|
30
|
-
*
|
|
29
|
+
* Every image ref one converge resolved. `harness` is the kit's own (built from a kit checkout, or
|
|
30
|
+
* the published `<kitversion>` tag); `custodian` is the pinned Envoy the kit deploys and never
|
|
31
|
+
* builds (ADR-0038, ADR-0059); `sandbox` is the instance's own docker-context
|
|
31
32
|
* builds — one `docker build` straight to a content tag, carrying no jr2 layers at all, because the
|
|
32
33
|
* Harness arrives at POD time on the `/opt/jr2` volume (ADR-0037).
|
|
33
34
|
*
|
|
@@ -38,9 +39,10 @@ import { fileURLToPath } from "node:url";
|
|
|
38
39
|
export type ImageRefs = {
|
|
39
40
|
/** The stock Harness image — also the last leg of the Sandbox Image fallback chain. */
|
|
40
41
|
harness: string;
|
|
41
|
-
/** The
|
|
42
|
+
/** The Custodian's image (ADR-0059) — upstream Envoy, pinned by digest, re-homed onto a
|
|
43
|
+
* `kitRegistry` when one is set. Every Harness pod runs one (it carries the Menu, ADR-0013), so
|
|
42
44
|
* this is required: a map without it fails the provision rather than shipping a mute pod. */
|
|
43
|
-
|
|
45
|
+
custodian: string;
|
|
44
46
|
/** Built Sandbox Images by their build context's CONTENT DIGEST (ADR-0049), plus the reserved
|
|
45
47
|
* key `default` — the Instance's `images/default`, ADR-0037's middle leg. Empty when the
|
|
46
48
|
* instance ships no context and scaffolded no default. */
|
|
@@ -99,10 +101,9 @@ export async function readImageRefs(path: string): Promise<ImageRefs> {
|
|
|
99
101
|
return value as string;
|
|
100
102
|
};
|
|
101
103
|
const harness = ref("harness");
|
|
102
|
-
// Required, not optional:
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
const adapter = ref("adapter");
|
|
104
|
+
// Required, not optional: an Agent whose pod has no Custodian parks forever on a Menu it cannot
|
|
105
|
+
// read (ADR-0013). Failing the provision is the only honest outcome.
|
|
106
|
+
const custodian = ref("custodian");
|
|
106
107
|
const sandboxRaw = map["sandbox"];
|
|
107
108
|
if (sandboxRaw !== undefined && (typeof sandboxRaw !== "object" || sandboxRaw === null || Array.isArray(sandboxRaw)))
|
|
108
109
|
bad("`sandbox` must be an object of name → ref");
|
|
@@ -121,7 +122,7 @@ export async function readImageRefs(path: string): Promise<ImageRefs> {
|
|
|
121
122
|
if (typeof value !== "string") bad(`\`sandboxUser.${name}\` is not a USER (got ${JSON.stringify(value)})`);
|
|
122
123
|
sandboxUser[name] = value as string;
|
|
123
124
|
}
|
|
124
|
-
return { harness,
|
|
125
|
+
return { harness, custodian, sandbox, sandboxUser };
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
/**
|
package/src/index.ts
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
// delivery surfaces, the duplex Actor over the Harness wire, and durable snapshot persistence
|
|
3
3
|
// (ADR-0002/0006/0007). Builds on the `@jr2/agent-protocol` wire contract.
|
|
4
4
|
//
|
|
5
|
-
// It does NOT speak MCP (ADR-0013): the
|
|
6
|
-
//
|
|
5
|
+
// It does NOT speak MCP (ADR-0013): the Harness presents the Menu to its model itself, and reaches
|
|
6
|
+
// the surface here through the Custodian in its pod. What lives here is the registration table and
|
|
7
|
+
// two thin HTTP adapters over it — plus what composes a Custodian (ADR-0059).
|
|
7
8
|
|
|
8
9
|
// The wire contract, re-exported: a workflow authors against ONE package (`defineEvent`,
|
|
9
10
|
// `jr2Setup`, `workspace`, … all import from "@jr2/orchestrator" — ADR-0015).
|
|
@@ -24,6 +25,9 @@ export * from "./actor.ts";
|
|
|
24
25
|
export * from "./gate.ts";
|
|
25
26
|
export * from "./workspace.ts";
|
|
26
27
|
export * from "./images.ts";
|
|
28
|
+
export * from "./model-keys.ts";
|
|
29
|
+
export * from "./held-secrets.ts";
|
|
30
|
+
export * from "./custodian.ts";
|
|
27
31
|
export * from "./sandbox-kubectl.ts";
|
|
28
32
|
export * from "./repos.ts";
|
|
29
33
|
export * from "./repo-fetch.ts";
|
package/src/instance.ts
CHANGED
|
@@ -116,7 +116,7 @@ export async function startInstance(opts: InstanceOptions): Promise<RunningInsta
|
|
|
116
116
|
// Resolved BEFORE the host, and the signing key with it: the host derives every Harness bearer
|
|
117
117
|
// from the key (ADR-0058) — admission, stream, abort, echo. The key is loaded from (or minted
|
|
118
118
|
// into) the instance folder, NOT generated per process: live Sandboxes outlive a restart, their
|
|
119
|
-
//
|
|
119
|
+
// Custodians still hold tokens this key signed, and their Harnesses still check bearers it derived.
|
|
120
120
|
// The Instance token is per-boot; the key is not. Both are served under in step 4 below.
|
|
121
121
|
const instanceToken = opts.instanceToken ?? mintInstanceToken();
|
|
122
122
|
const signingKey = opts.signingKey ?? (await loadSigningKey(opts.dir));
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// The known model keys (ADR-0059): every env var pi's catalog reads a provider's credential from,
|
|
2
|
+
// the catalog provider(s) that read it, and the one host those providers' calls go to by default.
|
|
3
|
+
//
|
|
4
|
+
// A COPY of pi's data at the exact pin `@jr2/harness` holds, because this package has no pi
|
|
5
|
+
// dependency and must not grow one: `jr2 up` reads this table host-side (R1, and the default
|
|
6
|
+
// `hosts` of a held secret), and the in-cluster Orchestrator never touches pi. The copy cannot
|
|
7
|
+
// drift silently — `@jr2/harness`'s `model-keys.test.ts` checks it against pi itself, env names per
|
|
8
|
+
// provider and every provider's default host, in the default gate. A pi bump that adds or moves a
|
|
9
|
+
// provider fails there first, the same canary role Harness conformance plays (ADR-0027).
|
|
10
|
+
//
|
|
11
|
+
// `host` is present only when every model of every provider that reads the name goes to ONE static
|
|
12
|
+
// host. It is absent when there is none: the base URL is a template (`{location}`, an account id),
|
|
13
|
+
// the providers disagree (MOONSHOT_API_KEY serves two regions), or the host is regional (Bedrock).
|
|
14
|
+
// A held secret with such a name must then say its `hosts` itself (§3.4 of the spec, R5).
|
|
15
|
+
|
|
16
|
+
/** One known model-key env name. */
|
|
17
|
+
export type ModelKey = {
|
|
18
|
+
/** The catalog provider ids that read this name. */
|
|
19
|
+
providers: readonly string[];
|
|
20
|
+
/** The host (port 443) those providers' calls go to by default, when there is exactly one. */
|
|
21
|
+
host?: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const MODEL_KEYS: Readonly<Record<string, ModelKey>> = {
|
|
25
|
+
// anthropic reads three names. AUTH_TOKEN goes as `Authorization: Bearer`, the other two as
|
|
26
|
+
// `x-api-key` (an `sk-ant-oat` value is an OAuth token, which a Stand-in never is).
|
|
27
|
+
ANTHROPIC_API_KEY: { providers: ["anthropic"], host: "api.anthropic.com" },
|
|
28
|
+
ANTHROPIC_AUTH_TOKEN: { providers: ["anthropic"], host: "api.anthropic.com" },
|
|
29
|
+
ANTHROPIC_OAUTH_TOKEN: { providers: ["anthropic"], host: "api.anthropic.com" },
|
|
30
|
+
OPENAI_API_KEY: { providers: ["openai"], host: "api.openai.com" },
|
|
31
|
+
GEMINI_API_KEY: { providers: ["google"], host: "generativelanguage.googleapis.com" },
|
|
32
|
+
// `https://{location}-aiplatform.googleapis.com` — regional, so no default.
|
|
33
|
+
GOOGLE_CLOUD_API_KEY: { providers: ["google-vertex"] },
|
|
34
|
+
COPILOT_GITHUB_TOKEN: { providers: ["github-copilot"], host: "api.individual.githubcopilot.com" },
|
|
35
|
+
// The bearer form of Bedrock auth. Regional, so no default. (SigV4 credentials cannot be held at
|
|
36
|
+
// all — a signature is not a header a Stand-in can be swapped into — and `jr2 up` warns on them.)
|
|
37
|
+
AWS_BEARER_TOKEN_BEDROCK: { providers: ["amazon-bedrock"] },
|
|
38
|
+
ANT_LING_API_KEY: { providers: ["ant-ling"], host: "api.ant-ling.com" },
|
|
39
|
+
QWEN_TOKEN_PLAN_API_KEY: {
|
|
40
|
+
providers: ["qwen-token-plan"],
|
|
41
|
+
host: "token-plan.ap-southeast-1.maas.aliyuncs.com",
|
|
42
|
+
},
|
|
43
|
+
QWEN_TOKEN_PLAN_CN_API_KEY: { providers: ["qwen-token-plan-cn"], host: "token-plan.cn-beijing.maas.aliyuncs.com" },
|
|
44
|
+
// The catalog names no base URL: an Azure deployment is the user's own resource.
|
|
45
|
+
AZURE_OPENAI_API_KEY: { providers: ["azure-openai-responses"] },
|
|
46
|
+
NVIDIA_API_KEY: { providers: ["nvidia"], host: "integrate.api.nvidia.com" },
|
|
47
|
+
DEEPSEEK_API_KEY: { providers: ["deepseek"], host: "api.deepseek.com" },
|
|
48
|
+
GROQ_API_KEY: { providers: ["groq"], host: "api.groq.com" },
|
|
49
|
+
CEREBRAS_API_KEY: { providers: ["cerebras"], host: "api.cerebras.ai" },
|
|
50
|
+
XAI_API_KEY: { providers: ["xai"], host: "api.x.ai" },
|
|
51
|
+
// pi reads the name, and its catalog lists no model for the provider at this pin.
|
|
52
|
+
RADIUS_API_KEY: { providers: ["radius"] },
|
|
53
|
+
OPENROUTER_API_KEY: { providers: ["openrouter"], host: "openrouter.ai" },
|
|
54
|
+
AI_GATEWAY_API_KEY: { providers: ["vercel-ai-gateway"], host: "ai-gateway.vercel.sh" },
|
|
55
|
+
ZAI_API_KEY: { providers: ["zai"], host: "api.z.ai" },
|
|
56
|
+
ZAI_CODING_CN_API_KEY: { providers: ["zai-coding-cn"], host: "open.bigmodel.cn" },
|
|
57
|
+
MISTRAL_API_KEY: { providers: ["mistral"], host: "api.mistral.ai" },
|
|
58
|
+
MINIMAX_API_KEY: { providers: ["minimax"], host: "api.minimax.io" },
|
|
59
|
+
MINIMAX_CN_API_KEY: { providers: ["minimax-cn"], host: "api.minimaxi.com" },
|
|
60
|
+
// Two providers, two hosts (`.ai` and `.cn`): no single default.
|
|
61
|
+
MOONSHOT_API_KEY: { providers: ["moonshotai", "moonshotai-cn"] },
|
|
62
|
+
HF_TOKEN: { providers: ["huggingface"], host: "router.huggingface.co" },
|
|
63
|
+
FIREWORKS_API_KEY: { providers: ["fireworks"], host: "api.fireworks.ai" },
|
|
64
|
+
TOGETHER_API_KEY: { providers: ["together"], host: "api.together.ai" },
|
|
65
|
+
// Two providers, one host.
|
|
66
|
+
OPENCODE_API_KEY: { providers: ["opencode", "opencode-go"], host: "opencode.ai" },
|
|
67
|
+
KIMI_API_KEY: { providers: ["kimi-coding"], host: "api.kimi.com" },
|
|
68
|
+
// Account-templated base URLs.
|
|
69
|
+
CLOUDFLARE_API_KEY: { providers: ["cloudflare-workers-ai", "cloudflare-ai-gateway"] },
|
|
70
|
+
XIAOMI_API_KEY: { providers: ["xiaomi"], host: "api.xiaomimimo.com" },
|
|
71
|
+
XIAOMI_TOKEN_PLAN_CN_API_KEY: { providers: ["xiaomi-token-plan-cn"], host: "token-plan-cn.xiaomimimo.com" },
|
|
72
|
+
XIAOMI_TOKEN_PLAN_AMS_API_KEY: { providers: ["xiaomi-token-plan-ams"], host: "token-plan-ams.xiaomimimo.com" },
|
|
73
|
+
XIAOMI_TOKEN_PLAN_SGP_API_KEY: { providers: ["xiaomi-token-plan-sgp"], host: "token-plan-sgp.xiaomimimo.com" },
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** Every catalog provider id some known model key belongs to — the set `harness.catalog` may name. */
|
|
77
|
+
export const MODEL_KEY_PROVIDERS: ReadonlySet<string> = new Set(
|
|
78
|
+
Object.values(MODEL_KEYS).flatMap((key) => key.providers),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Credentials that sign a request instead of riding it in a header (ADR-0059). A Stand-in cannot be
|
|
83
|
+
* swapped into a signature, so these cannot be held — `jr2 up` warns when `harness.env` sets one,
|
|
84
|
+
* because the Agent can read it, and names a gateway as the way out.
|
|
85
|
+
*/
|
|
86
|
+
export const SIGNING_CREDENTIALS: readonly string[] = [
|
|
87
|
+
"AWS_ACCESS_KEY_ID",
|
|
88
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
89
|
+
"AWS_SESSION_TOKEN",
|
|
90
|
+
"AWS_PROFILE",
|
|
91
|
+
"GOOGLE_APPLICATION_CREDENTIALS",
|
|
92
|
+
];
|
package/src/names.ts
CHANGED
|
@@ -12,13 +12,13 @@ export const INSTANCE_SECRET = "jr2-instance";
|
|
|
12
12
|
/** The Instance Harness's Deployment + Service name (ADR-0031): converged by `jr2 up` whenever any
|
|
13
13
|
* Agent definition declares `workspace: "none"`, and the deterministic Service DNS the Agent actor
|
|
14
14
|
* resolves such a Turn to. Doubles as the delivery scope a Menu-only registration records — the
|
|
15
|
-
* name the placement's
|
|
15
|
+
* name the placement's Sandbox token is signed for (tokens.ts, ADR-0013), which its Custodian holds. The port is the
|
|
16
16
|
* Harness's own listen port (`PORT` default). */
|
|
17
17
|
export const INSTANCE_HARNESS_SERVICE = "jr2-instance-harness";
|
|
18
18
|
export const INSTANCE_HARNESS_PORT = 8080;
|
|
19
19
|
|
|
20
20
|
/** The harness config ConfigMap the stock Harness boots from (ADR-0018): what this instance can
|
|
21
|
-
* REACH — the custom model provider
|
|
21
|
+
* REACH — the custom model provider minus its key, and the catalog providers' gateways (ADR-0059). No Agents ride it: a Machine carries its own
|
|
22
22
|
* (ADR-0049), and the definition rides each admission, so this holds deployment facts alone
|
|
23
23
|
* (ADR-0050). */
|
|
24
24
|
export const HARNESS_CONFIGMAP = "jr2-harness";
|
|
@@ -26,7 +26,7 @@ export const HARNESS_CONFIGMAP = "jr2-harness";
|
|
|
26
26
|
export const HARNESS_CONFIG_KEY = "harness.json";
|
|
27
27
|
|
|
28
28
|
/** The resolved image map `jr2 up` writes and every provision reads (ADR-0037/0038): key → ref for
|
|
29
|
-
* the Harness, the
|
|
29
|
+
* the Harness, the Custodian, and every Sandbox Image context the registered Machines carry — keyed
|
|
30
30
|
* by content digest, plus the reserved `default` (ADR-0049).
|
|
31
31
|
*
|
|
32
32
|
* MOUNTED, never projected into env — the load-bearing part. A mount updates in place through
|
|
@@ -38,13 +38,37 @@ export const IMAGES_MOUNT = "/etc/jr2/images";
|
|
|
38
38
|
/** The ConfigMap key, hence the filename under the mount — the two-sided contract's other half. */
|
|
39
39
|
export const IMAGES_KEY = "images.json";
|
|
40
40
|
|
|
41
|
-
/** The HARNESS containers' env Secret —
|
|
41
|
+
/** The HARNESS containers' env Secret — the literal `harness.env` values, never the Instance token
|
|
42
|
+
* (ADR-0013) and never a model key (ADR-0059: those are held). */
|
|
42
43
|
export const HARNESS_ENV_SECRET = "jr2-harness-env";
|
|
43
44
|
|
|
44
|
-
/** The instance's
|
|
45
|
-
*
|
|
45
|
+
/** The instance's trust ConfigMap (ADR-0020, ADR-0059): the user's `harness.caBundle` as `ca.crt`
|
|
46
|
+
* and, while a held secret exists, the three bundles the held-secret path needs — `extra.crt` and
|
|
47
|
+
* `bundle.crt` for the Harness, `upstream.crt` for the Custodian. Public data by nature. */
|
|
46
48
|
export const CA_CONFIGMAP = "jr2-ca";
|
|
47
49
|
|
|
50
|
+
/** Held secrets (ADR-0059). The per-Instance CA — its key included — that `jr2 up` alone reads over
|
|
51
|
+
* the kube API, and that NO pod mounts: every leaf is issued before a pod starts. */
|
|
52
|
+
export const HELD_CA_SECRET = "jr2-held-ca";
|
|
53
|
+
/** The literal held values, one key per name — mounted into the Custodian and nothing else. */
|
|
54
|
+
export const HELD_SECRETS_SECRET = "jr2-held-secrets";
|
|
55
|
+
/** One leaf per bound `host:port` (`<stem>.crt`, `<stem>.key`) — the Custodian's alone. */
|
|
56
|
+
export const HELD_TLS_SECRET = "jr2-held-tls";
|
|
57
|
+
/** `held.json` (what `jr2 up` resolved) plus the Custodian's bootstrap and script. Always applied,
|
|
58
|
+
* possibly holding no secret: every Harness pod runs a Custodian. */
|
|
59
|
+
export const HELD_CONFIGMAP = "jr2-held";
|
|
60
|
+
/** Where the Orchestrator reads `held.json`, per provision — a mount, so a held-secret edit reaches
|
|
61
|
+
* future Sandboxes without rolling the Orchestrator (the `jr2-images` pattern, ADR-0038). */
|
|
62
|
+
export const HELD_MOUNT = "/etc/jr2/held";
|
|
63
|
+
export const HELD_KEY = "held.json";
|
|
64
|
+
|
|
65
|
+
/** The Custodian's listeners (ADR-0059), all taken on every Harness pod (ADR-0005): the Harness's
|
|
66
|
+
* HTTPS proxy on loopback, the kubelet's probe, and the control listener the Menu and the ask ride
|
|
67
|
+
* to the Orchestrator, at the address `jr2-upload-pack` defaults to (ADR-0053). */
|
|
68
|
+
export const CUSTODIAN_PORT = 15001;
|
|
69
|
+
export const CUSTODIAN_HEALTH_PORT = 15021;
|
|
70
|
+
export const CUSTODIAN_CONTROL_PORT = 8081;
|
|
71
|
+
|
|
48
72
|
/** The scaffold's default deploy-key Secret name (ADR-0047/0051): what `jr2 init`'s wildcard
|
|
49
73
|
* `git.credentials` entry names as its `sshKey`, and what `jr2 up`'s ssh offer generates into when
|
|
50
74
|
* an entry names it. Only a default — an entry may name any Secret. */
|
package/src/registration.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// The internal registration table (ADR-0011): one structure behind both delivery dialects.
|
|
2
2
|
// An Agent slot and `gate` register `address → { accepted event defs, deliver closure, meta }`;
|
|
3
|
-
// the two HTTP surfaces (`/agents/:iid/*` for the
|
|
3
|
+
// the two HTTP surfaces (`/agents/:iid/*` for the Agent's Harness, `/runs/:id/gates/*` for humans and
|
|
4
4
|
// webhooks) are adapters over it — lookup, schema validation, delivery, discovery, and lifecycle
|
|
5
5
|
// are implemented ONCE. The table is implementation structure, not vocabulary: workflows speak
|
|
6
6
|
// only `defineEvent` / `agent` / `gate`.
|
package/src/repo-fetch.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Nothing in the pod holds a git credential, and nothing in it may talk to a remote — the node's
|
|
4
4
|
// cache agent is the only thing that fetches (ADR-0051). So a `git fetch` in a pod is a program on
|
|
5
|
-
// the runtime volume (`jr2-upload-pack`, operator/cmd) asking the
|
|
5
|
+
// the runtime volume (`jr2-upload-pack`, operator/cmd) asking the Custodian on localhost, the Custodian
|
|
6
6
|
// asking the Orchestrator with the Sandbox token it already holds, and this port marking the
|
|
7
7
|
// Sandbox CR: one annotation per Repo key, timestamp value. The operator copies the mark onto the
|
|
8
8
|
// pod, the agent reads its demand off pods alone, and the landing comes back on the Sandbox's
|
package/src/run-host.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// closures over their own `sendBack` — so delivery lands on the invoking state at any
|
|
8
8
|
// nesting depth and the host routes nothing.
|
|
9
9
|
// - Two thin surfaces sit on that table, and NEITHER is MCP (ADR-0013 — the Orchestrator does
|
|
10
|
-
// not speak it): `agentSurface` / `sendToAgent` serve the Agent's
|
|
10
|
+
// not speak it): `agentSurface` / `sendToAgent` serve the Agent's Harness (`/agents/:iid/*`),
|
|
11
11
|
// `gates` / `sendToGate` serve humans, webhooks and CI (`/runs/:id/gates/*`). Lookup,
|
|
12
12
|
// validation, delivery and lifecycle stay implemented once, in the table.
|
|
13
13
|
// - The run's Agent children report their durable admissions through the run binding into
|
|
@@ -169,13 +169,14 @@ export type GateView = {
|
|
|
169
169
|
};
|
|
170
170
|
|
|
171
171
|
/**
|
|
172
|
-
* One live agent surface, as
|
|
173
|
-
*
|
|
174
|
-
* tool
|
|
175
|
-
* fire-and-forget one — the room ADR-0006's deferred results will land in,
|
|
172
|
+
* One live agent surface, as the Harness reads it through its Custodian (`GET /agents/:iid/surface`
|
|
173
|
+
* — ADR-0013). The Harness presents it to its model as the Menu: each accepted event becomes a
|
|
174
|
+
* tool, its `input` schema the tool's parameters. `semantics` rides along so the Harness can tell
|
|
175
|
+
* an awaiting tool from a fire-and-forget one — the room ADR-0006's deferred results will land in,
|
|
176
|
+
* unbuilt today.
|
|
176
177
|
*
|
|
177
|
-
* `sandbox` is the Sandbox that may deliver here. It is not a secret from
|
|
178
|
-
*
|
|
178
|
+
* `sandbox` is the Sandbox that may deliver here. It is not a secret from that pod (the pod IS the
|
|
179
|
+
* sandbox), and serving it lets a caller fail loudly on a surface that is not its own.
|
|
179
180
|
*/
|
|
180
181
|
export type AgentSurfaceView = {
|
|
181
182
|
instanceId: string;
|
|
@@ -186,7 +187,7 @@ export type AgentSurfaceView = {
|
|
|
186
187
|
|
|
187
188
|
/**
|
|
188
189
|
* The answer to one Agent delivery (`POST /agents/:iid/events`) — a receipt that DESCRIBES ITSELF
|
|
189
|
-
* (ADR-0024). The
|
|
190
|
+
* (ADR-0024). The Harness renders it as prose, because a bare `deliveryId` told the Agent nothing
|
|
190
191
|
* about whether it was finished, and the model answered that silence by calling again.
|
|
191
192
|
*
|
|
192
193
|
* `turnComplete` is the HINT, never the guarantee (the abort on invocation end is): it is read off
|
|
@@ -579,15 +580,14 @@ export class RunHost {
|
|
|
579
580
|
/**
|
|
580
581
|
* An agent instance's LIVE surface (`GET /agents/:iid/surface` — ADR-0013): what the state that
|
|
581
582
|
* invoked this Agent accepts, right now. Undefined once nothing is registered (the state exited,
|
|
582
|
-
* the run settled, the iid is unknown) — the one catch point, and the reason the
|
|
583
|
+
* the run settled, the iid is unknown) — the one catch point, and the reason the Harness never
|
|
583
584
|
* has to learn which turn is live: it asks, per turn, and the answer IS the turn.
|
|
584
585
|
*
|
|
585
586
|
* "Right now" is load-bearing (ADR-0029): the registered defs are the state's VOCABULARY, derived
|
|
586
587
|
* statically from its transitions, and the guards on those transitions are asked here — so an
|
|
587
588
|
* event the Machine cannot currently accept is not offered. The pick has not happened yet, so the
|
|
588
589
|
* question is `mayMove`, not `wouldMove`: a guard that would have judged the Agent's arguments is
|
|
589
|
-
* left on the menu and settled at delivery. The
|
|
590
|
-
* Harness re-lists per Submission, so the filter lands at turn boundaries and never moves under
|
|
590
|
+
* left on the menu and settled at delivery. The Harness reads this afresh per Submission, so the filter lands at turn boundaries and never moves under
|
|
591
591
|
* an Agent mid-turn.
|
|
592
592
|
*/
|
|
593
593
|
agentSurface(instanceId: string): AgentSurfaceView | undefined {
|
|
@@ -609,8 +609,10 @@ export class RunHost {
|
|
|
609
609
|
}
|
|
610
610
|
|
|
611
611
|
/**
|
|
612
|
-
* Deliver one event from an Agent's
|
|
613
|
-
* and delivery are the table's; this only agent-scopes the address and mints the receipt.
|
|
612
|
+
* Deliver one event from an Agent's Harness (`POST /agents/:iid/events` — ADR-0013). Validation
|
|
613
|
+
* and delivery are the table's; this only agent-scopes the address and mints the receipt. (A
|
|
614
|
+
* `deferred` event never reaches here: `jr2Setup` refuses to build a Machine that declares one —
|
|
615
|
+
* ADR-0013.)
|
|
614
616
|
*/
|
|
615
617
|
sendToAgent(instanceId: string, event: { type?: unknown } & Record<string, unknown>): AgentDeliveryReceipt {
|
|
616
618
|
const { type, ...payload } = event;
|