@jc_stack/ez-agents 0.1.0-beta.33 → 0.1.0-beta.34

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 (65) hide show
  1. package/.env.example +4 -0
  2. package/CHANGELOG.md +25 -0
  3. package/Dockerfile +4 -0
  4. package/README.md +26 -18
  5. package/THIRD_PARTY_NOTICES.md +4 -4
  6. package/compose.yaml +2 -1
  7. package/docker/smoke.mjs +6 -3
  8. package/docs/application-channel.md +6 -0
  9. package/docs/architecture/ai-selection.md +3 -1
  10. package/docs/docker-runtime.md +27 -10
  11. package/docs/host-service.md +17 -6
  12. package/docs/plugins.md +15 -0
  13. package/docs/repair.md +1 -1
  14. package/docs/setup.md +88 -14
  15. package/docs/standalone-cli.md +6 -5
  16. package/package.json +1 -1
  17. package/scripts/smoke-scheduler.ts +2 -1
  18. package/src/agent-install.ts +20 -6
  19. package/src/ai-cli.ts +9 -7
  20. package/src/ai.ts +7 -7
  21. package/src/application-channel.ts +10 -10
  22. package/src/codex-session.ts +5 -3
  23. package/src/config.ts +6 -1
  24. package/src/control-state.ts +12 -1
  25. package/src/executor.ts +91 -9
  26. package/src/host-executor.ts +25 -6
  27. package/src/index.ts +1 -0
  28. package/src/install-config.ts +2 -2
  29. package/src/install-tools.mjs +3 -1
  30. package/src/isolation.ts +25 -0
  31. package/src/menu.ts +9 -6
  32. package/src/plugins/manager.mjs +49 -6
  33. package/src/reply-context.ts +0 -1
  34. package/src/setup.ts +11 -7
  35. package/src/task-executor.ts +4 -1
  36. package/src/updates/runtime.mjs +1 -1
  37. package/src/updates/supervisor.mjs +17 -3
  38. package/src/workspace-confine.ts +38 -0
  39. package/src/workspace.ts +23 -13
  40. package/templates/agent/AGENTS.md +4 -14
  41. package/templates/agent-guidance.md +13 -110
  42. package/templates/agent-purpose.md +1 -0
  43. package/test/agent-guidance.test.ts +8 -12
  44. package/test/agent-install.test.ts +34 -0
  45. package/test/ai-cli.test.ts +18 -0
  46. package/test/ai.test.ts +48 -0
  47. package/test/application-channel.test.ts +5 -2
  48. package/test/application-controls.test.ts +56 -3
  49. package/test/application-only.test.ts +2 -0
  50. package/test/codex-context.test.ts +61 -1
  51. package/test/codex-session.test.ts +6 -1
  52. package/test/config.test.ts +17 -2
  53. package/test/engine-handoff.test.ts +5 -2
  54. package/test/executor.test.ts +38 -1
  55. package/test/failure.test.ts +6 -0
  56. package/test/host-executor.test.ts +6 -0
  57. package/test/install-config.test.ts +17 -6
  58. package/test/install-tools.test.mjs +6 -0
  59. package/test/plugin-manager.test.mjs +24 -3
  60. package/test/scheduler.test.ts +2 -2
  61. package/test/updates.test.mjs +10 -1
  62. package/test/workspace-confine.test.ts +47 -0
  63. package/test/workspace.test.ts +23 -10
  64. package/templates/agent/SOUL.md +0 -11
  65. package/templates/agent/USER.md +0 -5
package/.env.example CHANGED
@@ -48,6 +48,10 @@ EZ_REPAIR_ENABLED=true
48
48
  # Optional native application-only runtime: no Telegram bot or token.
49
49
  # Requires EZ_APPLICATION_PORT; administrator bootstrap uses the real operator ID.
50
50
  # EZ_TELEGRAM_ENABLED=false
51
+ # Isolation class from agent creation (default isolated).
52
+ # EZ_ISOLATION=isolated
53
+ # EZ_EXECUTOR_TRANSPORT=local
54
+ # Host-capable: EZ_ISOLATION=host-capable and EZ_EXECUTOR_TRANSPORT=host
51
55
  # Private application-only local container isolation (optional; default workspace-write).
52
56
  # EZ_CODEX_SANDBOX=external
53
57
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.0-beta.34
4
+
5
+ - Correct setup guidance for host-capable custom Codex providers and replace
6
+ stale pinned-beta installation guidance with current release records.
7
+
8
+ - Record an isolation class at agent creation. The default is `isolated`: the
9
+ native CLI runs in the relay (`EZ_EXECUTOR_TRANSPORT=local`). Pass
10
+ `--isolation host-capable` to reuse the installer UID and host CLI login.
11
+ Mismatched class and transport fail closed. Isolated Codex sets
12
+ `EZ_CODEX_SANDBOX=external`. Host transport is not started for isolated
13
+ agents. Unlabeled existing host transports stay host-capable.
14
+ - Install pinned Codex CLI `0.153.4` in the relay runtime image so isolated
15
+ agents resolve `codex` on PATH. Isolated jobs keep auth in
16
+ `control/cli/codex` and do not link the operator `~/.codex`.
17
+
18
+ - Confine macOS host jobs to their bound workspace and prevent cross-agent
19
+ plugin registry access.
20
+ - Support agent-scoped OpenRouter models through the native Codex engine and
21
+ start a new native session when the provider or model changes.
22
+ - Resolve native CLIs from the host PATH, expose captured presets in application
23
+ run receipts, and seed one purpose-scoped AGENTS.md for new agents.
24
+
3
25
  ## 0.1.0-beta.33
4
26
 
5
27
  - Allow compatible plugin updates to add commands to existing services, registering
