@orchestraworks/cli 0.5.8 → 0.6.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.
Files changed (91) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/api/approval.js +3 -3
  3. package/dist/cli/commands/outpost/local.js +3 -3
  4. package/dist/cli/commands/outpost/machine-names.d.ts +17 -0
  5. package/dist/cli/commands/outpost/machine-names.js +22 -0
  6. package/dist/cli/commands/outpost/up.js +7 -5
  7. package/dist/cli/commands/worker/enroll.d.ts +4 -3
  8. package/dist/cli/commands/worker/enroll.js +4 -3
  9. package/dist/cli/core/auth.d.ts +11 -1
  10. package/dist/cli/core/auth.js +21 -4
  11. package/dist/cli/core/session-body.d.ts +1 -1
  12. package/dist/cli/core/session-body.js +1 -1
  13. package/dist/cli/firstRun.js +2 -2
  14. package/dist/cli/main.js +4 -3
  15. package/dist/constructs/environment.d.ts +24 -11
  16. package/dist/constructs/environment.js +76 -12
  17. package/dist/constructs/secret.d.ts +17 -7
  18. package/dist/constructs/secret.js +24 -16
  19. package/dist/core/labels.d.ts +32 -6
  20. package/dist/core/labels.js +70 -8
  21. package/dist/engine/diff.js +38 -11
  22. package/dist/engine/gateway-key-reconcile.d.ts +1 -2
  23. package/dist/engine/gateway-key-reconcile.js +19 -15
  24. package/dist/engine/pipeline-reconcile.js +2 -2
  25. package/dist/engine/pipeline-workflow-reconcile.js +15 -9
  26. package/dist/engine/reconcile.js +48 -15
  27. package/dist/engine/refusals.d.ts +0 -5
  28. package/dist/engine/refusals.js +1 -5
  29. package/dist/engine/secrets-apply.d.ts +10 -14
  30. package/dist/engine/secrets-apply.js +12 -17
  31. package/dist/engine/workflow-reconcile.d.ts +1 -1
  32. package/dist/generated/cli-commands.json +170 -34
  33. package/dist/generated/providers.d.ts +40 -0
  34. package/dist/generated/providers.js +30 -0
  35. package/docs/REFERENCE.md +9 -7
  36. package/node_modules/@orchestraworks/client/dist/esm/models/ApiErrorDetail.d.ts +1 -1
  37. package/node_modules/@orchestraworks/client/dist/esm/models/CreateEnvironmentRequest.d.ts +7 -0
  38. package/node_modules/@orchestraworks/client/dist/esm/models/CreateEnvironmentRequest.js +3 -0
  39. package/node_modules/@orchestraworks/client/dist/esm/models/CreateSecretRequest.d.ts +4 -3
  40. package/node_modules/@orchestraworks/client/dist/esm/models/Environment.d.ts +7 -0
  41. package/node_modules/@orchestraworks/client/dist/esm/models/Environment.js +3 -0
  42. package/node_modules/@orchestraworks/client/dist/esm/models/EnvironmentCredential.d.ts +38 -0
  43. package/node_modules/@orchestraworks/client/dist/esm/models/EnvironmentCredential.js +45 -0
  44. package/node_modules/@orchestraworks/client/dist/esm/models/EnvironmentProvisionSpec.d.ts +1 -1
  45. package/node_modules/@orchestraworks/client/dist/esm/models/EnvironmentProvisionSpecRequest.d.ts +1 -1
  46. package/node_modules/@orchestraworks/client/dist/esm/models/EnvironmentSnapshot.d.ts +1 -1
  47. package/node_modules/@orchestraworks/client/dist/esm/models/InjectionRecipe.d.ts +6 -0
  48. package/node_modules/@orchestraworks/client/dist/esm/models/InjectionRecipe.js +2 -0
  49. package/node_modules/@orchestraworks/client/dist/esm/models/OAuthTokenRequest.d.ts +1 -1
  50. package/node_modules/@orchestraworks/client/dist/esm/models/Secret.d.ts +1 -7
  51. package/node_modules/@orchestraworks/client/dist/esm/models/Secret.js +0 -4
  52. package/node_modules/@orchestraworks/client/dist/esm/models/UpdateEnvironmentRequest.d.ts +7 -0
  53. package/node_modules/@orchestraworks/client/dist/esm/models/UpdateEnvironmentRequest.js +3 -0
  54. package/node_modules/@orchestraworks/client/dist/esm/models/UpdateSecretRequest.d.ts +4 -3
  55. package/node_modules/@orchestraworks/client/dist/esm/models/index.d.ts +1 -0
  56. package/node_modules/@orchestraworks/client/dist/esm/models/index.js +1 -0
  57. package/node_modules/@orchestraworks/client/dist/models/ApiErrorDetail.d.ts +1 -1
  58. package/node_modules/@orchestraworks/client/dist/models/CreateEnvironmentRequest.d.ts +7 -0
  59. package/node_modules/@orchestraworks/client/dist/models/CreateEnvironmentRequest.js +3 -0
  60. package/node_modules/@orchestraworks/client/dist/models/CreateSecretRequest.d.ts +4 -3
  61. package/node_modules/@orchestraworks/client/dist/models/Environment.d.ts +7 -0
  62. package/node_modules/@orchestraworks/client/dist/models/Environment.js +3 -0
  63. package/node_modules/@orchestraworks/client/dist/models/EnvironmentCredential.d.ts +38 -0
  64. package/node_modules/@orchestraworks/client/dist/models/EnvironmentCredential.js +52 -0
  65. package/node_modules/@orchestraworks/client/dist/models/EnvironmentProvisionSpec.d.ts +1 -1
  66. package/node_modules/@orchestraworks/client/dist/models/EnvironmentProvisionSpecRequest.d.ts +1 -1
  67. package/node_modules/@orchestraworks/client/dist/models/EnvironmentSnapshot.d.ts +1 -1
  68. package/node_modules/@orchestraworks/client/dist/models/InjectionRecipe.d.ts +6 -0
  69. package/node_modules/@orchestraworks/client/dist/models/InjectionRecipe.js +2 -0
  70. package/node_modules/@orchestraworks/client/dist/models/OAuthTokenRequest.d.ts +1 -1
  71. package/node_modules/@orchestraworks/client/dist/models/Secret.d.ts +1 -7
  72. package/node_modules/@orchestraworks/client/dist/models/Secret.js +0 -4
  73. package/node_modules/@orchestraworks/client/dist/models/UpdateEnvironmentRequest.d.ts +7 -0
  74. package/node_modules/@orchestraworks/client/dist/models/UpdateEnvironmentRequest.js +3 -0
  75. package/node_modules/@orchestraworks/client/dist/models/UpdateSecretRequest.d.ts +4 -3
  76. package/node_modules/@orchestraworks/client/dist/models/index.d.ts +1 -0
  77. package/node_modules/@orchestraworks/client/dist/models/index.js +1 -0
  78. package/node_modules/@orchestraworks/client/package.json +1 -1
  79. package/package.json +4 -4
  80. package/skills/orchestra/SKILL.md +30 -14
  81. package/skills/orchestra-fdk/references/authoring.md +26 -20
  82. package/skills/orchestra-fdk/references/environment.md +49 -12
  83. package/skills/orchestra-fdk/references/integrations/code-repo.md +26 -5
  84. package/skills/orchestra-fdk/references/phases/1-discover.md +1 -1
  85. package/skills/orchestra-fdk/references/phases/2-plan.md +1 -1
  86. package/skills/orchestra-fdk/references/troubleshooting.md +3 -2
  87. package/templates/bug-fix/main.ts +7 -4
  88. package/templates/engineering-team/lib/environment.ts +6 -3
  89. package/templates/engineering-team/package.json +1 -1
  90. package/templates/todo-factory/main.ts +7 -4
  91. package/templates/todo-factory/package.json +1 -1
