@jc_stack/ez-agents 0.1.0-beta.49 → 0.1.0-beta.51

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 (58) hide show
  1. package/AGENTS.md +8 -6
  2. package/CHANGELOG.md +26 -0
  3. package/README.md +9 -5
  4. package/bin/ez +16 -7
  5. package/docs/architecture/ai-selection.md +4 -0
  6. package/docs/docker-runtime.md +28 -2
  7. package/docs/plugin-guide.md +3 -3
  8. package/docs/plugins.md +13 -10
  9. package/docs/scheduling.md +62 -22
  10. package/docs/setup.md +2 -2
  11. package/docs/standalone-cli.md +4 -4
  12. package/docs/upgrades.md +16 -15
  13. package/package.json +4 -2
  14. package/src/agent-guidance.ts +2 -2
  15. package/src/ai-cli.ts +4 -2
  16. package/src/ai.ts +17 -5
  17. package/src/application-channel.ts +12 -8
  18. package/src/control-state.ts +10 -4
  19. package/src/delivery-socket-client.mjs +15 -1
  20. package/src/delivery-socket.ts +18 -4
  21. package/src/executor.ts +63 -10
  22. package/src/host-executor-client.ts +23 -8
  23. package/src/host-executor.ts +63 -32
  24. package/src/index.ts +157 -35
  25. package/src/menu.ts +3 -2
  26. package/src/plugins/manager.mjs +43 -40
  27. package/src/plugins/workspace-lease.mjs +1 -1
  28. package/src/runs.ts +67 -3
  29. package/src/schedule-cli.ts +24 -15
  30. package/src/scheduled-tasks.ts +53 -17
  31. package/src/scheduler.ts +7 -8
  32. package/src/session-close.ts +17 -0
  33. package/src/setup-fallback.ts +62 -25
  34. package/src/tools-line.d.mts +1 -0
  35. package/src/tools-line.mjs +3 -0
  36. package/src/updates/binding.mjs +4 -10
  37. package/src/updates/control.mjs +3 -2
  38. package/src/updates/runtime.mjs +25 -9
  39. package/src/updates/supervisor.mjs +105 -16
  40. package/src/workspace.ts +12 -1
  41. package/test/ai-cli.test.ts +23 -1
  42. package/test/ai.test.ts +72 -4
  43. package/test/application-channel.test.ts +24 -0
  44. package/test/application-controls.test.ts +4 -1
  45. package/test/delivery-transport.test.ts +41 -2
  46. package/test/executor.test.ts +32 -4
  47. package/test/host-executor.test.ts +70 -3
  48. package/test/intake-relay.test.ts +71 -3
  49. package/test/model-policy.test.ts +1 -1
  50. package/test/plugin-broker.test.mjs +48 -1
  51. package/test/plugin-manager.test.mjs +52 -38
  52. package/test/run-handoff.test.ts +136 -0
  53. package/test/schedule-cli.test.ts +24 -1
  54. package/test/scheduled-tasks.test.ts +45 -15
  55. package/test/session-close.test.ts +17 -0
  56. package/test/setup-fallback.test.ts +194 -16
  57. package/test/updates.test.mjs +55 -12
  58. package/test/workspace.test.ts +6 -0
package/AGENTS.md CHANGED
@@ -16,11 +16,13 @@ through one generic transport, with separate agent workspaces and sessions.
16
16
 
17
17
  ## Operating-agent instructions
18
18
 
19
- Do not inject a coding handbook into production agent minds. The package-owned
20
- workspace locator is the managed `<!-- ez tools -->` block: the bound `ez`
21
- launcher, `ez --help`, and `ez tools list --details`. Inventory is generated on
22
- read; do not hardcode command recipes. Purpose lives in that agent's own
23
- `AGENTS.md`. Coding defaults for this repository stay in this file and
19
+ Do not inject a coding handbook into production agent minds. The only
20
+ package-written `AGENTS.md` text is the initial workspace seed: purpose, a short
21
+ engine-neutral `memory/` note and one static line pointing at
22
+ `ez tools list --details`. After seeding the agent owns `AGENTS.md`; inventory is
23
+ generated on read, so do not hardcode command recipes. The one transport-level
24
+ lifecycle prompt is the silent conversation-close turn (`src/session-close.ts`),
25
+ which runs only for owner conversations. Coding defaults for this repository stay in this file and
24
26
  CONTRIBUTING.md.
25
27
 
26
28
  ## Installing this package
@@ -105,7 +107,7 @@ If no answer identifies a material blocker, finish. Do not start another review
105
107
  round merely to seek more criticism, tests or hypothetical edge cases.
106
108
 
107
109
  ## 3. Crash-Safe Disk State
108
- - Persistent authority state (`ControlStore` owner/pairing/sessions/presets, application bindings, schedule definitions) stays in disk-backed JSON files. Run/outbox/inbox bookkeeping is relay memory reached through the delivery socket (`src/delivery-socket.ts`): a restart drops in-flight runs and queued deliveries by design, uncertain sends report unknown rather than success, and Telegram redelivery is the replay mechanism. The live delivery socket also replaces the relay flock as the single-relay guard.
110
+ - Persistent authority state (`ControlStore` owner/pairing/sessions/presets, application bindings, schedule definitions) stays in disk-backed JSON files. Run/outbox/inbox bookkeeping is relay memory reached through the delivery socket (`src/delivery-socket.ts`): a restart drops in-flight runs and queued deliveries by design, uncertain sends report unknown rather than success, and Telegram redelivery is the replay mechanism. The one exception: a graceful stop hands running host-transport runs, whose executors outlive the relay, to the next relay through private `control/run-handoff.json`; that relay adopts and watches them and never relaunches one. The live delivery socket also replaces the relay flock as the single-relay guard.
109
111
  - **Atomic write pattern:** Never write directly to a state file. Always write to a temporary file (`${target}.${process.pid}.tmp`) with mode `0o600`, then atomically `rename` it over the destination.