@@ -21,6 +43,9 @@
21
43
  host CLI configuration needed for restricted read-only plugin commands.
22
44
  - Clarify domain-agent positioning while preserving native engine ownership.
23
45
 
46
+ - Prevent accidental cross-agent plugin access by rejecting relay-bound registry
47
+ calls outside the owning workspace, and restrict its host-folder bindings to
48
+ workspace roots explicitly granted by the host.
24
49
  - Answer native app-server requests with their current protocol shapes. Scheduled
25
50
  `codex-gui` browser work now surfaces approval requests and grants only its
26
51
  active turn session-scoped HTTPS browser-origin and Chrome app elicitations,
package/Dockerfile CHANGED
@@ -9,6 +9,10 @@ COPY . .
9
9
  FROM dependencies AS test
10
10
  RUN pnpm verify
11
11
  FROM dependencies AS runtime
12
+ # Keep this pin aligned with TASK_CODEX_VERSION. Isolated agents run this CLI
13
+ # in the relay; do not bind-mount the operator's ~/.codex.
14
+ ARG CODEX_CLI_VERSION=0.153.4
15
+ RUN npm install -g @openai/codex@${CODEX_CLI_VERSION} && command -v codex
12
16
  RUN chmod +x docker/entrypoint.sh bin/ezenciel-agents* && mkdir -p /state/control /state/home /workspace && chown node:node /state/control /state/home /workspace
13
17
  RUN node -e 'for (const [name, target] of Object.entries(require("./package.json").bin)) require("node:fs").symlinkSync("/app/" + target, "/usr/local/bin/" + name)'
14
18
  ENV HOME=/state/home EZ_AGENT_WORKSPACE=/workspace EZ_CONTROL_DIR=/state/control EZ_EXECUTOR_CLI=grok PATH=/app/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
package/README.md CHANGED
@@ -148,15 +148,19 @@ the user's request and preserve its scope.
148
148
 
149
149
  ## Package and runtime
150
150
 