@@ -265,8 +265,8 @@ unless you set one, so it wins over the open default and over the usual allow ru
265
265
  **Live effect.** The edge reads the environment's rules on each request. So an apply that changes the rules
266
266
  reaches every machine at once, machines already running included, from their next request.
267
267
 
268
- **How it relates.** On a request a rule allows, the edge adds the secret or connection whose `hosts` cover the
269
- host, and only on `https` (section 6). Calls to the Orchestra platform itself skip these rules: the agent's
268
+ **How it relates.** On a request a rule allows, the edge adds a secret or connection whose `hosts` cover the
269
+ host, only on `https`, and only when the request names it or an environment default covers it (section 6). Calls to the Orchestra platform itself skip these rules: the agent's
270
270
  role decides them (section 1). So a machine blocked from the code host still downloads a file through
271
271
  `orchestra files get-content`. The worker points each harness's tools at the edge through proxy variables, so
272
272
  a service the agent runs in a container needs its name in `NO_PROXY` in the environment's settings.
@@ -291,10 +291,19 @@ resolved at the edge on an allow); AGENTS.md "Egress And Credentials". Kit: cli/
291
291
  **What it's for.** It lets agents use a key without seeing it. No agent, no person and no apply output ever shows
292
292
  the value.
293
293
 
294
- **What it is.** A *secret* is a stored value that can be written and never read back. It declares its `hosts`
295
- and an `injection` recipe, the headers it sets, and the network edge adds it to each allowed `https` request
296
- to one of those hosts. This is *credential injection at the network edge*. When two credentials cover one
297
- host, the request names one with a reference, `orchestra:secret/<id>`, in place of its key.
294
+ **What it is.** A *secret* is a stored value that can be written and never read back. It declares an
295
+ `injection` recipe, the headers it sets and the `hosts` it sets them on, and the network edge adds it to an
296
+ allowed `https` request to one of those hosts. A secret with no recipe is stored only, and the edge never adds it. This is *credential injection at the network edge*. The edge adds it only in two cases:
297
+
298
+ - The request names it with a reference, `orchestra:secret/<id>` or `orchestra:connection/<id>`, in place
299
+ of its key. A `Secret` or `Connection` set as an environment `env` value is written as that reference:
300
+ `ANTHROPIC_API_KEY` for claude, `GH_TOKEN` for `gh`, `GITLAB_TOKEN` for `glab`, `ORCHESTRA_GIT_CREDENTIAL`
301
+ for git. `routeModelsViaGateway({ key })` names the gateway key this way for claude and codex. The
302
+ `linear`, `slack` and `teams` commands take `--connection-id <id>`.
303
+ - The environment's `credentials` list covers the host, for a request that names nothing. The first
304
+ covering entry serves; with `replace: false`, a request carrying its own value keeps it.
305
+
306
+ A request that names nothing and that no default covers leaves as it was sent.
298
307
 
299
308
  **How it relates.** A secret is added only where a network rule lets the request leave (section 5). Never put
300
309
  a key in an environment's settings or in instructions: the machine's processes read every setting.
@@ -325,8 +334,9 @@ network rules carry the option (sections 2 and 5).
325
334
  **A Claude or Codex subscription.**
326
335
 
327
336
  - *Claude.* `claude setup-token` prints a long-lived token. It is stored as a secret, and the network edge
328
- adds it to each call to `api.anthropic.com`. The machine holds only a placeholder token and an empty API
329
- key, so no agent and no person sees the real one. Never put the token in an environment's settings: they
337
+ adds it to each call to `api.anthropic.com` that names it. The environment's `env` names the secret in
338
+ `CLAUDE_CODE_OAUTH_TOKEN` by reference, `orchestra:secret/<id>`, beside an empty API key, so no agent
339
+ and no person sees the real one. Never put the token in an environment's settings: they
330
340
  are plain text.