110
112
  - **Path sanitization:** Every ID parameter (`runId`, `outboxId`, `pairingId`) must be validated against `/^[a-zA-Z0-9_-]+$/` before being joined into paths. Never allow directory traversal (`..`).
111
113
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.0-beta.51
4
+
5
+ - Keep native runs alive during handoff-compatible core upgrades. The retiring executor finishes existing work while its replacement accepts new requests; the relay adopts runs without replay and preserves schedule holds, cancellation and settled delivery receipts. Isolated agents migrate once while idle to a separate, unprivileged executor container; later compatible upgrades preserve its running generation. Older runtimes without the handoff contract retain the full native-work drain.
6
+ - Prepare compatible-upgrade workspace and native-client backups before pausing admission or stopping the relay, so large histories do not extend the swap outage. Rollback restores code and configuration without rewinding active native state.
7
+ - Make stop requests reach handed-off native work even while its observer is starting. A failed cancellation write leaves the observer active and reports the failed stop request.
8
+ - Show paused schedules in `/tasks` and task details with a paused label and no next run. Editing a paused schedule keeps it paused.
9
+ - Replace the oldest unused saved AI choice when the saved-choice limit is reached, preserving the selected, default and recently used choices.
10
+ - Use the isolated native client's own catalog for AI selection, so available models such as Sonnet appear without relying on a host catalog.
11
+
12
+ Beta limits: live fresh-host/reboot acceptance and live compatible upgrades during active native work on the VM and Mac remain pending. Synthetic container lifecycle checks and an idle EzStocks OVH private-candidate upgrade passed; these do not establish live active-run or provider acceptance. Publication does not establish an installed-agent upgrade or authorize provider/domain writes.
13
+
14
+ ## 0.1.0-beta.50
15
+
16
+ - Share memory across engines through the workspace. New workspaces seed a short `## Memory` note (durable facts in `memory/`, one per file, indexed in `memory/MEMORY.md`); every engine loads `AGENTS.md`, so Codex and Claude read the same files. When a chat conversation is replaced (`/new`, the New button, web app new, or an AI/provider switch), its engine gets one silent turn in that native session to save durable facts; nothing is delivered and the engine decides what to write.
17
+ - Drop the managed `<!-- ez tools -->` AGENTS.md footer. The install seed writes one static line ("Tools: run `ez tools list --details`; each plugin's `--help` gives usage."); update binding replaces an existing footer with that line once and otherwise never writes instructions. `ez tools list --details` now prints per plugin `aliases`, `purpose`, `skills`, `skillReads` and one `example` call (optional manifest `example`, else `<alias> --help`).
18
+ - Stop reporting a live plugin call as a stale lease on isolated agents. Leases record their PID namespace; a lease written in the broker container is judged by its call container's running state, so `ez updates apply` no longer refuses during in-flight calls. Apply and recover only queue the job; main activation now drains in-flight calls before switching instead of failing.
19
+ - Keep host-capable native runs alive across a graceful relay restart. The stopping relay hands running host-transport runs to the next relay instead of cancelling them; it adopts and watches each run with its schedule hold, concurrency slot and message delivery, and records its exit status. Nothing is relaunched; a run that cannot be adopted or whose outcome is unknown is recorded failed and interrupted, holding its schedule, and a run that fails adoption is asked to stop. Core upgrades still wait for idle native work; isolated agents, crashes and queued work keep the previous behavior.
20
+ - Continue a scheduled occurrence on its next saved login when Claude stops at a usage/session limit after work began. The next setup starts a fresh native session with the saved prompt plus one factual line naming the run, stopped setup, its native session and reset time. Only a typed quota rejection the run ended on continues; work after a limit, access denial, other failures, cancellation and unknown outcomes still stop without replay. Attempts record `quota-after-work-began` and each Ez-assigned session ID.
21
+ - Stop isolated plugin calls hanging silently before reaching the broker when the caller leaves a socket stdin open (native engine shells, async Node spawns). Pipes and files are still read to EOF; a socket that sends nothing for one second is treated as no input. Remove a uniquely named call container whenever Compose exits non-zero, so a call that stops between create and start no longer strands it in `Created`.
22
+ - Advance scheduled work to its next saved login after a typed authentication rejection before work. Recognize Claude API-error metadata without marking analysis started; account/organization denials, hooks, model/tool work and uncertain outcomes still stop fallback. Preserve the saved chain for the next occurrence.
23
+ - Let scheduled fallback setups name an owner-provisioned native login profile per setup, with no fixed list length. A quota rejection skips later setups on the same login profile while other accounts stay eligible; model/tool/hook work, access denial, cancellation and uncertain failures still stop the chain.
24
+ - Show each scheduled task's frequency and full primary → fallback path (CLI, login profile, provider, model, effort) in Telegram `/tasks` and `/tasks N`.
25
+ - Report the exact AI selection error (missing client models, missing model, unsupported effort) instead of a generic invalid selection, without changing the saved choice.
26
+
27
+ Beta limits: live fresh-host/reboot acceptance, a live host-capable relay recreate during a running native run, two-real-account channel switching and real exhausted-account/provider rejection remain pending. An adopted scheduled occurrence does not advance to fallback setups, and messages a run sends while no relay is up still fail. The conversation close turn is engine-decided and runs only for owner conversations; existing workspaces do not receive the `## Memory` seed note automatically. Plugin manifests may declare `example`; a plugin that does requires core 0.1.0-beta.50 or newer, and older cores refuse that plugin update (manifest validation) and keep the installed version until core upgrades. Publication does not establish an installed-agent upgrade or authorize provider/domain writes.
28
+
3
29
  ## 0.1.0-beta.49
4
30
 