151
- **Beta 0.1.0-beta.12 — for testing on a trusted machine.** Offline and Docker
152
- fixtures are verified. Live account onboarding and reboot acceptance for this
153
- release are deferred; this is not a production-readiness claim. The source branch
154
- may contain changes newer than the published beta; inspect the supplied artifact's
155
- documentation when installing a pinned release.
156
-
157
- The npm package is `@jc_stack/ez-agents` (channel `beta`). Download a pinned
158
- release with `npm pack @jc_stack/ez-agents@0.1.0-beta.12`, or get the tarball and SHA256SUMS from
159
- [GitHub prereleases](https://github.com/jdorado/ez-agents/releases/tag/v0.1.0-beta.12).
151
+ **Published beta releases are for testing on a trusted machine.** Offline and
152
+ Docker fixtures are verified for the selected release. Live account onboarding
153
+ and reboot acceptance remain beta gates; this is not a production-readiness
154
+ claim. The source branch may contain changes newer than the published beta, so
155
+ inspect the supplied artifact's documentation when installing a pinned release.
156
+
157
+ The npm package is `@jc_stack/ez-agents`. Approved beta publications use npm's
158
+ `latest` distribution tag while their versions and GitHub releases remain
159
+ prereleases. For a reproducible download, choose an exact version from
160
+ [GitHub prereleases](https://github.com/jdorado/ez-agents/releases) and substitute
161
+ it for `VERSION` in `npm pack @jc_stack/ez-agents@VERSION`.
162
+ You can also download the release's candidate tarball and verify its SHA-256
163
+ against the attached release receipt or release notes.
160
164
  GitHub remains under `jdorado`; npm uses `jc_stack`. Do not install the unrelated
161
165
  unscoped `ez-whatsapp` package.
162
166
 
@@ -242,28 +246,32 @@ credentials, startup, and verified pairing; never present this as human homework
242
246
  | `ezenciel-agents-install build` | Build once per artifact, with a private log and duplicate-build detection |
243
247
  | `ezenciel-agents-install status --deployment <path>` | Distinguish runtime/configuration, pairing and Telegram reply evidence |
244
248
  | `ezenciel-agents-create --register-cli <current-cli>` | Record the CLI that installs the initial package |
245
- | `ezenciel-agents-create --name <name> --purpose <purpose>` | Create an agent inheriting the installation CLI; token via stdin |
249
+ | `ezenciel-agents-create --name <name> --purpose <agent-brief>` | Create an agent from the installing LLM's compact brief; packaged Codex defaults isolated, other CLIs stay host-capable; token via stdin |
250
+ | `ezenciel-agents-create --name <name> --purpose <agent-brief> --isolation host-capable` | Explicitly reuse the host CLI and installer UID |
246
251
  | `ezenciel-agents-host` | Invoke the existing shared host CLI for this deployment |
247
- | `ezenciel-agents-setup configure <executor>` | Private configuration and missing starter files; preserves personal files |
248
- | `ezenciel-agents-setup configure <executor> --token-stdin` | Same, with the BotFather token supplied privately through stdin |
252
+ | `ezenciel-agents-setup configure <executor> --purpose-file <path>` | Private configuration and missing starter files; fresh workspaces require installer-scoped purpose; preserves personal files |
253
+ | `ezenciel-agents-setup configure <executor> --purpose-file <path> --token-stdin` | Same, with the BotFather token supplied privately through stdin |
249
254
  | `ezenciel-agents-setup service` | Start only the Docker relay bound by `docker.env` in the current directory; register the [host service](docs/host-service.md) separately |
250
255
  | `ezenciel-agents-owner status` | Inspect the owner and pending pairing requests |
251
256
  | `ezenciel-agents-owner approve <telegram-user-id>` | Approve the verified owner; never an arbitrary first sender |
252
257
  | `ezenciel-agents-setup status` | Inspect installed executor choices |
253
- | `ezenciel-agents-setup init` | Seed only missing workspace guidance |
258
+ | `ezenciel-agents-setup init --purpose-file <path>` | Seed missing workspace guidance; purpose is optional only when AGENTS.md already exists |
254
259
 
255
260
  Run runtime tools through `ezenciel-agents-docker run --rm relay <command>`
256
261
  as shown in the Docker guide. Keep the Docker engine available; Compose owns
257
- restart and shutdown. The installing agent registers the small host CLI transport with the native service manager.
262
+ restart and shutdown. Host-capable agents register the small host CLI transport with the native service manager. Isolated agents do not.
258
263
 
259
264
  ## Runtime and development reference
260
265
 
261
266
  The selected host CLI runs in this agent's persistent workspace and owns its
262
267
  Markdown/work files. New conversations and AI changes preserve those files.
263
- `AGENTS.md`, `SOUL.md`, and `USER.md` are seeded; `MEMORY.md` is
264
- optional. Installed plugin snippets and skill paths come from `ez tools list --details`; no tool inventory file needs maintenance. Received files go in `inbox/`, tasks and deliverables in `work/`.
265
- Credentials and control state stay outside the mind. File separation is not
266
- OS isolation against a process running as the same user.
268
+ One compact, purpose-scoped `AGENTS.md` is seeded; `MEMORY.md` is optional.
269
+ The installing LLM authors the initial brief from the owner's request and known
270
+ application context; Ez stores it without trying to infer the product itself.
271
+ Installed plugin snippets and skill paths come from `ez tools list --details`; no tool inventory file needs maintenance. Received files go in `inbox/`, tasks and deliverables in `work/`.
272
+ Credentials and control state stay outside the mind. Isolated agents use relay
273
+ mounts as the filesystem boundary. Host-capable file separation is not OS
274
+ isolation against a process running as the same user.
267
275
 
268
276
  In Telegram, **Conversations** (or `/chats`) lists this agent's saved Ez
269
277
  conversations by name. Tap a name to continue its native engine session and
@@ -8,7 +8,7 @@ The container is built locally; no bundled third-party binary image is promised.
8
8
 
9
9
  Direct runtime dependencies: grammy (MIT), tsx (MIT). Development dependencies
10
10
  include TypeScript (Apache-2.0) and Node type definitions (MIT). The runtime
11
- image installs ffmpeg and Debian packages: redistributing that image requires
12
- review of its exact package build, notices and corresponding-source obligations.
13
- Docker and the selected host AI CLI are separately installed products, not covered
14
- by this repository's MIT license.
11
+ image installs ffmpeg, Debian packages, and pinned `@openai/codex` (Apache-2.0):
12
+ redistributing that image requires review of its exact package build, notices and
13
+ corresponding-source obligations. Docker and a host-capable AI CLI remain
14
+ separately installed products, not covered by this repository's MIT license.
package/compose.yaml CHANGED
@@ -17,6 +17,7 @@ services:
17
17
  EZ_RELAY_UID: ${EZ_RELAY_UID:-1001}
18
18
  EZ_REPAIR_ENABLED: ${EZ_REPAIR_ENABLED:-true}
19
19
  EZ_AGENT_PURPOSE_FILE: /run/agent-purpose.md
20
+ EZ_ISOLATION: ${EZ_ISOLATION:-}
20
21
  EZ_EXECUTOR_TRANSPORT: ${EZ_EXECUTOR_TRANSPORT:-host}
21
22
  EZ_CHANNEL_BACKEND_URL: ${EZ_CHANNEL_BACKEND_URL:-}
22
23
  EZ_TELEGRAM_WEB_APP: ${EZ_TELEGRAM_WEB_APP:-}
@@ -50,7 +51,7 @@ services:
50
51
  retries: 3
51
52
  configs:
52
53
  agent_purpose:
53
- file: ${EZ_AGENT_PURPOSE_FILE:-./templates/agent/SOUL.md}
54
+ file: ${EZ_AGENT_PURPOSE_FILE:-./templates/agent-purpose.md}
54
55
  secrets:
55
56
  relay_env:
56
57
  file: ${EZ_RELAY_ENV_FILE:?Set EZ_RELAY_ENV_FILE to the private relay env file}
package/docker/smoke.mjs CHANGED
@@ -10,6 +10,7 @@ const volume = `${holder}-control`;
10
10
  const application = `${holder}-application`;
11
11
  const marker = 'qa-private-secret-never-in-executor';
12
12
  writeFileSync(join(dir, 'relay.env'), `TELEGRAM_BOT_TOKEN=${marker}\n`, { mode: 0o600 });
13
+ writeFileSync(join(dir, 'purpose.md'), 'Verify the packaged Ez runtime.\n', { mode: 0o644 });
13
14
  writeFileSync(join(dir, 'node'), `#!/bin/sh\nIFS= read -r value <&3\n[ "$value" = "TELEGRAM_BOT_TOKEN=${marker}" ] || exit 91\nexec /usr/local/bin/node "$@"\n`, { mode: 0o555 });
14
15
  const run = (args) => spawnSync('docker', args, { encoding: 'utf8', timeout: 60000 });
15
16
  try {
@@ -17,17 +18,19 @@ try {
17
18
  '--mount',`type=bind,src=${join(dir,'node')},dst=/qa/node,readonly`,'-e','PATH=/qa:/usr/local/bin:/usr/bin:/bin',image,'application','--help']);
18
19
  assert.equal(inherited.status,0,inherited.stderr);
19
20
  const nonroot = ['--user','20000:20000','--read-only','--cap-drop','ALL','--security-opt','no-new-privileges',
21
+ '--mount',`type=bind,src=${join(dir,'purpose.md')},dst=/run/agent-purpose.md,readonly`,'-e','EZ_AGENT_PURPOSE_FILE=/run/agent-purpose.md',
20
22
  '--tmpfs','/tmp:mode=1777','--tmpfs','/state/control:uid=20000,gid=20000,mode=700',
21
23
  '--tmpfs','/state/home:uid=20000,gid=20000,mode=700','--tmpfs','/workspace:uid=20000,gid=20000,mode=700',
22
- '-e','EZ_TELEGRAM_ENABLED=false','-e','EZ_APPLICATION_PORT=8110','-e','EZ_EXECUTOR_TRANSPORT=local','-e','EZ_EXECUTOR_CLI=codex'];
24
+ '-e','EZ_TELEGRAM_ENABLED=false','-e','EZ_APPLICATION_PORT=8110','-e','EZ_ISOLATION=isolated','-e','EZ_EXECUTOR_TRANSPORT=local','-e','EZ_EXECUTOR_CLI=codex'];
23
25
  const help = run(['run','--rm',...nonroot,image,'application','--help']);
24
26
  assert.equal(help.status,0,help.stderr);
25
27
  assert.match(help.stdout,/ezenciel-agents-application/);
26
28
  const login = run(['run','--rm',...nonroot,'--entrypoint','/bin/bash',image,'-lc',
27
- 'command -v ezenciel-agents-application && command -v ezenciel-agents-message && ezenciel-agents-message --help']);
29
+ 'command -v ezenciel-agents-application && command -v ezenciel-agents-message && command -v codex && ezenciel-agents-message --help && codex --version']);
28
30
  assert.equal(login.status,0,login.stderr);