331
341
  - *Codex.* A personal access token, made by a ChatGPT Business or Enterprise member, or a separate ChatGPT
332
342
  sign-in kept on a `/home` volume that every machine using it shares. A Codex sign-in stays off the model
@@ -337,7 +347,8 @@ network rules carry the option (sections 2 and 5).
337
347
 
338
348
  **An Orchestra account, through the model gateway.** The model gateway is a hosted service at
339
349
  `models.orchestraworks.ai`. One endpoint reaches OpenAI, Anthropic and open models. A *gateway key* is a key
340
- to it, stored as a secret. The network edge adds it to each model call, so no agent and no person sees it.
350
+ to it, stored as a secret. `routeModelsViaGateway({ key })` names it to each harness, and the network edge
351
+ adds it to each model call, so no agent and no person sees it.
341
352
  Without it, an agent's first message fails with "Invalid API key".
342
353
 
343
354
  - *Routing.* `orchestra model-configs` sets which providers serve a model, and in which order, so a call
@@ -348,10 +359,11 @@ Without it, an agent's first message fails with "Invalid API key".
348
359
  - *A cap.* Each gateway key can carry a monthly cap, set in the console at **Settings › Gateway keys**.
349
360
 
350
361
  **Their own provider API key.** The team's Anthropic or OpenAI API key is a secret that the network edge adds
351
- to calls straight to the provider, with no gateway in between.
362
+ to calls straight to the provider, with no gateway in between. The environment names it in `ANTHROPIC_API_KEY`
363
+ or `ORCHESTRA_WORKER_OPENAI_API_KEY`, or lists it in `credentials`.
352
364
 
353
365
  **How it relates.** Each option reaches the model through an ordinary allow rule on the model's host, and a
354
- secret whose `hosts` name that host (sections 5 and 6). An agent on the model gateway turns it on in its model settings (section 2). An agent on a
366
+ secret whose recipe's `hosts` name that host (sections 5 and 6). An agent on the model gateway turns it on in its model settings (section 2). An agent on a
355
367
  subscription or its own key turns it off. For plan questions, send the person to **Settings › Plan & usage**
356
368
  in the console.
357
369
 