5
31
  - Upgrade compatible plugins while native work continues, draining in-flight plugin calls for activation and preserving rollback. Core updates still wait for idle.
package/README.md CHANGED
@@ -172,7 +172,8 @@ transport, with separate agent workspaces and sessions. See
172
172
 
173
173
  Telegram `/status` shows the running relay and host versions plus installed
174
174
  plugin versions. Its read-only **Scheduled tasks** control lists the paired
175
- owner's active schedules in alphabetical order, with their engine/model/effort,
175
+ owner's active schedules in alphabetical order, with their frequency/timezone
176
+ and ordered CLI/login-profile/model/effort execution path (primary → fallbacks),
176
177
  next occurrence or current queued/running state, and a short preview of the
177
178
  saved invocation prompt; it never creates, changes or runs a task. The agent's `ez status` adds verified plugin
178
179
  runtime states and upgrade job receipts. See [status and upgrades](docs/upgrades.md).
@@ -260,11 +261,14 @@ restart and shutdown. Host-capable agents register the small host CLI transport
260
261
 
261
262
  The selected host CLI runs in this agent's persistent workspace and owns its
262
263
  Markdown/work files. New conversations and AI changes preserve those files.
263
- One compact `AGENTS.md` is seeded with the installer purpose; `MEMORY.md` is
264
- optional. The installing LLM authors the initial brief from the owner's request
264
+ One compact `AGENTS.md` is seeded with the installer purpose, a short note to
265
+ keep durable facts in `memory/` (one fact per file, indexed in `memory/MEMORY.md`)
266
+ and one line pointing at `ez tools list --details`. Every engine loads `AGENTS.md`,
267
+ so they share that memory. When a chat conversation is replaced (new conversation
268
+ or AI/provider change), its engine gets one silent turn to save durable facts. The installing LLM authors the initial brief from the owner's request
265
269
  and known application context; Ez stores it without trying to infer the product
266
- itself. Installed plugin snippets and skill paths come from the bound
267
- `ez tools list --details` locator; no tool inventory file needs maintenance.
270
+ itself. Installed plugin aliases, purposes, skills and examples come from
271
+ `ez tools list --details`; no tool inventory file needs maintenance.
268
272
  Credentials and control state stay outside the mind. Isolated agents use relay
269
273
  mounts as the filesystem boundary. Host-capable file separation is not OS
270
274
  isolation against a process running as the same user.
package/bin/ez CHANGED
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { createConnection } from 'node:net';
3
3
  import { randomUUID } from 'node:crypto';
4
+ import { fstatSync } from 'node:fs';
4
5
  import { fileURLToPath } from 'node:url';
5
6
  import { installedNativeHelp } from '../src/plugins/native-tasks.mjs';
6
7
 
7
8
  const MAX_STDIN_BYTES = 1024 * 1024;
9
+ const SOCKET_STDIN_IDLE_MS = 1000;
8
10
  const RUN_ID = /^[A-Za-z0-9_-]{1,160}$/;
9
11
  const socketPath = process.env.EZ_PLUGIN_BROKER_SOCKET?.trim();
10
12
  const runId = process.env.EZ_RUN_ID?.trim();
@@ -50,18 +52,25 @@ const request = (payload, signal = undefined) => new Promise((resolve, reject) =
50
52
  socket.on('connect', () => socket.write(JSON.stringify(payload) + '\n'));
51
53
  });
52
54
 
53
- const readStdin = async () => {
54
- if (process.stdin.isTTY) return '';
55
+ // Pipes and files always end, so read them to EOF. A socket is how Node-based
56
+ // parents (native engine shells, agent scripts) wire child stdio, and they
57
+ // often pass it on without ever closing it. Waiting for that EOF hung calls
58
+ // before the broker was contacted, so a socket that stays silent means no input.
59
+ const readStdin = () => new Promise((resolve, reject) => {
60
+ if (process.stdin.isTTY) return resolve('');
55
61
  const chunks = [];
56
62
  let bytes = 0;
57
- for await (const chunk of process.stdin) {
63
+ const idle = fstatSync(0).isSocket() ? setTimeout(() => { process.stdin.destroy(); resolve(''); }, SOCKET_STDIN_IDLE_MS) : undefined;
64
+ process.stdin.on('data', chunk => {
65
+ clearTimeout(idle);
58
66
  const value = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
59
67
  bytes += value.length;
60
- if (bytes > MAX_STDIN_BYTES) throw Error('plugin stdin exceeds the broker limit');
68
+ if (bytes > MAX_STDIN_BYTES) { process.stdin.destroy(); return reject(Error('plugin stdin exceeds the broker limit')); }
61
69
  chunks.push(value);
62
- }
63
- return Buffer.concat(chunks).toString('utf8');
64
- };
70
+ });
71
+ process.stdin.once('end', () => { clearTimeout(idle); resolve(Buffer.concat(chunks).toString('utf8')); });
72
+ process.stdin.once('error', error => { clearTimeout(idle); reject(error); });
73
+ });
65
74
 