29
31
  assert.match(login.stdout,/\/usr\/local\/bin\/ezenciel-agents-application/);
30
32
  assert.match(login.stdout,/\/usr\/local\/bin\/ezenciel-agents-message/);
33
+ assert.match(login.stdout,/codex-cli 0\.153\.4/);
31
34
  const started = run(['run','-d','--name',application,...nonroot,image,'start']);
32
35
  assert.equal(started.status,0,started.stderr);
33
36
  let ready = false;
@@ -82,5 +85,5 @@ try {
82
85
  assert.equal(run(['kill',holder]).status,0);
83
86
  const recovered=run(['run','--rm','-v',`${volume}:/state/control`,image,'exec','node','-e','process.exit(0)']);
84
87
  assert.equal(recovered.status,0,recovered.stderr);
85
- console.log('Docker smoke passed: direct non-root application help/start/health/stop, inherited private descriptor, non-root executor, private secret isolation, literal argv, exit code, no Docker socket, duplicate writer rejection and crash lock release.');
88
+ console.log('Docker smoke passed: direct non-root application help/start/health/stop, inherited private descriptor, non-root executor, isolated Codex on PATH, private secret isolation, literal argv, exit code, no Docker socket, duplicate writer rejection and crash lock release.');
86
89
  } finally { run(['rm','-f',holder,application]); run(['volume','rm',volume]); rmSync(dir, {recursive:true, force:true}); }
@@ -6,6 +6,10 @@ outbox. The application supplies domain tools and keeps its UI/content API; it
6
6
  can remove its own model runner, continuation loop and engine authentication.
7
7
  This is separate from the older outbound channel-backend integration.
8
8
 
9
+ Run admission, status and inbox receipts include the immutable `preset` captured
10
+ for that run so an application can label delivered messages with the engine choice
11
+ that actually produced them.
12
+
9
13
  ## Shared runtime controls
10
14
 