@@ -647,7 +659,7 @@ local/launch/modelkey.go (model key carriers per harness, the gateway address va
647
659
  worker/facts/families.json (how each harness takes the gateway address); on develop at f62fad1481,
648
660
  agents/runtime/assembly/common/placeholder-env (the images' placeholder ANTHROPIC_API_KEY),
649
661
  LauncherProperties.kt (the gallery images at Versions/latest), launcher/config/config.go (Dsv5 default);
650
- worker/facts/families.json (the ORCHESTRA_WORKER_ names), worker/service/manager_windows.go (SfdkWorker),
662
+ worker/facts/families.json (the ORCHESTRA_WORKER_ names), worker/service/service.go (OrchestraWorker, and the previous SfdkWorker),
651
663
  infra/packer/azure-windows-runtime.pkr.hcl (the startup task), launcher/cloud/cloud.go (the VM tags);
652
664
  the proven list is the test run of 2026-10-05. -->
653
665
 
@@ -677,8 +689,8 @@ the proven list is the test run of 2026-10-05. -->
677
689
  1. The environment's `env` (`provisionSpec.env`). A declared value replaces our image's placeholder,
678
690
  never a real value. The platform stores `env` values in plain text, an environment read returns
679
691
  them, and the machine's startup data carries them.
680
- 2. Baked into the team's image at machine scope: on Windows the `SfdkWorker` service's `Environment`
681
- registry value, on Linux a drop-in on `sfdk-worker.service`.
692
+ 2. Baked into the team's image at machine scope: on Windows the `OrchestraWorker` service's
693
+ `Environment` registry value, on Linux a drop-in on `orchestra-worker.service`.
682
694
  3. The `onStartAgent` hook writes the harness's own key file: `.claude/.credentials.json` for a Claude
683
695
  sign-in, `.codex/auth.json` for `codex`, `.pi/agent/auth.json` for `pi`. A raw Claude API key has
684
696
  no file.
@@ -686,6 +698,10 @@ the proven list is the test run of 2026-10-05. -->
686
698
  `ORCHESTRA_WORKER_CLAUDE_OAUTH_TOKEN` for a Claude subscription token; `ORCHESTRA_WORKER_OPENAI_API_KEY`
687
699
  or `ORCHESTRA_WORKER_CODEX_PERSONAL_ACCESS_TOKEN` for `codex`. With a gateway key, keep the agent's
688
700
  model gateway on. The platform sets the gateway address.
701
+ - An install replaces the service an earlier release registered as `SfdkWorker` on Windows,
702
+ `sfdk-worker.service` on Linux or `ai.livingcomputers.sfdk.worker` on macOS. The new ones are
703
+ `OrchestraWorker`, `orchestra-worker.service` and `ai.livingcomputers.orchestra.worker`. Change your own
704
+ scripts that name the old ones.
689
705
  - Never restart the worker service to load a key. A restart after the worker registered stops the
690
706
  machine's work, and the platform parks the VM.
691
707
  - The details: the orchestra-fdk skill's `references/environment.md`, "Tokens on a compute provider's
@@ -68,8 +68,10 @@ const factory = new Factory({ name: "acme-support" });
68
68
  new Secret(factory, "openai-api-key", {
69
69
  name: "OpenAI API key",
70
70
  value: SecretValue.fromEnv("OPENAI_API_KEY_VALUE"), // read from env at apply — never in the plan
71
- hosts: ["api.openai.com"], // where the edge presents it
72
- injection: { headers: [{ name: "Authorization", value: "Bearer ${secret}" }] }, // how
71
+ injection: {
72
+ headers: [{ name: "Authorization", value: "Bearer ${secret}" }], // how the edge presents it
73
+ hosts: ["api.openai.com"], // where
74
+ },
73
75
  });
74
76
 
75
77
  const runtime = new Environment(factory, "runtime", {
@@ -554,26 +556,29 @@ token the platform mints. The **Claude sign-in recipe** is the one to copy when
554
556
  on a person's Claude subscription token rather than an API key:
555
557
 
556
558
  ```ts
557
- new Secret(factory, "claude-sign-in", {
559
+ const claudeSignIn = new Secret(factory, "claude-sign-in", {
558
560
  value: SecretValue.fromEnv("ORCHESTRA_FDK_CLAUDE_CODE_OAUTH_TOKEN"),
559
- hosts: ["api.anthropic.com"],
560
561
  injection: {
561
562
  headers: [{ name: "authorization", value: "Bearer ${secret}" }],
562
563
  removeHeaders: ["x-api-key"], // stripped before injection
564
+ hosts: ["api.anthropic.com"],
563
565
  },
564
566
  });
565
567
 
566
568
  // …in the Environment's props:
567
- env: { CLAUDE_CODE_OAUTH_TOKEN: "software-factory-egress-placeholder", ANTHROPIC_API_KEY: "" },
569
+ env: { CLAUDE_CODE_OAUTH_TOKEN: claudeSignIn, ANTHROPIC_API_KEY: "" },
568
570
  egress: EgressPolicy.allowAll()
569
571
  .allow("claude-sign-in", { hosts: ["api.anthropic.com"], priority: 300 }),
570
572
  ```
571
573
 
572
574
  It injects `authorization` and nothing else. **Never inject an `anthropic-beta` header**: an injected
573
- one replaces the harness's own list of beta flags, and every call then answers 400. The placeholder
574
- `CLAUDE_CODE_OAUTH_TOKEN` makes the harness sign in the subscription way and send its own list, and the
575
- edge swaps the real token in. The empty `ANTHROPIC_API_KEY` covers the runtime image's placeholder key,
576
- which the harness would otherwise send instead.
575
+ one replaces the harness's own list of beta flags, and every call then answers 400. A Secret as an `env`
576
+ value is written as its reference, `orchestra:secret/<id>`, with the id resolved at apply. In
577
+ `CLAUDE_CODE_OAUTH_TOKEN` it makes the harness sign in the subscription way and send its own list, and the
578
+ edge reads the reference where the token goes and swaps the real token in. The edge adds a credential only
579
+ to a request that names one, or that an environment default covers (`references/environment.md`, "Naming a
580
+ credential"). The empty `ANTHROPIC_API_KEY` covers the runtime image's placeholder key, which the harness
581
+ would otherwise send instead.
577
582
 
578
583
  - Sources: `SecretValue.fromEnv(NAME)`, `SecretValue.fromCommandLine(label?)` and
579
584
  `SecretValue.fromGatewayKey(key)`, plus the derived recipes `basicAuth`, `registryCredential` and
@@ -613,7 +618,7 @@ which the harness would otherwise send instead.
613
618
  `GatewayKey` you declare in the same factory — `new GatewayKey(factory, "model-gateway-key", props)`,
614
619
  whose only props are `slug`, `displayName`, `monthlyCreditCap` and `labels` — so apply mints the
615
620
  key and writes the token it shows *exactly once* straight into this Secret, which the edge then
616
- adds on its `hosts` like any other. It is legal only as the whole top-level value of exactly one Secret:
621
+ adds on its recipe's `hosts` like any other. It is legal only as the whole top-level value of exactly one Secret:
617
622
  nesting it inside another recipe, or a key with zero or two Secrets, is a synth error, and there
618
623
  is no importer because an existing key's token is unrecoverable. Rotation is new-slug-only.
619
624
  - A **`ModelProviderKey`** is the other credential you can declare: your organization's own key for
@@ -648,11 +653,12 @@ which the harness would otherwise send instead.
648
653
  suppresses it and none can supply it either, so a headless apply of a `fromCommandLine`
649
654
  declaration refuses at *every* level and tells you to run from a terminal or change the source —
650
655
  it never recommends a policy lever that could not clear it.
651
- - Presenting a secret on the network: the `Secret` itself declares `hosts` (where: an exact host,
652
- `*.suffix`, or `*`) and an `injection` recipe (how: the headers it sets, reading `${secret}`, or
653
- `${secret.<field>}` for a `SecretValue.fields` value). Egress rules carry no credential, so a rule
654
- that passes `credential` or `credentials` is a synth error; a Secret with no `hosts` is presented
655
- nowhere.
656
+ - Presenting a secret on the network: the `Secret` itself declares an `injection` recipe — how: the
657
+ headers it sets, reading `${secret}`, or `${secret.<field>}` for a `SecretValue.fields` value; and
658
+ where: its `hosts`, each an exact host, `*.suffix`, or `*`. Egress rules carry no credential, so a rule
659
+ that passes `credential` or `credentials` is a synth error. A Secret with no `injection` is stored
660
+ only, and one whose recipe has no `hosts` is presented nowhere. The top-level `hosts` prop is
661
+ deprecated: it is moved into the recipe for one release.
656
662
 
657
663
  ## How the agents reach a model
658
664
 
@@ -668,9 +674,9 @@ on their own computer; run from your shell, its browser step hangs. Never read o
668
674
  At apply, the shell exports it from that file into the variable the Secret reads. Declare the Claude
669
675
  sign-in recipe (§ Secrets are recipes, never values): a `Secret`, an egress rule that allows
670
676
  `api.anthropic.com` and sets `Authorization: Bearer ${secret}` with `x-api-key` removed, and, in the
671
- environment's `env`, `CLAUDE_CODE_OAUTH_TOKEN: "software-factory-egress-placeholder"` and an empty
672
- `ANTHROPIC_API_KEY`. Turn `enableModelGateway` off for the agent. The machine holds only the
673
- placeholder; the edge adds the token. Copying the renewing credentials file instead is the fallback,
677
+ environment's `env`, `CLAUDE_CODE_OAUTH_TOKEN` set to that Secret and an empty `ANTHROPIC_API_KEY`.
678
+ Turn `enableModelGateway` off for the agent. The machine holds only the Secret's reference; the edge
679
+ adds the token. Copying the renewing credentials file instead is the fallback,
674
680
  and follows the ChatGPT sign-in steps below.
675
681
  - **Codex: a personal access token, where the workspace allows one.** A ChatGPT Business or Enterprise
676
682
  member creates it at chatgpt.com/admin/access-tokens, once the workspace owner allows access tokens,
@@ -762,7 +768,7 @@ An environment's `egress` is a policy you build up from one of **two starting po
762
768
  - **`EgressPolicy.allowAll()`** allows every host no rule refuses.
763
769
 
764
770
  Rules fold onto either one the same way: a rule only opens or closes the network, a credential still goes
765
- only to the hosts its own `hosts` cover, and a deny still refuses.
771
+ only to the hosts its own recipe's `hosts` cover, and a deny still refuses.
766
772
 
767
773
  Most of what an agent needs to reach arrives ready-made as a **bundle** — a list of rules folded onto
768
774
  the policy by one `applyPreset(...)` call — so **reach for the bundles first** and hand-write allow/deny
@@ -1079,7 +1085,7 @@ orchestra gateway-keys create --slug ci-smoke --output-file key.json
1079
1085
  export, a CI secret) suppresses the only prompt you would have seen, the command says so in one
1080
1086
  line on **stderr**, naming the lever — stdout stays the response document.
1081
1087
  - **Writing to a factory-managed resource warns.** `apply` stamps what it manages with an
1082
- `sfdk.managedBy` labels key naming the owning factory, and a write to a stamped resource says so:
1088
+ `orchestra.managedBy` labels key naming the owning factory, and a write to a stamped resource says so:
1083
1089
  the next `apply` reverts it. It is a warning, not a refusal — the fallback has to stay usable in an
1084
1090
  emergency — and it is the CLI telling you this belongs in `main.ts`.
1085
1091
 
@@ -219,7 +219,7 @@ the `agent` user. The worker writes a sign-in file only when none is there, so t
219
219
  **Before any cloud apply,** search `<factory>` for that marker, and for any credential in the setup script,
220
220
  in the environment's settings (`ENV`, which becomes `provisionSpec.env`), in the agents' instructions, and
221
221
  in every work-item description you write. Remove each one. Use a Secret
222
- instead: a gateway key, or a Secret whose `hosts` name where the edge adds it. Tell the developer why: the setup script and
222
+ instead: a gateway key, or a Secret whose recipe's `hosts` name where the edge adds it. Tell the developer why: the setup script and
223
223
  the settings are stored as plain text and shown on every read.
224
224
 
225
225
  ### Start the local environment
@@ -318,7 +318,7 @@ The local key is never carried over. It stays on the developer's computer. In th
318
318
  Orchestra Secret, and our network edge adds it to each model call, so no machine and no person holds it:
319
319
 
320
320
  - For the gateway, `apply` creates the gateway key and its secret. The starter already declares them.
321
- - For a subscription token or a provider key, declare a Secret whose `hosts` name the provider's host, and a
321
+ - For a subscription token or a provider key, declare a Secret whose recipe's `hosts` name the provider's host, and a
322
322
  rule that opens that host, as phase 2 of the orchestra-fdk skill, "The model route", says (orchestra skill,
323
323
  sections 6 and 7).
324
324
 
@@ -378,12 +378,15 @@ into `orchestra-factory-notes.md`.
378
378
  captured Windows VM does not register. The worker's own command, `worker install-machine --cloud
379
379
  azure`, adds the three parts the image must contain. The section "A team's own image" below gives the
380
380
  steps:
381
- - The worker's release package, at `C:\ProgramData\sfdk\worker\package`.
382
- - The worker's Windows service, `SfdkWorker`. It runs `worker.exe run --root C:\ProgramData\sfdk\worker
381
+ - The worker's release package, at `C:\ProgramData\orchestra\worker\package`.
382
+ - The worker's Windows service, `OrchestraWorker`. It runs `worker.exe run --root C:\ProgramData\orchestra\worker
383
383
  --service` as LocalSystem. It is demand-start (`Manual`), not automatic. On a failure it restarts
384
384
  three times, 2 seconds apart, and a clean stop with a non-zero exit code counts as a failure.
385
385
  - The startup task `orchestra-azure-userdata`. It runs `worker.exe boot-files --cloud azure` as SYSTEM
386
- at each boot. It writes the files that the VM's startup data carries, then starts `SfdkWorker`.
386
+ at each boot. It writes the files that the VM's startup data carries, then starts `OrchestraWorker`.
387
+ - An install replaces the service an earlier release registered as `SfdkWorker`. Change your own
388
+ scripts that name it. On an image an earlier release built, the startup task still starts
389
+ `SfdkWorker`.
387
390
 
388
391
  Our image runs the same command, from `install-vm-runtime.ps1` and the image template
389
392
  `infra/packer/azure-windows-runtime.pkr.hcl`. Under a Launcher, the image needs no WinDivert, no
@@ -463,7 +466,7 @@ agent can run the team's own `docker compose up` unchanged.
463
466
  *machine endpoint*, a web address that only signed-in members of the organization can open.
464
467
  4. Write the app's own settings files (`.env.local`) into the Set up item's description, because the code
465
468
  is not on the machine when the setup script runs. Put only local or test values there. A real secret
466
- becomes a Secret whose `hosts` name where the edge adds it.
469
+ becomes a Secret whose recipe's `hosts` name where the edge adds it.
467
470
  5. Choose the size by what CI uses for the same work. The template's sizes are fixed per agent: lg is
468
471
  32 CPU, md 16, sm 2.
469
472
  6. When the project runs containers, keep the disk at `/var/lib/docker`. The starter declares it. It keeps
@@ -650,8 +653,8 @@ failure stops the run and names the step. The team can run it again.
650
653
  - It refuses a VM that has already run as a machine, and a running worker service. Build on a new VM.
651
654
  - On Windows it creates the worker's folders for SYSTEM and the administrators only, so other accounts on
652
655
  the VM cannot add programs or read the boot files.
653
- - It copies the worker's package to the machine-wide root, `C:\ProgramData\sfdk\worker` or
654
- `/var/lib/sfdk/worker`.
656
+ - It copies the worker's package to the machine-wide root, `C:\ProgramData\orchestra\worker` or
657
+ `/var/lib/orchestra/worker`.
655
658
  - On Linux it creates the `agent` account, uid 1000, home `/home`.
656
659
  - It defines the worker's service, and the startup task that writes the boot files and starts the worker.
657
660
  - `--harness claude` preinstalls a harness. Without it, the harness installs itself on its first turn.
@@ -691,7 +694,7 @@ a Launcher machine, and the team installs their own tools.
691
694
  After each restart, run the check. Stop when it shows `"restartPending":false`:
692
695
 
693
696
  ```powershell
694
- & "$env:ProgramData\sfdk\worker\package\worker.exe" check --profile launcher --output json
697
+ & "$env:ProgramData\orchestra\worker\package\worker.exe" check --profile launcher --output json
695
698
  ```
696
699
 
697
700
  6. Generalize with Sysprep. It takes about 4 minutes, and the VM then stops:
@@ -815,8 +818,8 @@ Tokens on a compute provider's machines:
815
818
  carries them. Tell the developer this. If it is not acceptable, use route 2 or 3, or a Secret added
816
819
  at our network edge where one stands in front.
817
820
  2. **Baked into the team's image**, at machine scope, before Sysprep or generalizing. On Windows, that
818
- is the `SfdkWorker` service's `Environment` registry value. On Linux, it is a drop-in on
819
- `sfdk-worker.service`.
821
+ is the `OrchestraWorker` service's `Environment` registry value. On Linux, it is a drop-in on
822
+ `orchestra-worker.service`.
820
823
  3. **The `onStartAgent` hook** writes the harness's own key file in the agent's home:
821
824
  `.claude/.credentials.json` for a Claude sign-in, `.codex/auth.json` for `codex`, or
822
825
  `.pi/agent/auth.json` for `pi`. The worker keeps a real value it finds there. The hook can read the
@@ -870,6 +873,40 @@ that need no private service.
870
873
  **Check:** the typecheck and the synth pass, and the plan is as in item 3. Go back to the orchestra-fdk
871
874
  skill's phase 4 for the apply.
872
875
 
876
+ ### Naming a credential
877
+
878
+ The network edge adds a credential to a request in two cases only. A request that names none and that no
879
+ environment default covers leaves with the headers it was sent, a placeholder included.
880
+
881
+ - **The request names it.** Set the variable a tool reads its token from to a `Secret` or a `Connection`
882
+ in the environment's `env`. Apply writes `orchestra:secret/<id>` or `orchestra:connection/<id>`, and the
883
+ tool sends that reference where its token goes. The edge reads it and adds the credential, if the
884
+ agent may use it.
885
+
886
+ | Tool | Variable |
887
+ |---|---|
888
+ | claude, pi | `ANTHROPIC_API_KEY`, or `CLAUDE_CODE_OAUTH_TOKEN` for a subscription token |
889
+ | codex | `ORCHESTRA_WORKER_OPENAI_API_KEY`, which the worker writes into codex's own auth file |
890
+ | `gh` | `GH_TOKEN` |
891
+ | `glab` | `GITLAB_TOKEN`, which `glab` sends as `Private-Token` |
892
+ | git | `ORCHESTRA_GIT_CREDENTIAL`: the image's git credential helper answers it when the server asks |
893
+ | `linear`, `slack`, `teams` | `--connection-id <id>` on the command; it reads no connection |
894
+
895
+ `routeModelsViaGateway({ key })` sets `ANTHROPIC_API_KEY` and `ORCHESTRA_WORKER_OPENAI_API_KEY` to the
896
+ key Secret itself, so declare neither beside it.
897
+
898
+ - **An environment credential covers the host.** `credentials` lists Secrets and Connections. A
899
+ request from the environment's machines that names none gets the first whose hosts cover its host,
900
+ whatever the agent. `replace` is true unless set: with `replace: false`, a request that carries its own
901
+ value in the credential's header keeps it and gets nothing. Adding an entry needs your own use of that
902
+ credential.
903
+
904
+ ```ts
905
+ credentials: [{ credential: githubConnection }, { credential: modelKey, replace: false }],
906
+ ```
907
+
908
+ With no `credentials` declared, apply leaves the live list alone; `[]` clears it.
909
+
873
910
  ## 7. The cloud
874
911
 
875
912
  The cloud is the fallback when no local mode can run. It is also the confirmation after the local proof:
@@ -914,7 +951,7 @@ agent's shell, its browser step hangs.
914
951
  copy it, then `umask 077 && pbpaste | tr -d '\n' > <file> && pbcopy </dev/null`. At apply, the shell
915
952
  exports it from that file into the variable a `Secret` reads. An egress rule allows `api.anthropic.com`
916
953
  and adds `Authorization: Bearer ${secret}`, removing `x-api-key`. The environment's `env` holds only
917
- `CLAUDE_CODE_OAUTH_TOKEN=software-factory-egress-placeholder` and an empty `ANTHROPIC_API_KEY`, and the
954
+ `CLAUDE_CODE_OAUTH_TOKEN` set to the Secret, its reference, and an empty `ANTHROPIC_API_KEY`, and the
918
955
  agent's gateway setting is off. Never put the token itself in `env`. The Claude sign-in recipe in
919
956
  `references/authoring.md` shows the declaration. Copying the renewing credentials file is the fallback,
920
957
  and follows the ChatGPT sign-in steps below.
@@ -4,7 +4,10 @@ Give access with a Secret the network edge adds, or with the provider's platform
4
4
  so add only the rule that adds the credential. Git over HTTPS and the REST API often take the token in
5
5
  different forms: give each its own rule, scoped by host or by path. A connection also brings events, which
6
6
  a `Trigger` turns into work. The agent installs or uses whatever git or API client it prefers. The edge adds
7
- the credential to its requests.
7
+ the credential only to a request that names it, or that an environment default covers
8
+ (`../environment.md`, "Naming a credential"). Name it in the environment's `env`: `GH_TOKEN` for `gh`,
9
+ `GITLAB_TOKEN` for `glab`, and `ORCHESTRA_GIT_CREDENTIAL` for git, each set to the Secret or the
10
+ Connection.
8
11
 
9
12
  Phase 3, "From a recipe to code", turns a recipe into a Secret and a rule.
10
13
 
@@ -21,8 +24,25 @@ project or the profile; Azure DevOps **User settings › Personal access tokens*
21
24
 
22
25
  ## Git over HTTPS
23
26
 
24
- Git sends Basic auth. The edge adds it, so the agent clones `https://<host>/<path>.git` with no user name
25
- and no password in the address.
27
+ Git sends Basic auth. The image's git credential helper answers the reference in `ORCHESTRA_GIT_CREDENTIAL`
28
+ as the password when the server asks, and the edge replaces it, so the agent clones
29
+ `https://<host>/<path>.git` with no user name and no password in the address. A default that covers the
30
+ host adds the credential with no reference.
31
+
32
+ A git token Secret stores the encoded value `base64("<user>:<token>")` and presents it as `Basic`. The
33
+ recipe has no base64 step, so encode the pair first. `SecretValue.basicAuth(user, value)` encodes it at
34
+ apply; to store it through the API or the console, encode it yourself:
35
+
36
+ ```sh
37
+ printf 'x-access-token:%s' "$TOKEN" | base64 | tr -d '\n'
38
+ ```
39
+
40
+ ```ts
41
+ new Secret(factory, "github-git", {
42
+ value: SecretValue.basicAuth("x-access-token", SecretValue.fromEnv("ORCHESTRA_FDK_GITHUB_TOKEN")),
43
+ injection: { headers: [{ name: "Authorization", value: "Basic ${secret}" }], hosts: ["github.com"] },
44
+ });
45
+ ```
26
46
 
27
47
  - **GitHub:** the user name is `x-access-token`, and the password is the token:
28
48
  `SecretValue.basicAuth("x-access-token", SecretValue.fromEnv("ORCHESTRA_FDK_GITHUB_TOKEN"))`. The GitHub
@@ -42,8 +62,9 @@ the first declared wins for every request. So scope the API rule to `paths: ["/a
42
62
  priority, 310. The git rule at 300 takes the rest of the host.
43
63
 
44
64
  The API rule replaces the `PRIVATE-TOKEN` header. A project access token must be sent this way: sent as
45
- `Authorization: Bearer` beside a placeholder `PRIVATE-TOKEN`, GitLab answers 401. A personal token works the
46
- same way.
65
+ `Authorization: Bearer` beside another `PRIVATE-TOKEN`, GitLab answers 401. A personal token works the
66
+ same way. `glab` sends `GITLAB_TOKEN` as `Private-Token`, so set `GITLAB_TOKEN` to the Secret or the
67
+ Connection, and the edge reads the reference there.
47
68
 
48
69
  With the `gitlab` connection and `Auth.oauth()`, use `.allow(gitlab, { paths: ["/api/*"], priority: 310 })`
49
70
  for the API instead, and keep the git rule. GitLab does not let the platform register the webhook. The
@@ -19,7 +19,7 @@ npx --package @orchestraworks/cli orchestra workflows list --all-pages --output
19
19
  npx --package @orchestraworks/cli orchestra connections list --output json
20
20
  ```
21
21
 
22
- A row that an apply made carries the label `sfdk.managedBy`, whose value is the factory's name.
22
+ A row that an apply made carries the label `orchestra.managedBy`, whose value is the factory's name.
23
23
 
24
24
  1. Infer what the developer most likely wants:
25
25
  - *No agents and no pipelines:* the account is empty. Infer **a new factory**.
@@ -90,7 +90,7 @@ your shell, its browser step hangs. You never read or copy the value.
90
90
 
91
91
  - *Claude.* `claude setup-token` prints a long-lived token. The developer saves it in the private file as
92
92
  `ORCHESTRA_FDK_CLAUDE_CODE_OAUTH_TOKEN`. The factory stores it as a Secret, and the network edge adds it to
93
- calls to `api.anthropic.com`. The machine holds only a placeholder token and an empty API key. Declare the
93
+ calls to `api.anthropic.com` that name it. The machine holds only the Secret's reference and an empty API key. Declare the
94
94
  Claude sign-in recipe from `../authoring.md`, "Secrets are recipes, never values". Never put the token in
95
95
  the environment's settings: they are stored and shown in plain text.
96
96
  - *Codex.* A personal access token, which begins `at-`. A ChatGPT Business or Enterprise member makes it at
@@ -83,7 +83,8 @@ C, "Tokens on a compute provider's machines").
83
83
 
84
84
  - A `401` from the model provider, or "Invalid API key": the agent has no model key. Check the route the
85
85
  team chose: the environment's `env`, the image's worker service environment (on Windows, the
86
- `SfdkWorker` service's `Environment` registry value), or the key file the `onStartAgent` hook writes.
86
+ `OrchestraWorker` service's `Environment` registry value, or `SfdkWorker` on an image an earlier
87
+ release built), or the key file the `onStartAgent` hook writes.
87
88
  - The image's value is a real key and `env` declares another: the image's value wins. A declared value
88
89
  replaces only our image's placeholder.
89
90
  - Never restart the worker service to load a key. A restart after the worker registered stops the
@@ -109,7 +110,7 @@ C, "Tokens on a compute provider's machines").
109
110
  - A trigger that could not start a session shows the reason on the trigger's `lastDecision`. A work item
110
111
  shows a feed note: `No session started for <agent>: ...`.
111
112
  - The model gateway answers `403` with `credits_exhausted` or `budget_exceeded`, in the model provider's
112
- own error shape, with a `[sfdk]` prefix on the message.
113
+ own error shape, with an `[orchestra]` prefix on the message.
113
114
  - `403` with `PLAN_FEATURE_DISABLED`: the plan does not include the feature. This is not a credit problem.
114
115
 
115
116
  `maxSessionsPerMachine` is not an error. A session past it starts another machine.
@@ -68,13 +68,16 @@ const github = new Connection(factory, "github", {
68
68
  install: { permissions: ["contents:write"] },
69
69
  });
70
70
 
71
- // The edge adds the gateway key on the gateway's host. The runtime image's placeholder key rides x-api-key, and
72
- // the gateway refuses two keys that disagree, so the recipe removes it.
71
+ // The edge adds the gateway key on the gateway's host. Claude sends the key's reference in x-api-key, and the
72
+ // gateway refuses two keys that disagree, so the recipe removes it.
73
73
  const gatewayHost = required("ORCHESTRA_FDK_MODEL_GATEWAY_HOST");
74
74
  const gatewayKey = new Secret(factory, "model-gateway-key", {
75
75
  value: SecretValue.fromGatewayKey(new GatewayKey(factory, "model-gateway", { displayName: "__FACTORY_NAME__ bug fixing" })),
76
- hosts: [gatewayHost],
77
- injection: { headers: [{ name: "Authorization", value: "Bearer ${secret}" }], removeHeaders: ["x-api-key"] },
76
+ injection: {
77
+ headers: [{ name: "Authorization", value: "Bearer ${secret}" }],
78
+ removeHeaders: ["x-api-key"],
79
+ hosts: [gatewayHost],
80
+ },
78
81
  });
79
82
  // init-if: anthropic
80
83
 
@@ -59,9 +59,12 @@ export function declareKeys(factory: Factory): Keys {
59
59
  name: "Model gateway key",
60
60
  description: "Added to model calls at the network edge. The agents never see it.",
61
61
  value: SecretValue.fromGatewayKey(gatewayKey),
62
- hosts: [DEFAULT_MODEL_GATEWAY_HOST],
63
- // The runtime image's placeholder key rides x-api-key, and the gateway refuses two keys that disagree.
64
- injection: { headers: [{ name: "Authorization", value: "Bearer ${secret}" }], removeHeaders: ["x-api-key"] },
62
+ // Claude sends the key's reference in x-api-key, and the gateway refuses two keys that disagree.
63
+ injection: {
64
+ headers: [{ name: "Authorization", value: "Bearer ${secret}" }],
65
+ removeHeaders: ["x-api-key"],
66
+ hosts: [DEFAULT_MODEL_GATEWAY_HOST],
67
+ },
65
68
  });
66
69
  return { modelGateway };
67
70
  }