66
75
  const invoke = async (args, stdin) => {
67
76
  if (!runId || !RUN_ID.test(runId)) throw Error('EZ_RUN_ID is required for an isolated plugin command');
@@ -3,6 +3,10 @@
3
3
  `menu.ts` owns Telegram controls; `ai.ts` projects installed-client metadata.
4
4
  `ControlStore` stores saved presets, the next-conversation default and current choice.
5
5
  No provider SDK, model fallback, history migration, or second execution loop.
6
+ A missing catalog entry rejects selection without changing the conversation.
7
+ An unsupported effort reports the exact installed model's advertised levels.
8
+ Missing selectable models do not establish that the active native session is
9
+ unavailable, and editing a scheduled task does not change the chat selection.
6
10
 
7
11
  At intake each journal entry receives an immutable preset and conversation ID.
8
12
  Batches cannot cross that boundary. The run copies that choice and invokes the
@@ -141,6 +141,17 @@ An interrupted Telegram poller keeps authorized work and in-flight deliveries
141
141
  alive while it retries intake. Run and delivery records are relay memory: a
142
142
  relay process restart drops in-flight work by design, Telegram redelivery is the
143
143
  intake replay path, and uncertain sends report unknown instead of success.
144
+ Host-capable agents are the exception for running work: their native CLIs run under
145
+ the host transport, so a graceful relay stop (Compose recreate, Docker restart)
146
+ writes those running runs to private `control/run-handoff.json` instead of
147
+ cancelling them. The next relay adopts each one, keeps its concurrency slot and
148
+ schedule hold, accepts its messages and records its exit status; it never
149
+ relaunches a run. A run whose outcome is unknown (no request, claim or result
150
+ record, or an adoption failure) is recorded failed and interrupted, which holds
151
+ its schedule until an explicit edit; the host is asked to cancel it. A request
152
+ the host had not claimed before the relay stopped is failed by the host without
153
+ starting. Records older than 24 hours are ignored, and the file is removed on
154
+ every start. Queued runs are still dropped. Accepted deliveries are drained before listener shutdown completes; settled receipts for handed-off runs move with the handoff. A connection failure during a declared upgrade swap waits for the relay; an accepted timeout is never retried.
144
155
  A `409 Conflict` still requires the operator to stop the competing poller; it
145
156
  remains unhealthy after the startup grace. Pending and uncertain deliveries keep
146
157
  their existing receipt semantics; executor stdout is not replayed as a reply.
@@ -196,8 +207,12 @@ The image pins modern Docker Compose, including its standalone executable, so
196
207
  existing operator-created volumes retain their data and driver bindings.
197
208
 
198
209
  The relay-side `ez` client sends only declared aliases, literal argument arrays,
199
- bounded stdin, and the active run ID to the broker. The broker re-reads owner
200
- authority and the registry before every call, pins the discovered revision, and
210
+ bounded stdin, and the active run ID to the broker. It reads a piped or
211
+ redirected stdin to EOF. A socket stdin, which native engine shells and Node
212
+ child processes often leave open, counts as no input unless data arrives within
213
+ one second; write and close it immediately when a command needs input. The
214
+ broker re-reads owner authority and the registry before every call, pins the
215
+ discovered revision, and
201
216
  passes only the installed plugin's `run.application.context.plugins[plugin-id]`
202
217
  object as `EZ_PLUGIN_CONTEXT`. Missing, revoked, cross-agent, expired, or reused
203
218
  admissions fail closed. Timeouts, cancellation and bounded output produce a
@@ -376,3 +391,14 @@ The isolated executor image includes Node, jq and Python 3 (`python` and
376
391
  remain behind installed plugins. Registered command admission waits up to five
377
392
  seconds for the short registry lock; lifecycle changes still fail closed, and
378
393
  no caller removes an existing lock. Serialize calls when a plugin requires it.
394
+
395
+ ## Isolated executor generations
396
+
397
+ A handoff-capable supervisor runs the existing native file transport in a separate
398
+ unprivileged per-agent container. It derives workspace/control mounts and the image
399
+ from the owning relay deployment, adds only a read-only executor binding, and gives
400
+ the worker no relay secrets, provider secret environment or Docker socket. Native
401
+ credential homes stay in the existing control volume. Only the active generation
402
+ claims work; retired generations keep cancellation and per-run liveness until
403
+ their last process exits. The supervisor removes their temporary Compose resources.
404
+ The first upgrade from an embedded isolated executor drains before this migration.
@@ -57,14 +57,14 @@ At minimum, provide:
57
57
  machine-readable commands, stable exit codes, and readback for writes.
58
58
 
59
59
  The manager snapshots the package according to `package.json.files` plus the
60
- required manifests and Docker inputs. `ez tools list --details` includes
60
+ required manifests and Docker inputs. `ez tools list --details` prints aliases, purpose, skill paths, one example call
61
+ (the optional manifest `example`, else `<alias> --help`) and
61
62
  `skillReads`: literal command argument arrays for reading each installed skill.
62
63
  Use `ez tools skill <plugin> <declared-path>` to return its text and pinned
63
64
  revision. The isolated executor cannot open private host package paths directly;
64
65
  the authorized broker reads only manifest-declared regular skill files, without
65
66
  copying them into the agent workspace. Skill reads reject symlinks and files over
66
- 64 KiB. The existing generic discovery footer remains accurate; no plugin-specific
67
- usage snippet or per-turn instruction is needed for this read contract.
67
+ 64 KiB. No AGENTS.md text or per-turn instruction is needed for this read contract.
68
68
 
69
69
  Keep one canonical checkout; do not keep
70
70
  permanent packaged copies or a second registry. See the full
package/docs/plugins.md CHANGED
@@ -27,8 +27,7 @@ node /absolute/ezenciel_agents/bin/ezenciel-agents-tools.mjs init \
27
27
  Without `--catalog`, init loads an empty packaged catalog. Keep it empty for
28
28
  initial main onboarding; no sibling repository, broker or provider account is
29
29
  needed. Finish owner pairing and verify an actual Telegram agent reply first.
30
- Init creates a private registry and `tools/bin/ez`, adds a
31
- managed AGENTS.md registry locator, and binds the matching host executor to that bin
30
+ Init creates a private registry and `tools/bin/ez` and binds the matching host executor to that bin
32
31
  folder. Native binaries are linked through; the package's own `bin/ez` is replaced
33
32
  by that bound launcher, while an unrelated existing `ez` collision fails.
34
33
  Both host-bound and isolated `ez --help` list installed native scheduling and delivery helpers
@@ -307,8 +306,10 @@ never a source of network attachment authority.
307
306
 
308
307
  Commands run in one-shot client containers against their own service's volumes;
309
308
  stdin, stdout, stderr, literal arguments and exit codes are preserved. SIGINT/
310
- SIGTERM cancel the Docker call and remove its unique client container. The manager
311
- never retries a provider operation. Service startup/restart is explicit; commands
309
+ SIGTERM cancel the Docker call and remove its unique client container; a Compose
310
+ exit with a non-zero status also removes it, so a call that stopped between
311
+ create and start never remains `Created`. The manager never retries a provider
312
+ operation. Service startup/restart is explicit; commands
312
313
  never implicitly start a stopped provider service. Plugin data does not enter the
313
314
  relay. The manager sends only a whitelist of Docker client environment variables.
314
315
 
@@ -412,10 +413,12 @@ QMD runtimes or edit installed Compose/package files to bypass missing support.
412
413
 
413
414
  ## Generated capability discovery
414
415
 
415
- `ez tools list --details` generates a compact index directly from installed
416
- `ez-plugin.json` manifests: each plugin supplies its `description`, command aliases
417
- and `skills`. Descriptions are limited to 200 characters in this view; full
418
- instructions stay in the skill. Installation, replacement and removal are reflected
416
+ `ez tools list --details` generates a self-sufficient index directly from
417
+ installed `ez-plugin.json` manifests. Per plugin it prints `aliases`, a one-line
418
+ `purpose` (the manifest `description`, at most 200 characters), absolute `skills`
419
+ paths and one `example` call. The optional manifest `example` is one line of at
420
+ most 160 characters starting with a declared command alias; without it the
421
+ example is `ez <first alias> --help`. Full instructions stay in the skill. Installation, replacement and removal are reflected
419
422
  on the next read, without hooks, LLM calls or a cached inventory file.
420
423
 
421
424
  The additive `skillReads` field supplies literal argument arrays for
@@ -424,8 +427,8 @@ installed plugin revision, declared path and skill content. Use it in isolated
424
427
  agents, where host package paths are private. It reads only the installed
425
428
  manifest's skills; arbitrary files, symlinks and files over 64 KiB are rejected.
426
429
 
427
- `ez tools list` retains its alias mapping for existing clients. Native AGENTS.md
428
- contains only the agent-bound discovery shortcut. New workspaces do not seed
430
+ `ez tools list` retains its alias mapping for existing clients. The workspace
431
+ seed writes one static line pointing here; nothing else is written. New workspaces do not seed
429
432
  TOOLS.md, SOUL.md, USER.md, or an `inbox/`/`work/` layout; upgrades preserve
430
433
  legacy notes without rewriting them. Keep owner/account policies in agent
431
434
  instructions or linked policy files, separate from plugin metadata.
@@ -21,7 +21,8 @@ ezenciel-agents-schedule cancel RUN_ID
21
21
  ```
22
22
 
23
23
  Use `--text-file` for longer instructions. `edit ID` replaces the complete schedule
24
- with a new revision; use the same trigger/name/text flags as `create`.
24
+ with a new revision; use the same trigger/name/text flags as `create`. It keeps
25
+ the paused/enabled state: an edited paused task stays paused until `resume`.
25
26
  An edit keeps a future pending occurrence when its trigger is unchanged;
26
27
  otherwise the edited rule begins at its next future occurrence. Editing does
27
28
  not replay missed work from an old start date. Use `--now` to request a new
@@ -57,7 +58,7 @@ available through `ezenciel-agents-schedule context`.
57
58
 
58
59
  ## Ordered setup fallback
59
60
 
60
- An agent task may list up to three fallback setups after its saved one, for
61
+ An agent task may list ordered fallback setups after its saved one, for
61
62
  example `claude · opus → claude · sonnet → codex · gpt-6-sol`:
62
63
 
63
64
  ```sh
@@ -68,30 +69,58 @@ ezenciel-agents-schedule create --name Daily --cli claude --model opus \
68
69
 
69
70
  `edit` keeps the saved fallbacks unless `--fallback` replaces them or
70
71
  `--clear-fallbacks` removes them. Setups must be distinct and each needs a model.
72
+ There is no fixed list-length limit. Each setup captures its own supported CLI,
73
+ model, effort and optional provider; the installed native client owns valid model
74
+ and effort values. The existing failure rules below apply to every setup.
75
+ Each fallback may include `auth-profile=NAME` to select its own owner-provisioned
76
+ Codex or Claude account. Omitting it selects the default login, independently of
77
+ the primary setup. For four separate Claude accounts, provision and log into
78
+ `claude2`, `claude3` and `claude4`, then use:
79
+
80
+ ```sh
81
+ ezenciel-agents-schedule create --name Daily --cli claude --model opus \
82
+ --cron '0 9 * * *' --timezone Asia/Dubai --text 'Prepare the daily report.' \
83
+ --fallback cli=claude,model=opus,auth-profile=claude2 \
84
+ --fallback cli=claude,model=opus,auth-profile=claude3 \
85
+ --fallback cli=claude,model=opus,auth-profile=claude4
86
+ ```
71
87
 
72
88
  Core advances to the next setup only with typed evidence that nothing ran: the
73
89
  CLI is not installed or executable, or the provider rejected the turn before any
74
90
  model output, tool or hook item (quota/rate limit, billing, overload, server
75
- error, unavailable model or bad request). It stops, without replay, on access
76
- denial (authentication, account or organization), cancellation, relay shutdown,
91
+ error, unavailable model or bad request), including a missing, expired or revoked
92
+ login (typed authentication failure or HTTP401). Native API-error metadata does
93
+ not count as model/tool work. It stops, without replay, on access
94
+ denial (HTTP403, account or organization restriction), cancellation, relay shutdown,
77
95
  a schedule edit, a policy or unknown error, an interrupted transport, or any
78
- failure after work began. Evidence comes from structured events only: Codex
96
+ other failure after work began. Evidence comes from structured events only: Codex
79
97
  native-session `codexErrorInfo` and Claude `stream-json` error fields (scheduled
80
98
  Claude runs use `--output-format stream-json`; the host forwards only a compact
81
99
  startup summary). Other engines advance only when their CLI is unavailable.
82
100
 
83
- A quota rejection skips the remaining setups in the same quota scope: the native
84
- client (`claude`, `codex` including desktop and custom Codex providers, `grok`)
85
- or the upstream provider for OpenCode/Pi models. A different credential alone is
86
- never treated as an independent allowance. Each setup runs at most once per
87
- occurrence.
101
+ The one failure after work began that continues is a Claude usage limit the run
102
+ ended on (for example "You've hit your session limit"): a typed quota rejection
103
+ with no model, tool or hook activity after it. A native session cannot be resumed
104
+ under another login's configuration home, so the next eligible setup starts a fresh
105
+ session with the saved prompt plus one factual line naming the run, the stopped
106
+ setup, its native session and reset time, and that its work may be partly applied.
107
+ The agent reconciles from its own records; core replays nothing else.
108
+
109
+ A login or quota rejection skips the remaining setups in the same scope: the native
110
+ client and login profile (`claude`, `codex` including desktop and custom Codex
111
+ providers), `grok`, or the upstream provider for OpenCode/Pi models. A named
112
+ profile selects a separate owner-provisioned account and is eligible for its own
113
+ attempt; changing only the model in the same profile does not reset its scope.
114
+ Do not provision aliases of the same account to obtain more allowance. Ez does
115
+ not infer account independence from credentials or promise that a shared
116
+ organization's allowance is separate. Each setup runs at most once per occurrence.
88
117
 
89
118
  Every attempt keeps the same run ID, occurrence, schedule revision, workspace,
90
119
  delivery binding and scheduled concurrency slot, in a fresh native session.
91
120
  `run RUN_ID` shows `attempts`: each setup, quota scope, outcome, failure category
92
- (`cli-unavailable`, `quota`, `provider-rejected`, `access-denied`,
93
- `after-work-began`, `uncertain`, or `same-quota-scope` for a skipped setup), exit
94
- code and reported reset time. If every setup is unavailable the run fails with
121
+ (`cli-unavailable`, `login-unavailable`, `quota`, `provider-rejected`, `access-denied`,
122
+ `quota-after-work-began`, `after-work-began`, `uncertain`, or `same-login-scope`/`same-quota-scope`
123
+ for a skipped setup), exit code, reported reset time and the session ID Ez assigned. If every setup is unavailable the run fails with
95
124
  reason `setups-unavailable`, a summary of each setup, and an owner notice on
96
125
  Telegram-bound tasks. Core never purchases credits, enables overage or consumes
97
126
  reset credits; the next regular occurrence runs normally.
@@ -225,10 +254,13 @@ queued occurrence; already-running work continues until explicitly cancelled.
225
254
 
226
255
  `/stop` stops all active work; `cancel RUN_ID` stops one background task. `/cancel`
227
256
  clears queued work. Pause/remove a recurring schedule to prevent future runs.
228
- Stopping the relay also stops its workers. Run and delivery records are relay
229
- memory: a process restart drops in-flight runs and queued deliveries by design,
230
- so the next eligible occurrence dispatches normally and nothing is replayed from
231
- disk. Status labels failed runs as history and shows recent reasons; new failures retain their exit code or interruption cause. Typing indicators stop after 30 seconds even when work continues. A failed run that cannot prove whether an external side effect happened stays visible until the agent inspects the evidence and explicitly edits the schedule. Inspect the task's files, native session and delivery
257
+ Stopping the relay also stops its workers, except on host-capable agents: a
258
+ graceful relay stop hands running host-transport runs to the next relay, which
259
+ adopts them without relaunching (see [Docker runtime](docker-runtime.md)). A
260
+ request the host had not yet claimed when the relay stopped is failed by the host
261
+ without starting. Run and delivery records are otherwise relay memory: a process
262
+ restart drops in-flight runs and queued deliveries by design, so the next
263
+ eligible occurrence dispatches normally and nothing is replayed from disk. Status labels failed runs as history and shows recent reasons; new failures retain their exit code or interruption cause. Typing indicators stop after 30 seconds even when work continues. A failed run that cannot prove whether an external side effect happened stays visible until the agent inspects the evidence and explicitly edits the schedule. Inspect the task's files, native session and delivery
232
264
  receipts before deciding whether to resume. A clock cannot reconstruct an
233
265
  in-flight process or prove whether an external side effect happened.
234
266
 
@@ -307,10 +339,18 @@ messages, read private sessions, trigger work or certify owning results.
307
339
 
308
340
  The prompt controls diagnosis, authorized recovery and quiet notification behavior. Inspect prior effects and receipts before retrying anything. A failed review run itself remains visible as a new failure for the next occurrence.
309
341
 
310
- The Telegram Scheduled tasks menu lists enabled schedules that still have a pending
311
- occurrence or a queued/running occurrence. Finished one-time tasks, paused
312
- schedules and revisions stopped for review are hidden. Each entry shows the
313
- effective engine/model/effort, next occurrence in UTC (or queued/running state),
342
+ The Telegram Scheduled tasks menu lists paused schedules alongside enabled schedules
343
+ that still have a pending occurrence or a queued/running occurrence. Paused tasks
344
+ are labeled Paused and have no next run; an existing queued/running occurrence
345
+ keeps its execution state visible. Finished enabled one-time tasks and enabled
346
+ revisions stopped for review are hidden. Each entry shows the
347
+ frequency with the cron timezone, the saved primary and fallback setups in order
348
+ (CLI, login profile, model and effort), next occurrence in UTC (or queued/running state),
314
349
  and the first sentence of its saved invocation
315
350
  prompt, limited to 140 characters. This is a read-only view; history and full
316
- prompts remain available through the scheduling CLI.
351
+ prompts remain available through the scheduling CLI. `/tasks N` shows the same
352
+ frequency and execution path with the saved instructions. Common minute/hourly,
353
+ daily and weekday rules have plain-language labels; other calendar rules show
354
+ the exact cron expression and timezone. Interval rules show their fixed period,
355
+ and one-time tasks show `Once`. This describes the schedule, not a guarantee of
356
+ work when admission/preflight conditions are unmet.
package/docs/setup.md CHANGED
@@ -261,8 +261,8 @@ node /absolute/ezenciel_agents/bin/ezenciel-agents-tools.mjs init \
261
261
 
262
262
  This binds a private `ez` and preserves native command access. The default catalog
263
263
  is empty. Do not supply a plugin catalog during first-time main onboarding.
264
- Initialization
265
- adds discovery instructions to the mind's AGENTS.md. Verify `tools/bin/ez plugins
264
+ The seeded
265
+ AGENTS.md already points at `ez tools list --details`. Verify `tools/bin/ez plugins
266
266
  available` before the first agent turn. Also execute the agent-bound
267
267
  `ezenciel-agents-message --help` through the selected CLI sandbox and verify
268
268
  its actual tool output, so absent launchers or blocked execution are detected
@@ -22,14 +22,14 @@ node /absolute/package/bin/ezenciel-agents-tools.mjs init --standalone \
22
22
  /absolute/private/company-tools/bin/ez plugins list
23
23
  ```
24
24
 
25
- Init starts nothing, uses an empty catalog by default, preserves existing
26
- workspace notes and adds a managed registry locator to AGENTS.md. A registry
25
+ Init starts nothing, uses an empty catalog by default and never writes
26
+ workspace instructions. A registry
27
27
  cannot be replaced by rerunning init. Keep the package at its original path:
28
28
  the launcher imports it. Status reports `main: null` without a relay binding;
29
29
  automated software upgrades currently require a relay deployment.
30
30
 
31
- Use `ez tools list --details` for generated installed-plugin descriptions, help shortcuts and absolute skill paths. Read only the relevant skill.
32
- Add that instruction to its existing project instructions without replacing them.
31
+ Use `ez tools list --details` for each installed plugin's aliases, purpose, skill paths and one example call. Read only the relevant skill.
32
+ Add "Tools: run `ez tools list --details`; each plugin's `--help` gives usage." to the project's existing instructions, with `ez` resolving to the returned launcher.
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
35
  A standalone company registry remains explicit even when invoked from a different
package/docs/upgrades.md CHANGED
@@ -16,7 +16,12 @@ automatic channel for core and plugins without a saved policy. Existing explicit
16
16
  stable or manual policies are preserved. The owner may select either per target.
17
17
  The main target and installed plugins version independently.
18
18
 
19
- Core upgrades leave channel admission open while native work runs, then pause it after work is idle and recheck races. Plugin upgrades build while native work continues. Activation closes the existing registry admission lock, drains in-flight plugin command leases, and replaces the plugin under that lock. New native plugin calls wait for admission; an outdated revision fails before provider execution and must be rediscovered. No admitted provider operation is replayed. The shared broker and native sessions remain running. Core upgrades still require the full native-work drain. A plugin update also retains that drain if an operator replaced the broker’s single-file host binding (for example to approve new network pins), because that case still requires broker recreation.
19
+ Handoff-compatible core upgrades build while native work continues and pause admission only for activation. The current native executor retires from claiming new work; the replacement claims new requests while the retiring generation finishes its existing runs. The relay adopts those runs without replay, preserving their schedule holds, cancellation and settled delivery receipts. The supervisor reloads only after native work is idle, so OS service process-group cleanup cannot kill a retiring generation. Plugin upgrades build while native work continues. Activation closes the existing registry admission lock, drains in-flight plugin command leases, and replaces the plugin under that lock. New native plugin calls wait for admission; an outdated revision fails before provider execution and must be rediscovered. No admitted provider operation is replayed. The shared broker and native sessions remain running. Older runtimes without the handoff contract retain the full native-work drain. Isolated agents migrate once while idle to a separate, unprivileged executor container using the same file transport; later relay/runtime upgrades preserve that executor generation. No native CLI or provider credentials move to the host. A plugin update also retains that drain if an operator replaced the broker’s single-file host binding (for example to approve new network pins), because that case still requires broker recreation.
20
+
21
+ Compatible main upgrades also finish their advisory workspace/native-client
22
+ backup before closing admission or stopping the relay. Large native histories do
23
+ not extend the swap's delivery outage. These live-state copies are best effort;
24
+ rollback restores code and configuration, never the backup over active state.
20
25
 
21
26
  The owner chooses the saved update policy. The host supervisor owns
22
27
  interruption-safe replacement. There is one host service per agent, not a second
@@ -153,7 +158,7 @@ is required. Stable automation excludes prereleases, major changes and 0.x minor
153
158
  changes. Beta policy permits compatible prereleases. Downgrades are rejected;
154
159
  rollback is a recovery operation for the previous installation only.
155
160
 
156
- `apply` records one durable queued job. **For a core upgrade, the agent must finish its turn after queuing it** so the supervisor can drain native work. A plugin upgrade can complete while that turn continues; only plugin-call admission is held during activation. Calls already executing finish first.
161
+ `apply` records one durable queued job. **For a core upgrade from a runtime without the handoff contract, the agent must finish its turn after queuing it** so the supervisor can drain native work. New compatible runtimes can swap while native work continues. A plugin upgrade can complete while that turn continues; only plugin-call admission is held during activation. Calls already executing finish first.
157
162
  One job at a time; an unresolved recovery blocks new upgrades. The agent processes
158
163
  multiple requested packages over successive update requests.
159
164
 
@@ -255,19 +260,15 @@ handbook from workspace `AGENTS.md` (and existing `AGENTS.override.md`).
255
260
  Personal text outside that block is preserved byte-for-byte. Do not reinsert
256
261
  coding, KISS, contribution or repair loops into operating minds.
257
262
 
258
- The package-owned guidance is the managed `<!-- ez tools -->` block: the bound
259
- `ez` launcher, `ez --help`, `ez tools list --details`, and one routing hint for
260
- foreground owner turns to prefer `ezenciel-agents-schedule create --now` when work
261
- can continue independently without blocking interaction. The hint tells scheduled
262
- turns to execute their assigned work directly instead of rescheduling themselves.
263
- Inventory and full scheduler usage are generated on read. Plugin registry init and update binding refresh that block.
264
- Malformed markers and symlinks fail visibly rather than overwriting personal work.
265
- Upgrade and rollback render the block through the exact package root being activated,
266
- so an already-running package cannot leave stale guidance behind.
267
-
268
- Codex, Grok and OpenCode discover workspace instructions natively. Claude
269
- receives the native `--append-system-prompt-file` binding to `AGENTS.md`, alongside
270
- its own normal instructions. Fresh and resumed owner input is literal; ez does
263
+ Ez writes no managed guidance after the initial workspace seed. Update binding
264
+ (init with a host config, `enable-updates`, upgrade and rollback) replaces a former
265
+ `<!-- ez tools -->` footer once with the seeded static line
266
+ "Tools: run `ez tools list --details`; each plugin's `--help` gives usage." and
267
+ otherwise leaves `AGENTS.md` untouched. Malformed markers and symlinks fail visibly
268
+ rather than overwriting personal work. Inventory and usage are generated on read.
269
+
270
+ Codex, Grok, OpenCode and Claude Code discover workspace `AGENTS.md` natively
271
+ (Claude Code loads it when the workspace has no `CLAUDE.md`). Fresh and resumed owner input is literal; ez does
271
272
  not surround it with policies, tool recipes, repair instructions or history.
272
273
  The native engine owns instruction loading and its context/token overhead.
273
274
  Already running sessions retain their current context until native reload.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jc_stack/ez-agents",
3
- "version": "0.1.0-beta.49",
3
+ "version": "0.1.0-beta.51",
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.",
@@ -112,7 +112,9 @@
112
112
  "protocol": 1,
113
113
  "kind": "main",
114
114
  "stateSchema": 1,
115
- "mainProtocol": 1
115
+ "mainProtocol": 1,
116
+ "runHandoff": 1,
117
+ "containerExecutor": 1
116
118
  },
117
119
  "exports": {
118
120
  "./application-client": "./src/application-client.mjs",
@@ -5,8 +5,8 @@ import path from 'node:path'
5
5
  const start = '<!-- ez shared guidance: begin -->'
6
6
  const end = '<!-- ez shared guidance: end -->'
7
7
 
8
- // Drop the obsolete package-owned handbook. Discovery lives in the managed
9
- // ez tools locator. Personal text outside the markers stays byte-for-byte.
8
+ // Drop the obsolete package-owned handbook. Discovery is seeded once and
9
+ // generated by `ez tools list --details`. Personal text outside the markers stays byte-for-byte.
10
10
  export async function installAgentGuidance(workspace: string): Promise<void> {
11
11
  for (const name of ['AGENTS.md', 'AGENTS.override.md']) {
12
12
  const file = path.join(workspace, name)
package/src/ai-cli.ts CHANGED
@@ -5,6 +5,7 @@ import { mkdir, readFile } from 'node:fs/promises'
5
5
  import { authProfileStatus, readModels, presetProvider, sameChoice, sameEngine, validateSelection, type AiPreset, type ModelChoice } from './ai.js'
6
6
  import { cliHome } from './auth-profile.js'
7
7
  import { ControlStore } from './control-state.js'
8
+ import { resolveIsolation } from './isolation.js'
8
9
 
9
10
  const usage='Usage: ezenciel-agents-ai list | select --cli <installed-cli> [--auth-profile <name>] [--provider <provider>] [--model <model>] [--effort <effort>] | default | profiles | profile add --cli codex|claude --auth-profile <name>'
10
11
  const {values,positionals}=parseArgs({allowPositionals:true,options:{help:{type:'boolean'},cli:{type:'string'},'auth-profile':{type:'string'},provider:{type:'string'},model:{type:'string'},effort:{type:'string'}}})
@@ -15,8 +16,9 @@ if(values.help){
15
16
  if (!process.env.EZ_CONTROL_DIR) throw new Error('Use this agent’s bound control directory')
16
17
  const controlDir=process.env.EZ_CONTROL_DIR
17
18
  const authProfile=values['auth-profile']
18
- const catalog=():Promise<ModelChoice[]>=>readFile(join(controlDir,'host-executor','models.json'),'utf8')
19
- .then(text=>JSON.parse(text)).catch(()=>readModels(undefined,undefined,join(controlDir,'cli','codex'),undefined,undefined,undefined,controlDir))
19
+ const catalog=():Promise<ModelChoice[]>=>(process.env.EZ_AI_CATALOG==='host'||(process.env.EZ_AI_CATALOG===undefined&&resolveIsolation(process.env)==='host-capable'))
20
+ ? readFile(join(controlDir,'host-executor','models.json'),'utf8').then(text=>JSON.parse(text))
21
+ : readModels(undefined,undefined,join(controlDir,'cli','codex'),undefined,undefined,undefined,controlDir)
20
22
  if(positionals[0]==='list')console.log(JSON.stringify(await catalog()))
21
23
  else if(positionals[0]==='profiles')console.log(JSON.stringify(await authProfileStatus(controlDir)))
22
24
  else if(positionals[0]==='profile' && positionals[1]==='add'){