11
15
  Register with `--share-owner` (the existing `--share-telegram` spelling is an
@@ -267,6 +271,8 @@ separate CLI histories. A retry cannot change an admitted turn's AI choice.
267
271
 
268
272
  Register owner-chat channels with `--share-owner` and submit `followOwner:true`.
269
273
  Web, phone and Telegram then use the same selected native conversation and AI.
274
+ A later same-client provider or model change starts a new native conversation for the next
275
+ admitted turn; already-queued work keeps its original engine.
270
276
  This works without Telegram and requires no transcript replay or new runner.
271
277
  Scoped application conversations remain separate when the flag is omitted.
272
278
  Owner identity does not automatically merge histories or grant learner access.
@@ -8,7 +8,9 @@ At intake each journal entry receives an immutable preset and conversation ID.
8
8
  Batches cannot cross that boundary. The run copies that choice and invokes the
9
9
  native adapter with its exact model/effort. A later menu change cannot reroute it.
10
10
  Old conversation metadata is retained only so accepted work can finish; selecting
11
- a different CLI never restores that CLI's old history.
11
+ a different CLI never restores that CLI's old history. Selecting a different provider
12
+ or model on the same client does not resume the previous selection's native thread; the next
13
+ admitted turn starts a new native conversation.
12
14
 
13
15
  Grok/Claude accept caller-selected native UUIDs. Codex/OpenCode generate IDs, so
14
16
  the adapter reads only their typed JSONL session metadata; stdout never becomes a
@@ -1,9 +1,9 @@
1
1
  # Docker runtime and shared host CLI
2
2
 
3
- Docker Compose owns each relay and executable plugin. The CLI the user installs
4
- Ez from stays on the host, with its existing authentication. All agents in that
5
- installation use that CLI; there is no second CLI installation or agent-specific
6
- CLI login. The relay image contains Node, relay dependencies and ffmpeg.
3
+ Docker Compose owns each relay and executable plugin. Isolated agents run the
4
+ native CLI from the relay image (pinned Codex). Host-capable agents reuse the
5
+ CLI the user installed Ez from, with its existing authentication. The relay
6
+ image contains Node, relay dependencies, ffmpeg and that pinned Codex CLI.
7
7
  The runtime image exposes Ez commands through the wrappers in `/app/bin`, including
8
8
  `ezenciel-agents-application`; application images do not need to link Ez themselves.
9
9
  The image also installs package-manifest commands in `/usr/local/bin`, so native
@@ -13,26 +13,33 @@ belongs to Ez packaging, not an application image or an engine-specific prompt.
13
13
  ## Agent binding
14
14
 
15
15
  Use [agent-led setup](setup.md). `ezenciel-agents-create` accepts a name, purpose,
16
+ optional `--isolation isolated|host-capable` (default `isolated`),
16
17
  a bot token through stdin; the CLI defaults to the one recorded at package
17
18
  installation by `--register-cli <current-cli>`. It creates a private deployment
18
19
  with a unique Compose project, mind, control directory and secret file. Names
19
20
  cannot overwrite existing agents. `installation.json` keeps subsequent agents
20
21
  on the same CLI. Each agent gets its own purpose and native conversation IDs.
22
+ `isolated` sets `EZ_EXECUTOR_TRANSPORT=local` and runs the native CLI in the
23
+ relay. `host-capable` sets `host` and reuses the installer UID; unlabeled
24
+ existing host transports stay host-capable so they are not flipped.
21
25
 
22
26
  `docker.env` contains explicit absolute paths, never token values:
23
27
 
24
28
  ```dotenv
25
29
  COMPOSE_PROJECT_NAME=ez-agent-family
26
30
  EZ_EXECUTOR_CLI=grok
31
+ EZ_ISOLATION=isolated
32
+ EZ_EXECUTOR_TRANSPORT=local
27
33
  EZ_AGENT_WORKSPACE=/absolute/private/agents/family/mind
28
34
  EZ_CONTROL_DIR=/absolute/private/agents/family/control
29
35
  EZ_RELAY_ENV_FILE=/absolute/private/agents/family/relay.env
30
36
  EZ_AGENT_PURPOSE_FILE=/absolute/private/agents/family/purpose.md
31
37
  ```
32
38
 
33
- The installing agent runs `ezenciel-agents-host` under the host's service manager
39
+ Host-capable agents run `ezenciel-agents-host` under the host's service manager
34
40
  with `EZ_DEPLOYMENT_DIR` bound to that deployment. This small transport invokes
35
- the existing CLI; it is not a second relay or model loop. It reads requests from
41
+ the existing CLI; it is not a second relay or model loop. Isolated agents do not
42
+ start it. It reads requests from
36
43
  the agent's control directory, fixes cwd/control/tool paths from its installation
37
44
  binding, strips environment secrets, forwards native output and exit status, and
38
45
  propagates cancellation. Requests cannot select another executable. No network
@@ -41,9 +48,10 @@ is required. Host Node 22+ and the package dependencies run this transport.
41
48
 
42
49
  Mount mind and control at identical absolute paths in Docker and on the host,
43
50
  so incoming files, message attachments and CLI outputs need no path translation.
44
- Only that agent's directories are mounted into its relay. The host CLI reuses the existing login. Codex receives a private per-agent
51
+ Only that agent's directories are mounted into its relay. Host-capable CLI reuses the existing login. Codex receives a private per-agent
45
52
  state home with linked authentication; global memory/configuration is excluded.
46
- This is not an OS security boundary between agents running as the same user.
53
+ Host-capable is not an OS security boundary between agents running as the same user.
54
+ Isolated agents use the relay mounts; sibling host paths are not present.
47
55
  Plugin setup also binds `toolsHome` in the host configuration. Codex receives
48
56
  write access to that registry and network access for the Docker client; the host
49
57
  rejects registries belonging to a different workspace. Restart the host worker
@@ -146,13 +154,22 @@ default. Existing queued jobs retain their captured execution choice.
146
154
 
147
155
  ## Codex context isolation
148
156
 
149
- The host Codex binary and existing login are reused, but each agent has its own
150
- `control/cli/codex` state directory. Only authentication is linked to the host
157
+ Isolated agents run the image Codex with `EZ_CODEX_SANDBOX=external`. Auth is a
158
+ real file at that agent's `control/cli/codex/auth.json`. Do not bind-mount the
159
+ operator `~/.codex` or `$HOME` into an isolated relay. Host-capable agents reuse
160
+ the host Codex binary and login, with a
161
+ `control/cli/codex` state directory. The native CLI is resolved from the host
162
+ PATH, not the package `binDir`, so a shared runtime or one agent's wrapper cannot
163
+ retarget another agent's `CODEX_HOME`. Only authentication is linked to the host
151
164
  login; global configuration, sessions and memories are not imported. Global
152
165
  memory and host skill discovery are disabled for relay jobs. A conversation
153
166
  that already received unrelated global context must be replaced with a fresh
154
167
  native conversation; disabling injection does not remove prior turn content.
155
168
  This prevents automatic context sharing, not adversarial access by the host user.
169
+ On macOS host jobs, Seatbelt also denies sibling directories of the bound
170
+ workspace (a tenant farm), while allowing explicitly shared and additional
171
+ workspace grants and leaving `$HOME` readable for the host CLI.
172
+ Codex then uses `danger-full-access` so it does not apply a nested Seatbelt.
156
173
 
157
174
  ## Chat latency
158
175
 
@@ -1,7 +1,8 @@
1
1
  # Host transport startup
2
2
 
3
- Register one service per deployment after initializing its tools. Substitute the
4
- exact absolute paths below. Use the existing authenticated host user and include
3
+ Register one service per **host-capable** deployment after initializing its tools.
4
+ Isolated agents run the native CLI in the relay and must not start this service.
5
+ Substitute the exact absolute paths below. Use the existing authenticated host user and include
5
6
  its Node 22+, pnpm (or Corepack), Docker and CLI directories in PATH. Resolve the
6
7
  actual installed launchers first; shell aliases and interactive shell startup
7
8
  files are not available to services. No bot token goes in these service files.
@@ -21,6 +22,7 @@ Description=Ez family host CLI transport
21
22
  Type=simple
22
23
  Environment=EZ_DEPLOYMENT_DIR=/absolute/private/agents/family
23
24
  Environment=PATH=/absolute/node/bin:/absolute/package-manager/bin:/absolute/cli/bin:/usr/local/bin:/usr/bin:/bin
25
+ EnvironmentFile=-%h/.config/ez/family-provider.env
24
26
  ExecStart=/absolute/ez-package/package/bin/ezenciel-agents-host
25
27
  Restart=on-failure
26
28
  RestartSec=5
@@ -29,6 +31,13 @@ RestartSec=5
29
31
  WantedBy=default.target
30
32
  ```
31
33
 
34
+ When the installation declares a custom Codex provider, create that private
35
+ environment file with mode `0600` and only the referenced key, for example
36
+ `OPENROUTER_API_KEY=...`. Provider and model selection do not belong in the
37
+ environment; they remain agent runtime state. The supervisor forwards only keys
38
+ explicitly named by the agent's provider binding, and the Codex subprocess still
39
+ receives none of the relay or plugin secrets.
40
+
32
41
  ```sh
33
42
  systemctl --user daemon-reload
34
43
  systemctl --user enable --now ez-family.service
@@ -81,10 +90,12 @@ main/plugin replacement. See [upgrade setup, tools and recovery](upgrades.md). E
81
90
 
82
91
  ## Shared application workspace
83
92
 
84
- Codex tasks inherit the agent's `control/cli/codex/auth.json` binding. Its default
85
- is a link to the host user's existing login. An operator may provision a private
86
- agent credential there during migration; new task homes link to that binding,
87
- preserving it rather than reverting to a different or expired host login.
93
+ Codex tasks inherit the agent's `control/cli/codex/auth.json` binding. Host-capable
94
+ default is a link to the host user's existing login. Isolated agents use a real
95
+ file in that directory and the image Codex; they do not link `~/.codex`. An
96
+ operator may provision a private agent credential there during migration; new
97
+ task homes link to that binding, preserving it rather than reverting to a
98
+ different or expired host login.
88
99
 
89
100
  An agent binding may set `sharedWorkspace` to an absolute canonical application
90
101
  repository. The host resolves its canonical path, adds that directory to Codex
package/docs/plugins.md CHANGED
@@ -291,6 +291,21 @@ already exist on the host. Keep indexes and writable metadata in the plugin's
291
291
  normal volume. This uses Docker bind mounts; it copies no source bytes and
292
292
  starts no provider sync. Read-only is the default.
293
293
 
294
+ For a relay-bound agent, the source must be inside its workspace/shared workspace
295
+ or under a plugin-specific root granted in the agent's external host configuration:
296
+
297
+ ```json
298
+ {
299
+ "pluginFolderRoots": {
300
+ "library": ["/absolute/existing/notes"]
301
+ }
302
+ }
303
+ ```
304
+
305
+ The agent cannot add this grant through its registry. A missing grant fails before
306
+ Compose or registry state changes. Standalone registries remain operator-owned and
307
+ do not use a relay host allowlist.
308
+
294
309
  Stop the plugin before changing a binding:
295
310
 
296
311
  ```sh
package/docs/repair.md CHANGED
@@ -30,7 +30,7 @@ True makes the capability available; it does not itself grant a repair mandate.
30
30
  The setting does not change filesystem/GitHub permissions and does
31
31
  not cancel an already running task. Explicitly stop active repair work when needed.
32
32
  An owner can also disable repairs globally or for a repository in the agent's
33
- saved USER.md preferences; carry those restrictions into background task context.
33
+ saved AGENTS.md preferences; carry those restrictions into background task context.
34
34
 
35
35
  ## Setup boundary
36
36
 
package/docs/setup.md CHANGED
@@ -67,8 +67,23 @@ not imported into restricted sessions. No workflow prompt is added to input.
67
67
  Use the existing owner's host account. Unless a layout was supplied, use
68
68
  `${XDG_DATA_HOME:-$HOME/.local/share}/ez/packages/<version>/` for extracted main
69
69
  packages and `${XDG_DATA_HOME:-$HOME/.local/share}/ez/agents/` for private deployments.
70
- Never overwrite an existing package/deployment or extract over its state. Infer
71
- the agent name and purpose from the owner's request; ask only when missing.
70
+ Never overwrite an existing package/deployment or extract over its state.
71
+
72
+ Before creation, use your own reasoning and the context already available from
73
+ the owner to write the agent's initial brief. This is not a label or a copy of
74
+ the request. Describe, in concrete language, who the agent serves, what product
75
+ or environment it works in, the outcomes it should help produce, the capabilities
76
+ currently available to it, and important scope boundaries. Include material facts
77
+ you already know about the application; do not invent unknown facts. Omit generic
78
+ personality prose and sections irrelevant to this agent. Keep the result compact
79
+ enough to read on every turn. Ask the owner only when a missing fact would
80
+ materially change the agent being installed.
81
+
82
+ Pass that generated brief as the purpose. For example, a request to create a
83
+ personal assistant should produce a brief tailored to that person's stated work,
84
+ preferences and connected capabilities—not merely `Personal assistant`. A product
85
+ agent should identify the product, user, current product scope and available
86
+ actions without embedding product-specific behavior in Ez Core.
72
87
 
73
88
  Before running Node commands, check `command -v node` and `node --version`.
74
89
  If Node 22+ or this package's pinned pnpm is missing, provision it using the
@@ -163,20 +178,72 @@ so later service starts use the same build without depending on shell environmen
163
178
 
164
179
  ```sh
165
180
  export EZ_AGENTS_HOME=/absolute/private/agents
166
- bin/ezenciel-agents-create --name family-shopper --purpose 'Help my family plan shopping.' < /private/bot-token
181
+ bin/ezenciel-agents-create --name family-shopper --purpose 'You are the shopping assistant for the owner and their family. Help plan shared shopping, organize confirmed requests, and use only the installed shopping and messaging capabilities. Do not purchase, message others, or infer household preferences without owner authority.' < /private/bot-token
167
182
  export EZ_DEPLOYMENT_DIR="$EZ_AGENTS_HOME/family-shopper"
168
183
  ```
169
184
 
185
+ Creation records an isolation class. Packaged Codex defaults to `isolated`: the
186
+ native CLI runs inside the relay, which only has that agent's workspace and control mounts.
187
+ Sibling host paths are absent (`ENOENT`), not merely unreadable. Pass
188
+ `--isolation host-capable` only when the owner needs the installer UID (host
189
+ browser, local git, host files). Other installed CLIs default to `host-capable`
190
+ because they are not packaged in the relay; requesting isolated execution for
191
+ them fails closed. Host-capable reuses the host CLI login and is not an OS tenant
192
+ boundary. The class cannot be changed later by flipping Compose; recreate the agent.
193
+
194
+ Isolated Codex uses the pinned CLI in the relay image and
195
+ `EZ_CODEX_SANDBOX=external` so Codex does not nest a sandbox inside the
196
+ container. Provision that agent's `control/cli/codex/auth.json`; do not bind-mount
197
+ the operator `~/.codex`. The host LaunchAgent is not started. Host-capable agents
198
+ still need [host startup](host-service.md).
199
+
170
200
  Agent creation defaults to the CLI recorded when the initial package was
171
201
  installed. It needs no `--cli` argument. A later shell or different available CLI
172
202
  does not change that saved default. No installed-binary ranking or Grok fallback
173
203
  is used.
204
+
205
+ Custom Codex providers such as OpenRouter currently require an explicitly
206
+ host-capable agent: their keys are supplied by the private host-service
207
+ environment. The isolated relay has no custom-provider key injection path.
208
+ Register the provider and initial model set as non-secret installation metadata;
209
+ repeat `--codex-model` for every model the agent may select:
210
+
211
+ ```sh
212
+ bin/ezenciel-agents-create \
213
+ --name family-shopper \
214
+ --purpose 'Help my family plan shopping.' \
215
+ --isolation host-capable \
216
+ --codex-provider openrouter \
217
+ --codex-provider-name OpenRouter \
218
+ --codex-base-url https://openrouter.ai/api/v1 \
219
+ --codex-env-key OPENROUTER_API_KEY \
220
+ --codex-model google/gemini-3.8-flash \
221
+ --codex-model deepseek/deepseek-v4.1-flash \
222
+ < /private/bot-token
223
+ ```
224
+
225
+ This stores provider configuration and model IDs in `host-executor.json`, never
226
+ the API key. The key belongs only in the private host-service environment. The
227
+ agent or owner can later change the active provider/model through the existing
228
+ runtime control without reinstalling or editing an environment file:
229
+
230
+ ```sh
231
+ ezenciel-agents-ai list
232
+ ezenciel-agents-ai select --cli codex --provider openrouter \
233
+ --model google/gemini-3.8-flash
234
+ ```
235
+
236
+ Changing provider starts a fresh native session so a session is never resumed
237
+ against a different backend. Queued work retains the provider/model captured
238
+ when it was admitted.
174
239
  The creator writes `host-executor.json`, private secrets, mind/control paths,
175
240
  purpose and unique plugin volume names. Duplicate names fail without overwriting.
176
241
  `ezenciel-agents-create --list` lists deployments without secrets.
177
242
 
178
- Before starting that host transport, initialize the main package’s built-in plugin manager. It runs on the host
243
+ Before starting a host-capable transport, initialize the main package’s built-in plugin manager. It runs on the host
179
244
  for Docker access and adds no separate package or provider library. The packaged catalog is empty so the relay installs independently.
245
+ Isolated agents still initialize this registry for plugin discovery; they do not
246
+ start `ezenciel-agents-host`.
180
247
  Use its empty default catalog. Plugin preparation follows the verified Telegram
181
248
  handoff, not this step. Run:
182
249
 
@@ -202,16 +269,21 @@ the plugin skill through onboarding and verified use under that same request.
202
269
  Registry initialization itself does not connect accounts or start plugins.
203
270
  Never silently reuse a global `ez` from the old sandbox runtime.
204
271
 
205
- 4. Follow [host startup templates](host-service.md). Register `bin/ezenciel-agents-host` with the host's native service manager,
206
- binding `EZ_DEPLOYMENT_DIR` and the user's existing CLI PATH/HOME. On macOS use
207
- a LaunchAgent; on Linux use a user service. Use the absolute Node/package
272
+ 4. Isolated agents skip host transport. Run `bin/ezenciel-agents-docker up -d --wait`
273
+ from the deployment directory. Do not register `ezenciel-agents-host` for them;
274
+ starting it fails closed.
275
+
276
+ Host-capable agents follow [host startup templates](host-service.md). Register
277
+ `bin/ezenciel-agents-host` with the host's native service manager, binding
278
+ `EZ_DEPLOYMENT_DIR` and the user's existing CLI PATH/HOME. On macOS use a
279
+ LaunchAgent; on Linux use a user service. Use the absolute Node/package
208
280
  paths. This generic transport is the only host execution bridge. Do not start
209
281
  another host relay or install another CLI. Verify its heartbeat, then run
210
282
  `bin/ezenciel-agents-docker up -d --wait`.
211
283
 
212
284
  `ezenciel-agents-setup service` is Docker-only startup: run it from the
213
285
  deployment directory containing `docker.env`. It starts the relay and does
214
- not install or start the host-executor user service. Complete the host
286
+ not install or start the host-executor user service. Complete host-capable
215
287
  registration above separately.
216
288
 
217
289
  5. Ask the owner to message the exact bot. Verify the pending numeric identity,
@@ -224,8 +296,8 @@ Never silently reuse a global `ez` from the old sandbox runtime.
224
296
  stopped to verify repairs, then restore the poller; never replay uncertain
225
297
  user actions merely to test delivery.
226
298
 
227
- The main runtime is fully installer-owned: build it, register its host
228
- transport, bring its Compose project up, and verify restart persistence. Plugins
299
+ The main runtime is fully installer-owned: build it, register host transport
300
+ only for host-capable agents, bring its Compose project up, and verify restart persistence. Plugins
229
301
  are installed by the working Telegram agent when the owner requests their
230
302
  capability there; account onboarding and provider confirmation follow that request.
231
303
  Never use a missing plugin as a reason
@@ -238,9 +310,11 @@ current heartbeats and owner-bound completed-run delivery receipts without sendi
238
310
  messages or exposing tokens. Receipt evidence is historical: separately verify
239
311
  the current conversation and restart persistence before declaring setup complete.
240
312
 
241
- Purpose seeds SOUL.md once; future customization survives restart. Conversations,
242
- pairing, files and plugin accounts are separate. The existing host CLI login is
243
- shared, so this is not a security sandbox against other agents under that user.
313
+ Purpose scopes AGENTS.md once; future customization survives restart. Conversations,
314
+ pairing, files and plugin accounts are separate. Isolated agents use the relay
315
+ mount namespace as the filesystem boundary. Host-capable agents share the
316
+ installer UID and CLI login; that class is not a security sandbox against other
317
+ agents under that user.
244
318
  Plugins run in their own containers and own their authentication. Install and
245
319
  register them only for the requested agent. Never clone another agent's plugin
246
320
  credentials. See [runtime, migration and QA](docker-runtime.md).
@@ -256,7 +330,7 @@ default. Existing queued jobs retain their captured execution choice.
256
330
  | Symptom | Inspect and recover |
257
331
  |---|---|
258
332
  | Docker unavailable | Check engine and Compose, start the supported installation; complete any required OS consent. |
259
- | Bot stays silent | Check pending owner identity, relay health, host heartbeat and outbox; approve only the verified owner. |
333
+ | Bot stays silent | Check pending owner identity, relay health, host heartbeat (host-capable only) and outbox; approve only the verified owner. |
260
334
  | CLI exits without reply | Inspect the actual tool result and delivery receipt; stdout is not Telegram output. |
261
335
  | Exit 73 | An existing writer holds this deployment; stop the exact duplicate, never delete the kernel lock. |
262
336
  | Plugin absent | Check the bound catalog and install/start the reviewed package explicitly; no plugin ships by default. |
@@ -32,8 +32,9 @@ Use `ez tools list --details` for generated installed-plugin descriptions, help
32
32
  Add that instruction to its existing project instructions without replacing them.
33
33
  Use the absolute launcher, or prepend its bin directory to that session's PATH.
34
34
  Never overwrite another global `ez`; it may belong to a different installation.
35
- A company registry remains explicit even when invoked from a different directory.
36
- Other projects/accounts should use separate registries and private plugin state.
35
+ A standalone company registry remains explicit even when invoked from a different
36
+ directory. A relay-bound registry rejects calls from outside its workspace. Other
37
+ projects/accounts must use separate registries and private plugin state.
37
38
 
38
39
  ## Install and verify a capability
39
40
 
@@ -54,9 +55,9 @@ Native client plugins/connectors are not converted into Ez plugins automatically
54
55
 
55
56
  A relay can coexist with CLI-only registries on the same machine. It retains its
56
57
  own pairing, single-writer queue, mind and plugin registry. Calling its existing
57
- bound launcher explicitly reuses that registry and its accounts; never initialize
58
- over it or silently select it from another workspace. Coordinate writes with its
59
- active jobs. Separate registries do not automatically share credentials or data.
58
+ bound launcher must be run from the owning agent workspace. Never initialize over
59
+ it or select it from another workspace. Coordinate writes with its active jobs.
60
+ Separate registries do not automatically share credentials or data.
60
61
 
61
62
  Continuous monitoring requires a configured event consumer/relay and the plugin's
62
63
  supported watcher. CLI-only installation does not create background agent turns.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jc_stack/ez-agents",
3
- "version": "0.1.0-beta.33",
3
+ "version": "0.1.0-beta.34",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "A lightweight foundation for persistent business AI assistants using existing AI harnesses, workspaces and plugins.",
@@ -15,7 +15,8 @@ const duration=Number(process.argv[2] || 1860),cli=process.argv[3] || 'codex'
15
15
  const nativeGoal=process.argv[4]==='goal'
16
16
  if(!Number.isSafeInteger(duration) || duration<30)throw new Error('Duration must be at least 30 seconds')
17
17
  const root=await mkdtemp(join(tmpdir(),'ez-scheduler-smoke-')),workspace=join(root,'agent'),controlDir=join(root,'control')
18
- await initializeWorkspace(workspace)
18
+ const purpose=join(root,'purpose.md');await writeFile(purpose,'Verify native scheduled work and responsive owner chat.\n')
19
+ await initializeWorkspace(workspace,purpose)
19
20
  const control=new ControlStore(controlDir,1000),scheduler=new Scheduler(controlDir),runs=new RunStore(controlDir)
20
21
  await control.requestPairing(101,101);await control.approveOwner(101)
21
22
  const owner=(await control.status()).owner!,execution=await control.captureChoice(initialPreset(cli))