@@ -9,7 +9,7 @@
9
9
  "typecheck": "tsc -p tsconfig.json"
10
10
  },
11
11
  "dependencies": {
12
- "@orchestraworks/cli": "^0.5.0"
12
+ "@orchestraworks/cli": "^0.6.0"
13
13
  },
14
14
  "devDependencies": {
15
15
  "@types/node": "^22.10.0",
@@ -66,13 +66,16 @@ const github = new Connection(factory, "github", {
66
66
  install: { permissions: ["contents:write"] },
67
67
  });
68
68
 
69
- // The edge adds the gateway key on the gateway's host. The runtime image's placeholder key rides x-api-key, and
70
- // the gateway refuses two keys that disagree, so the recipe removes it.
69
+ // The edge adds the gateway key on the gateway's host. Claude sends the key's reference in x-api-key, and the
70
+ // gateway refuses two keys that disagree, so the recipe removes it.
71
71
  const gatewayHost = required("ORCHESTRA_FDK_MODEL_GATEWAY_HOST");
72
72
  const gatewayKey = new Secret(factory, "model-gateway-key", {
73
73
  value: SecretValue.fromGatewayKey(new GatewayKey(factory, "model-gateway", { displayName: "todo-app bug fixing" })),
74
- hosts: [gatewayHost],
75
- injection: { headers: [{ name: "Authorization", value: "Bearer ${secret}" }], removeHeaders: ["x-api-key"] },
74
+ injection: {
75
+ headers: [{ name: "Authorization", value: "Bearer ${secret}" }],
76
+ removeHeaders: ["x-api-key"],
77
+ hosts: [gatewayHost],
78
+ },
76
79
  });
77
80
 
78
81
  // The image tag hashes the runtime image and every file under image/: a rebuilt image under an unchanged tag
@@ -9,7 +9,7 @@
9
9
  "typecheck": "tsc -p tsconfig.json"
10
10
  },
11
11
  "dependencies": {
12
- "@orchestraworks/cli": "^0.5.0"
12
+ "@orchestraworks/cli": "^0.6.0"
13
13
  },
14
14
  "devDependencies": {
15
15
  "@types/node": "^22.10.0",