@jc_stack/ez-agents 0.1.0-beta.26 → 0.1.0-beta.28

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 (128) hide show
  1. package/.env.example +10 -1
  2. package/AGENTS.md +40 -9
  3. package/CHANGELOG.md +35 -0
  4. package/CONTRIBUTING.md +31 -1
  5. package/Dockerfile +1 -0
  6. package/README.md +84 -12
  7. package/bin/ezenciel-agents-application +2 -0
  8. package/bin/ezenciel-agents-application.mjs +16 -0
  9. package/compose.yaml +8 -0
  10. package/docker/entrypoint.sh +20 -2
  11. package/docker/healthcheck.mjs +1 -1
  12. package/docker/run.ts +3 -3
  13. package/docker/smoke.mjs +41 -2
  14. package/docs/application-channel.md +366 -0
  15. package/docs/architecture/ai-selection.md +12 -15
  16. package/docs/docker-runtime.md +29 -0
  17. package/docs/host-service.md +5 -8
  18. package/docs/local-qa.md +1 -1
  19. package/docs/managed-applications.md +68 -0
  20. package/docs/plugin-catalog.md +1 -0
  21. package/docs/plugin-connection.md +76 -0
  22. package/docs/plugins.md +54 -5
  23. package/docs/repair.md +26 -25
  24. package/docs/responsive-channels.md +13 -55
  25. package/docs/scheduling.md +40 -36
  26. package/docs/setup.md +11 -21
  27. package/docs/standalone-cli.md +2 -2
  28. package/docs/upgrades.md +43 -18
  29. package/package.json +8 -4
  30. package/src/agent-guidance.ts +32 -3
  31. package/src/ai-cli.ts +5 -1
  32. package/src/ai.ts +6 -28
  33. package/src/application-channel.ts +308 -0
  34. package/src/application-cli.ts +41 -0
  35. package/src/application-client.mjs +87 -0
  36. package/src/application-origin.ts +15 -0
  37. package/src/codex-session.ts +7 -10
  38. package/src/config.ts +23 -5
  39. package/src/control-state.ts +274 -21
  40. package/src/conversation-menu.ts +89 -0
  41. package/src/delivery-context.d.mts +5 -0
  42. package/src/delivery-context.mjs +25 -0
  43. package/src/desktop-bridge.ts +11 -43
  44. package/src/event-sources.ts +2 -2
  45. package/src/execution-authority.ts +2 -0
  46. package/src/executor.ts +29 -58
  47. package/src/host-executor.ts +11 -9
  48. package/src/identity.ts +11 -3
  49. package/src/index.ts +191 -93
  50. package/src/menu.ts +76 -55
  51. package/src/message-history.ts +52 -0
  52. package/src/message-send.ts +1 -1
  53. package/src/message.ts +49 -7
  54. package/src/model-policy.ts +5 -15
  55. package/src/owner.ts +7 -1
  56. package/src/plugins/connection-artifacts.mjs +31 -0
  57. package/src/plugins/connection.mjs +124 -0
  58. package/src/plugins/manager.mjs +93 -23
  59. package/src/plugins/native-tasks.d.mts +4 -0
  60. package/src/plugins/native-tasks.mjs +66 -0
  61. package/src/plugins/workspace-lease.d.mts +3 -0
  62. package/src/plugins/workspace-lease.mjs +44 -0
  63. package/src/repair-policy.ts +0 -8
  64. package/src/reply-context.ts +3 -29
  65. package/src/runs.ts +67 -9
  66. package/src/schedule-cli.ts +33 -15
  67. package/src/scheduled-tasks.ts +20 -21
  68. package/src/scheduler.ts +55 -22
  69. package/src/task-executor.ts +4 -5
  70. package/src/task-workspace.ts +2 -11
  71. package/src/update-attention.ts +1 -1
  72. package/src/updates/binding.mjs +2 -6
  73. package/src/updates/control.mjs +4 -0
  74. package/src/updates/supervisor.mjs +10 -4
  75. package/src/web-launcher.ts +19 -0
  76. package/src/workspace.ts +3 -1
  77. package/templates/agent/AGENTS.md +13 -55
  78. package/templates/agent-guidance.md +90 -37
  79. package/templates/deployments.md +24 -0
  80. package/templates/failure-review.md +6 -0
  81. package/templates/maintainer-purpose.md +12 -6
  82. package/test/agent-guidance.test.ts +29 -39
  83. package/test/ai-cli.test.ts +9 -0
  84. package/test/ai.test.ts +66 -22
  85. package/test/application-channel.test.ts +283 -0
  86. package/test/application-client.test.mjs +84 -0
  87. package/test/application-controls.test.ts +224 -0
  88. package/test/application-only.test.ts +100 -0
  89. package/test/busy-reply-relay.test.ts +11 -7
  90. package/test/channel-delivery.test.ts +63 -0
  91. package/test/channel-owner.test.ts +161 -0
  92. package/test/client-defaults.test.ts +1 -1
  93. package/test/codex-session.test.ts +18 -10
  94. package/test/config.test.ts +16 -1
  95. package/test/connection-artifacts.test.mjs +32 -0
  96. package/test/conversation-menu.test.ts +67 -0
  97. package/test/conversations.test.ts +84 -0
  98. package/test/desktop-bridge.test.ts +17 -11
  99. package/test/engine-handoff.test.ts +73 -0
  100. package/test/event-sources.test.ts +5 -8
  101. package/test/executor.test.ts +68 -16
  102. package/test/failure.test.ts +64 -0
  103. package/test/host-executor.test.ts +58 -17
  104. package/test/install-config.test.ts +1 -1
  105. package/test/intake-relay.test.ts +169 -25
  106. package/test/message-history.test.ts +127 -0
  107. package/test/model-policy.test.ts +23 -48
  108. package/test/native-tasks.test.ts +36 -0
  109. package/test/plugin-connection.test.mjs +124 -0
  110. package/test/plugin-manager.test.mjs +70 -10
  111. package/test/repair-policy.test.ts +8 -12
  112. package/test/runs.test.ts +13 -0
  113. package/test/runtime-identity.test.mjs +18 -0
  114. package/test/schedule-cli.test.ts +34 -5
  115. package/test/scheduled-tasks.test.ts +79 -8
  116. package/test/scheduler.test.ts +30 -1
  117. package/test/task-native.test.ts +5 -2
  118. package/test/update-attention.test.ts +1 -2
  119. package/test/updates.test.mjs +44 -5
  120. package/test/workspace.test.ts +2 -3
  121. package/scripts/smoke-busy-reply.ts +0 -58
  122. package/src/reply-executor.ts +0 -55
  123. package/src/reply-mcp.ts +0 -23
  124. package/templates/agent/TOOLS.md +0 -105
  125. package/templates/chat-guidance.md +0 -23
  126. package/templates/standalone-tools.md +0 -20
  127. package/templates/updates.md +0 -45
  128. package/test/reply.test.ts +0 -159
@@ -0,0 +1,366 @@
1
+ # Application input to the standard agent
2
+
3
+ An installed application can submit work to the ordinary Ez agent. Ez uses its
4
+ existing run queue, native executor, session store, cancellation and message
5
+ outbox. The application supplies domain tools and keeps its UI/content API; it
6
+ can remove its own model runner, continuation loop and engine authentication.
7
+ This is separate from the older outbound channel-backend integration.
8
+
9
+ ## Shared runtime controls
10
+
11
+ Register with `--share-owner` (the existing `--share-telegram` spelling is an
12
+ alias) to grant access to the runtime's active conversation and standard AI
13
+ settings. This explicit grant works with or without a Telegram bot. It grants
14
+ control of the same active state Telegram uses when enabled; it is not a
15
+ scope-only permission. Ordinary application bindings cannot read or change it.
16
+
17
+ `GET /v1/control` returns the existing Ez AI presets, installed native model
18
+ catalog, active control-session ID and conversations visible through `/chats`.
19
+ Native engine session IDs and private application scopes are omitted. The token
20
+ stays in the authenticated application backend, never in browser code.
21
+
22
+ `POST /v1/control` accepts one action and the `expectedSession` returned by the
23
+ last read (null before a conversation exists):
24
+
25
+ | Action | Additional fields | Existing Ez operation |
26
+ | --- | --- | --- |
27
+ | `new` | None | `/new`, using the default AI |
28
+ | `switch` | `sessionId` from the visible conversation list | `/chats` selection |
29
+ | `select` | `presetId` from `ai.presets` | Select a saved AI |
30
+ | `model` | `cli`, optional `model` and `effort`, from `models` | Save/select the same native choice as `/ai` |
31
+
32
+ Engine changes start a fresh conversation through the same selection operation
33
+ as Telegram. A changed active conversation rejects a stale mutation; refresh
34
+ controls before another attempt. Model changes within one conversation retain
35
+ the ordinary last-selection-wins behavior. Already-admitted runs keep their
36
+ captured conversation and AI. Uncertain control responses require a fresh read,
37
+ not blind repetition of `/new`.
38
+
39
+ For private application conversations, `GET /v1/scope-control?scope=<encoded-scope>` returns
40
+ the same catalog and the current scope's public control ID and preset. The scope
41
+ is the application's original admission scope, resolved under its authenticated
42
+ binding. `POST` accepts `new`, `select` and `model` with that scope's
43
+ `expectedSession`. It does not require shared-control permission. Shared scopes
44
+ must use `/v1/control` instead.
45
+
46
+ A scope reset uses the configured default AI. A client change starts a fresh
47
+ private conversation; a model change within the same client retains it. Retired
48
+ private conversations stay hidden from `/chats`; admitted work can finish in its
49
+ original native session. Neither operation changes the shared active selection.
50
+ Control mutations are never marked retryable by the public client: read back
51
+ after uncertainty before deciding on another change.
52
+
53
+ Current HTTP gaps: rename/archive, scheduling
54
+ administration and shared-chat stop-all are not exposed here. Per-run application
55
+ cancellation remains available. The native agent can use the standard scheduler
56
+ from an application turn; its replies retain that channel binding.
57
+
58
+ ## Install and authorize
59
+
60
+ An installation has one owner, independent of its channels. As the installing
61
+ administrator, outside an agent turn, generate a random 32-byte base64url token
62
+ into a private file and register the first channel with a verified opaque owner ID:
63
+
64
+ ```sh
65
+ ezenciel-agents-application --owner-id verified-account --id web --token-file /private/web-token --share-owner
66
+ ezenciel-agents-application --id phone --token-file /private/phone-token --share-owner
67
+ ```
68
+
69
+ The application backend receives that token through its secret store. Core stores
70
+ only its SHA-256 digest, bound to the installation owner. Channel IDs are arbitrary
71
+ labels, not a predefined provider list. The trusted adapter verifies its provider's
72
+ identity (for example Privy); a browser-supplied owner ID is never authentication.
73
+ For an existing Telegram owner, omit `--owner-id`: the new channel attaches to that
74
+ same owner. `GET /v1/registration` returns the owner ID and binding ID.
75
+ Revoke a channel with `--id web --revoke`. Rotate with `--id web --token-file
76
+ /private/new-token --rotate`: the binding ID, retries and native sessions remain
77
+ unchanged. Revoking the installation owner invalidates every channel.
78
+ Revocation blocks admission, results and delivery and stops an active app run on
79
+ the existing queue tick. `--list` exposes IDs, never tokens or hashes.
80
+
81
+ This grant lets a **trusted application backend act for the paired owner**. It is
82
+ not a multi-tenant filesystem sandbox. The application authenticates its users,
83
+ derives their allowed scope, and enforces domain tool permissions. Independent
84
+ owners or untrusted applications need separate agents/workspaces. Scoped sessions
85
+ separate conversation history; they share the ordinary agent's filesystem and
86
+ tool authority. Do not give this bearer token to browser JavaScript or models.
87
+
88
+ Enable the optional listener in the relay environment:
89
+
90
+ ```dotenv
91
+ EZ_APPLICATION_PORT=8787
92
+ EZ_APPLICATION_HOST=0.0.0.0
93
+ ```
94
+
95
+ The default bind address is loopback. In Docker, put the relay and backend on a
96
+ private shared network and use `http://<relay-service>:8787`; do not publish a host
97
+ port. Cross-host use requires HTTPS or an authenticated private tunnel. There is
98
+ no listener unless a port is configured. This mode cannot be combined with the
99
+ older channel-backend URL. Install core normally: its bin manifest makes the
100
+ application administration command available through the standard tools setup.
101
+
102
+ ## Request and result
103
+
104
+ All endpoints require `Authorization: Bearer <token>`.
105
+
106
+ ```text
107
+ POST /v1/runs
108
+ {"requestId":"job-123","scope":"principal:program","text":"Prepare our next lesson","context":{"reference":"lesson-1"}}
109
+
110
+ GET /v1/runs/<id>
111
+ GET /v1/runs
112
+ GET /v1/registration
113
+ POST /v1/runs/<id>/cancel
114
+ ```
115
+
116
+ Submission returns HTTP 202; reads/cancellation return 200. Each response is:
117
+
118
+ ```json
119
+ {"id":"r_app_...","scope":"principal:program","status":"queued","messages":[]}
120
+ ```
121
+
122
+ Status is `queued`, `running`, `completed`, `failed` or `cancelled`. Messages are
123
+ `{id,text}` records sent by the native agent through the existing message CLI.
124
+ The backend polls and renders them; no raw native JSONL or Telegram messages are
125
+ exposed. A delivery receipt means durable availability in the application inbox,
126
+ not that a human read it. Failure adds a generic `error`; detailed logs remain
127
+ local to core. Cancellation uses the existing child termination boundary and may
128
+ return `running` until the process exits.
129
+
130
+ `requestId` and `scope` accept 1–200 ASCII letters, digits, `_:.-`. Text is at most
131
+ 16,000 characters. Context is an optional JSON object (48 KiB); the complete HTTP
132
+ body is bounded to 64 KiB. Unknown input fields are rejected. Use a stable job ID:
133
+ retries with the same ID, scope and text return the original run and original
134
+ context, even if the caller supplies refreshed context. Changed scope/text is a
135
+ 409 conflict. Persist the submitted context/capability for recovery, or poll the
136
+ original run; retries never grant replacement authority to an admitted run.
137
+
138
+ Context is opaque domain-tool data, not prompt text or an environment override.
139
+ The existing `ezenciel-agents-schedule context` exposes it as
140
+ `run.application.context` only to the current native run. Domain tools and their
141
+ filesystem mounts are installed using ordinary Ez plugin/runtime setup. No API
142
+ field selects a native session, working directory, process environment or owner.
143
+ This first version transports text only, without file delivery, reactions or
144
+ approval UI.
145
+
146
+ ## Conversation continuity and migration
147
+
148
+ Each application binding plus scope gets an existing core session. Its first run
149
+ captures the selected engine/preset; later runs resume that same native session.
150
+ Application sessions are excluded from the Telegram session selector. Engines
151
+ that only resume an implicit latest session (`agy`) cannot serve isolated scopes.
152
+
153
+ Before cutting over an existing application, stop its old runner, copy its native
154
+ transcripts into the ordinary engine's private state, and import each authoritative
155
+ scope pointer as the administrator:
156
+
157
+ ```sh
158
+ ezenciel-agents-application --id myapp --import-scope principal:program \
159
+ --native-session NATIVE_SESSION_ID --cli codex
160
+ ```
161
+
162
+ Do this before submitting work for that scope. Import does not copy transcripts,
163
+ change the model preset, or merge histories. Verify the next real native turn
164
+ recalls the intended history and the app renders its reply before removing the
165
+ old deployment. Keep exactly one executor owner throughout the cutover.
166
+ For an owner-chat channel registered with `--share-owner`, add `--share-owner`
167
+ to the import command to select that imported conversation as the owner's current
168
+ chat. Without it, the import remains scoped and does not switch owner chat.
169
+
170
+ ## Reviewed deployment migration
171
+
172
+ The first upgrade adding the two optional application listener environment lines
173
+ changes `compose.yaml`. The existing updater deliberately rejects that runtime
174
+ shape change with “Runtime deployment changed; a separately reviewed migration is
175
+ required.” Keep this guard. Installation requires a separately reviewed deployment
176
+ migration before subsequent ordinary updates can use the new baseline.
177
+
178
+ The operator verifies the immutable archive's SHA-256, extracts it into a new
179
+ versioned package directory, copies `docker/pnpm-lock.yaml` to `pnpm-lock.yaml`,
180
+ and installs frozen dependencies **inside that package root**. Verify the new
181
+ application CLI and root-local `tsx` resolve. Build its runtime image without
182
+ stopping or changing the existing agent. Then the authorized deployment owner
183
+ switches the saved package/image binding using the standard host/update setup,
184
+ preserving the agent's existing workspace, control, native engine state, owner
185
+ pairing and plugin registry. Do not copy another agent's state or disable the
186
+ updater's deployment compatibility check.
187
+
188
+ For Docker applications, a deployment-owned overlay can attach the ordinary relay
189
+ to an explicitly created private network shared with the application backend:
190
+
191
+ ```yaml
192
+ services:
193
+ relay:
194
+ environment:
195
+ EZ_APPLICATION_PORT: "8787"
196
+ EZ_APPLICATION_HOST: "0.0.0.0"
197
+ networks:
198
+ default: {}
199
+ application:
200
+ aliases: [standard-agent]
201
+ networks:
202
+ application:
203
+ external: true
204
+ name: ${EZ_APPLICATION_NETWORK:?Set the deployment-owned application network}
205
+ ```
206
+
207
+ Create that network once through normal Docker administration; attach the backend
208
+ to the same external network in its own deployment overlay. Preserve the default
209
+ network. The backend connects to `http://standard-agent:8787`; no `ports` entry or
210
+ host listener is required. Keep the overlay outside the package, owned by the
211
+ deployment, and include it in that deployment's saved Compose invocation. This is
212
+ an explicit network configuration, not a second runtime or automatic discovery
213
+ service. Read back the loaded package/image and verify a real authenticated app
214
+ request after switching; preparation and image build alone are not rollout.
215
+
216
+ ## Shared backend client and private principals
217
+
218
+ Node backends can import `applicationBinding`, `applicationCall`, and
219
+ `runApplication` from `@jc_stack/ez-agents/application-client`. This is a small
220
+ HTTP client; core remains the sole owner of agent execution. Pin the package
221
+ revision in the application's lockfile. Do not copy this client into each app.
222
+
223
+ `applicationBinding(file, principalId)` reads a backend-private registry:
224
+
225
+ ```json
226
+ {"version":1,"bindings":[
227
+ {"principalId":"verified-person-one","url":"http://person-one-agent:8787","tokenFile":"/run/secrets/person-one"},
228
+ {"principalId":"verified-person-two","url":"http://person-two-agent:8787","tokenFile":"/run/secrets/person-two"}
229
+ ]}
230
+ ```
231
+
232
+ Resolve identity in the app before lookup. Unknown and revoked principals fail
233
+ closed. Set `revoked:true` to remove a mapping from new lookup and revoke its
234
+ core application grant to stop admitted work. Do not use a default owner's
235
+ connection. Each endpoint must be a separate ordinary Ez deployment with private
236
+ workspace, native CLI state, OS/container access and tools. The client rejects
237
+ reused endpoint origins, but cannot prove that two DNS aliases name different
238
+ containers. Provision isolation explicitly; this registry does not create it.
239
+ The backend's registry and service credentials must not be mounted in an agent.
240
+
241
+ The app keeps its existing user and coach/tutor grants. A delegated principal
242
+ must be distinct from the learner's personal principal, and every domain tool
243
+ must recheck the current grant. A coach can edit permitted learner records
244
+ without inheriting the learner's private agent conversation. No new role system
245
+ is required in Ez.
246
+
247
+ Use `runApplication({requestId,scope,text,context}, connection)`. It returns the
248
+ completed snapshot plus `reply`, the last nonempty message. An interrupted or
249
+ invalid HTTP response reports a retryable transport error; the caller reconnects
250
+ using the same persisted job ID and authority. The client does not resubmit by
251
+ itself. `applicationCall('/v1/runs/<id>/cancel', {}, connection)` uses normal
252
+ core cancellation. Aborting a local poll does not cancel admitted work.
253
+
254
+ Snapshots also expose `sessionId`, `nativeSessionId` when known, and `cli`.
255
+ An optional `expectedNativeSessionId` on submission is an assertion, never a
256
+ session selector. It rejects a missing or different imported history before
257
+ execution. Import old histories administratively before cutover. An optional
258
+ `ai:{cli,model,effort}` uses the existing preset/effort contract; a scope keeps its
259
+ CLI, while model/effort can change for later turns. Use distinct scopes for
260
+ separate CLI histories. A retry cannot change an admitted turn's AI choice.
261
+
262
+ ## One owner across channels
263
+
264
+ Register owner-chat channels with `--share-owner` and submit `followOwner:true`.
265
+ Web, phone and Telegram then use the same selected native conversation and AI.
266
+ This works without Telegram and requires no transcript replay or new runner.
267
+ Scoped application conversations remain separate when the flag is omitted.
268
+ Owner identity does not automatically merge histories or grant learner access.
269
+
270
+ To link Telegram, enable its ordinary bot configuration, send a real DM, then
271
+ approve the observed pending identity using `ezenciel-agents-owner approve ID`.
272
+ `ezenciel-agents-owner unlink-telegram` removes that channel without removing the
273
+ owner, application bindings or native sessions. Relinking does not authorize old
274
+ Telegram deliveries. Linking other providers requires an authenticated adapter;
275
+ registering the label `phone` does not install a phone service.
276
+
277
+ The legacy `--share-telegram`, `--share-active` and `followTelegram:true` spellings remain supported.
278
+
279
+ ### Existing scoped Telegram sharing
280
+
281
+ For an application using the ordinary agent's Telegram channel, the administrator
282
+ may register its grant with `--share-telegram`. An ordinary application turn can
283
+ then send `activateTelegram:true`: its scoped session becomes the owner's current
284
+ Telegram conversation. Both inputs resume the same native history. Shared scopes
285
+ appear in the existing `/chats` selector. Omit activation for temporary selection,
286
+ extraction, and delegated work; those must not switch the personal conversation.
287
+ Without the administrator's flag, application requests cannot switch Telegram.
288
+
289
+ For a main chat that should follow the owner's current Telegram conversation,
290
+ submit `followTelegram:true` under the same administrator-approved sharing grant.
291
+ This uses the normal selected conversation and AI at admission, including after
292
+ `/new`, `/chats`, or `/ai`. Do not also send `activateTelegram`, `ai`, or a native
293
+ session assertion. Retries remain pinned to their originally admitted run even
294
+ if the owner has since switched conversations. Keep exercise/detail scopes on
295
+ the ordinary scoped path by omitting this flag.
296
+
297
+ This shares native context, not an application's transcript database. Existing
298
+ Telegram-to-backend integrations are a legacy limitation. Drain/reconcile their
299
+ actual admitted runs before moving Telegram onto ordinary Ez transport and app
300
+ admission onto this path. Do not introduce an application execution queue or
301
+ route a channel-backend job into its own occupied execution queue.
302
+
303
+ For backends holding a data lock across a native turn, `reconnect:true` keeps
304
+ retrying transient transport failures with the same admitted request/GET until
305
+ core reports the outcome. It never changes a job or capability. Do not apply a
306
+ wall-clock abort that releases the data lock while remote execution continues.
307
+ Persist a restart barrier before admission if backend restart could otherwise
308
+ allow conflicting domain writes. Core remains the execution owner.
309
+
310
+ A rejected submission includes `admitted:false` only when core can verify that
311
+ no run exists for that binding/request ID. An existing conflicting run produces
312
+ `admitted:true` and `runId`. Authentication or unreadable state may leave admission
313
+ unknown. Backends may release a pending data barrier on explicit non-admission;
314
+ a generic HTTP error or revoked credential alone is not proof of termination.
315
+
316
+ ## Application-only deployment (no Telegram bot)
317
+
318
+ For a private per-user native runtime, set `EZ_TELEGRAM_ENABLED=false` and
319
+ `EZ_APPLICATION_PORT` (plus `EZ_APPLICATION_HOST` when other containers connect).
320
+ No bot token is required or used. This mode runs the existing native executor,
321
+ application queue and application outbox without creating a Telegram client,
322
+ starting Telegram sources/polling, or registering bot commands. Default deployments
323
+ still enable Telegram and require its token.
324
+
325
+ The installing administrator can initialize empty control authority and register
326
+ an application in one local command:
327
+
328
+ ```sh
329
+ ezenciel-agents-application --id web --token-file /run/private/application-token --owner-id VERIFIED_ACCOUNT_ID --share-owner
330
+ ```
331
+
332
+ No Telegram ID is required or fabricated. Account identity remains server-resolved
333
+ by the app and bound to its isolated runtime. Registration is unavailable inside
334
+ agent turns and cannot replace an existing owner or adopt orphaned session state.
335
+ Existing owners require no bootstrap. Tokens remain private; normal binding and
336
+ run authorization checks still apply. The old numeric `--owner` option is retained
337
+ only for compatibility, not recommended for new installations.
338
+
339
+ Each learner/coach principal requires its own workspace, CLI state and isolated
340
+ runtime. No separate bot is required. A channel is an authenticated route to its
341
+ existing owner, not a new owner, scheduler or native execution engine.
342
+
343
+ The standard scheduler, task controls and native delegation remain available.
344
+ Schedules created during an application turn retain its binding and reply scope;
345
+ scheduled replies use the normal outbox and appear in `GET /v1/runs` (100 channel
346
+ runs per page), with their `originRunId`. Follow `nextCursor` through
347
+ `GET /v1/runs?before=RUN_ID` until null to catch up after a disconnect; receipts
348
+ from older origins must not be limited to the UI's current page. The backend projects these receipts into
349
+ its UI; it does not schedule or execute work. Revoked channels cannot launch or
350
+ receive scheduled work. Token rotation retains delivery. Telegram-specific intake
351
+ and delivery require Telegram; pending Telegram work is never rerouted to web.
352
+
353
+ When a private container supplies the isolation boundary and
354
+ Codex cannot create its nested sandbox, its installing administrator may set
355
+ `EZ_CODEX_SANDBOX=external` together with `EZ_EXECUTOR_TRANSPORT=local`.
356
+ Local owner-authorized Codex app and Telegram turns use
357
+ `--sandbox danger-full-access`. Native scheduled owner sessions use Codex's
358
+ `externalSandbox` turn policy, with network access supplied by the container.
359
+ The default remains `workspace-write`.
360
+ Keep the container's private mounts, non-root UID, dropped capabilities and
361
+ no-new-privileges policy; the agent can access everything mounted into it.
362
+ Use standard root-start relay privilege separation when Telegram/provider
363
+ secrets are present; do not put those secrets in a same-UID process environment
364
+ or readable mount. This setting is rejected for host/backend execution and
365
+ restricted delegated tasks, and cannot be selected by an application request.
366
+ It is not forwarded to the host executor.
@@ -28,8 +28,11 @@ offer their own default only in this slice. Refresh by opening the native client
28
28
  the relay does not install models, manage subscriptions or guess aliases.
29
29
 
30
30
  Setup initialization and relay startup seed one default choice per installed client.
31
- Choose AI opens the available installed-model catalog directly; Settings keeps
32
- saved choices and Refresh available AIs repeats default discovery. Active/default
31
+ Choose AI shows the three most recent valid choices and installed clients, then
32
+ the selected client's models and reasoning levels. Selecting a model and reasoning
33
+ level applies that choice immediately. The retired Settings control points to
34
+ Choose AI; application-backed channels keep these controls in their application.
35
+ Refresh available AIs repeats default discovery. Active/default
33
36
  presets and queued snapshots are preserved; discovery only refreshes unused
34
37
  detected entries.
35
38
  Codex uses its native `config/read` interface; Grok reads its documented user
@@ -38,17 +41,11 @@ workspace JSON settings; OpenCode reports resolved config. Unknown defaults and
38
41
  opaque wrappers remain explicitly “client default”. No credentials are stored,
39
42
  no inference runs, no new dependency, and no cross-CLI session transfer.
40
43
 
41
- New Codex CLI and desktop agents seed **Responsive chat** (`gpt-5.6-sol`,
42
- `medium`). Durable work keeps its independent `gpt-5.6-luna` / `max` default;
43
- workers can explicitly select another model and effort for the job. Native
44
- choices for other executors remain unchanged. No fallback is selected when a
45
- model is unavailable.
44
+ New agents leave model/effort unset for native configuration to resolve. Captured
45
+ and explicitly saved choices are preserved; schedules inherit the selected engine
46
+ settings unless overridden. There are no hardcoded chat/worker models or reasoning
47
+ caps. Explicit OPENCODE_MODEL remains supported.
46
48
 
47
- Upgrades add the responsive preset to the menu without changing an existing
48
- selection, default, session, or queued run. Choose it through Choose AI for the
49
- current conversation or Settings for future conversations. `/status` shows the
50
- saved selection. Legacy client-default seeds still use native configuration
51
- metadata for display only.
52
-
53
- See [responsive channels](../responsive-channels.md) for the conversational
54
- policy and the separate boundaries for plugin correspondence and app backends.
49
+ Restricted sessions intentionally ignore unrestricted user configuration and use
50
+ isolated native defaults when no choice is supplied. See [responsive channels](../responsive-channels.md)
51
+ for their scoped tool and authority boundaries.
@@ -4,6 +4,11 @@ Docker Compose owns each relay and executable plugin. The CLI the user installs
4
4
  Ez from stays on the host, with its existing authentication. All agents in that
5
5
  installation use that CLI; there is no second CLI installation or agent-specific
6
6
  CLI login. The relay image contains Node, relay dependencies and ffmpeg.
7
+ The runtime image exposes Ez commands through the wrappers in `/app/bin`, including
8
+ `ezenciel-agents-application`; application images do not need to link Ez themselves.
9
+ The image also installs package-manifest commands in `/usr/local/bin`, so native
10
+ engine login shells can resolve them after resetting PATH. Command installation
11
+ belongs to Ez packaging, not an application image or an engine-specific prompt.
7
12
 
8
13
  ## Agent binding
9
14
 
@@ -46,6 +51,21 @@ between jobs after adding a registry binding.
46
51
 
47
52
  ## Operate and verify
48
53
 
54
+ Root-started containers default to executor UID/GID `1000:1000` and relay real
55
+ UID `1001`. To preserve an existing private workspace's ownership, set
56
+ `EZ_RUNTIME_UID`, `EZ_RUNTIME_GID` and, if necessary, `EZ_RELAY_UID` in the
57
+ deployment environment. IDs must be positive decimal integers; the relay real
58
+ UID must differ from the executor UID. The entrypoint retains its secret file
59
+ descriptor and privilege separation, then runs the executor with the configured
60
+ UID. It changes ownership only on the top-level control, home and workspace
61
+ directories; it does not recursively change existing data. Provision nested
62
+ files/mounts for that UID before starting. These settings apply to root startup;
63
+ an explicit Docker `user` still controls an already non-root process.
64
+
65
+ Changing IDs is an operator installation step, not a new tenant-isolation
66
+ mechanism. Keep private mounts and secrets isolated as described above. This
67
+ option does not change native sandbox or channel support.
68
+
49
69
  ```sh
50
70
  export EZ_DEPLOYMENT_DIR=/absolute/private/agents/family
51
71
  bin/ezenciel-agents-docker up -d --wait
@@ -153,3 +173,12 @@ its polling delay in executor duration; it is not a pure model-inference measure
153
173
  Delivery processing includes pacing, media preparation and provider calls. A sent
154
174
  message may precede executor exit. No prompt, message body or credentials are added
155
175
  to these timing logs.
176
+
177
+ ### Polling faults
178
+
179
+ Permanent errors escaping Telegram setup/polling (including unauthorized tokens,
180
+ competing pollers and programming errors) stop relay-level retry until explicit
181
+ restart after repair. Existing authorized work and outbox draining remain active;
182
+ polling health remains unhealthy. Transient HTTP, rate-limit and server errors
183
+ may reconnect. grammY still owns reconnect behavior inside its poller; ez does
184
+ not override private library methods or implement a replacement poller.
@@ -88,12 +88,9 @@ preserving it rather than reverting to a different or expired host login.
88
88
 
89
89
  An agent binding may set `sharedWorkspace` to an absolute canonical application
90
90
  repository. The host resolves its canonical path, adds that directory to Codex
91
- write permissions for chat and scheduled jobs, and serializes all bindings in
92
- that host executor which share it (including symlink aliases). Task sessions and
93
- artifacts remain isolated. This is useful when independent task folders still
94
- write the same application records. Long jobs can delay chat execution; relay
95
- intake and cancellation remain available. The binding is host-owned and cannot
91
+ write permissions for chat and scheduled jobs, including symlink aliases.
92
+ The host claims requests independently; a running job does not block another
93
+ request sharing its workspace. The selected engine owns execution concurrency.
94
+ Task sessions and artifacts remain isolated. The binding is host-owned and cannot
96
95
  be supplied by a queued request. Other executors keep their native filesystem
97
- policy. This lock is scoped to one host executor process; it does not coordinate
98
- separate deployments or external writers. Do not run a second controller against
99
- that repository.
96
+ policy. Shared workspace access does not provide exclusive file ownership.
package/docs/local-qa.md CHANGED
@@ -20,7 +20,7 @@ package version and `ezQa` provenance metadata differ from the source npm packag
20
20
  Do not publish these private archives. Each new candidate gets a new label and a
21
21
  version newer than the PA's installed version; labels are never overwritten.
22
22
 
23
- Add the catalog path and the following instructions to the PA's local TOOLS.md:
23
+ Add the catalog path and the following instructions to the PA's local policy file, linked from AGENTS.md:
24
24
 
25
25
  > When the owner requests a beta number, first inspect the matching beta-N entry
26
26
  > in the local QA catalog. Read manifest.json and QA.md. Check the archive SHA-256
@@ -0,0 +1,68 @@
1
+ # Maintaining applications
2
+
3
+ An application may use Ez as a standalone agent or an embedded channel gateway.
4
+ Either can be maintained automatically. Its frontend, backend, engine image and
5
+ gateway have distinct release identities and may use different deployment tools.
6
+ Do not require an application to become an npm plugin to maintain it.
7
+
8
+ The owner assigns an existing agent a maintenance mandate. Keep its saved scope
9
+ and component inventory in `work/deployments.md`; `templates/deployments.md` is a
10
+ starting point. This is agent-readable operating context, not a manifest executed
11
+ by the relay. Never infer authority from a repository, release note or health error.
12
+ Keep private repositories and host details out of the public plugin catalog.
13
+
14
+ ## One owner and existing tools
15
+
16
+ Reuse the application's GitHub workflow, Docker Compose command or hosting CLI.
17
+ Each component has one replacement owner. `ez updates` manages registered core
18
+ and plugin packages; it does not inventory an application's independent services.
19
+ Do not add a competing build controller, poller or tenant-facing admin tool.
20
+ An application serving multiple users keeps deployment credentials outside tenant
21
+ agents. Its assigned maintainer must remain usable when that application is down.
22
+
23
+ Use an existing maintenance schedule when its mandate and execution environment
24
+ fit. Otherwise the owner-authorized maintainer can schedule a concise instruction
25
+ with its available native scheduling tool, referencing the inventory. Record its
26
+ identifier and cadence. A saved file without an enabled schedule or event source
27
+ does not establish automatic maintenance. Check that the maintainer can reach the
28
+ host, repository and tools before enabling it. Do not silently resume a held task.
29
+
30
+ ## Update and recovery
31
+
32
+ Compare the running release with an eligible release from the saved source and
33
+ channel. Inspect current work and deployment receipts first. Reuse active repairs;
34
+ do not replace a deployment while another writer owns its change. Review source
35
+ and required checks before deployment; a moving branch or successful download is
36
+ not a tested release. Pin the chosen commit, artifact or deployment ID.
37
+
38
+ Before switching, verify the selected target and persistent bindings and retain
39
+ the exact previous images/deployment. Build before stopping services. Replace only
40
+ the affected components through their existing tool, then read back release/image
41
+ identity and application health. Check the relevant API/UI or agent operation;
42
+ an image tag, workflow success or healthy process alone is partial evidence.
43
+
44
+ A failed health check should restore compatible previous code through the same
45
+ tool and verify that recovery. Code rollback does not rewind data, migrations,
46
+ message receipts or provider actions. An incompatible migration needs its own
47
+ reviewed plan. Never replay uncertain operations or delete state to pass a check.
48
+ Keep persistent policy in saved application/host configuration, not edits to
49
+ generated Compose files that disappear during replacement.
50
+
51
+ Process restart is service-manager recovery. A persistent defect needs agent
52
+ diagnosis and, within the saved repair mandate, an isolated PR, independent review,
53
+ required tests and verified deployment. A failed candidate is not a reason to keep
54
+ retrying it: retain its receipt and resume only with changed evidence or an explicit
55
+ intervention. Keep a working runtime while repair proceeds where possible.
56
+
57
+ Record candidate, previous release, check results and final running identity in a
58
+ private receipt. Keep maintenance quiet when unchanged. Report a verified change,
59
+ failed recovery or actionable blocker once; retain the next trigger for blocked
60
+ work. Do not manufacture releases just to exercise the maintenance loop.
61
+
62
+ ## Acceptance
63
+
64
+ Before calling a deployment self-maintaining, verify the enabled schedule/event,
65
+ maintainer access, exact-version deployment and readback, configuration retention,
66
+ and failed-health rollback in an isolated environment. Reconcile the live target
67
+ after rollout. State separately which live API/UI or delivery paths were exercised
68
+ and which still need evidence. Never test rollback by breaking a user's live app.
@@ -12,6 +12,7 @@ has been published. Install only released plugins needed for the owner's request
12
12
  | [GitHub](https://github.com/jdorado/ez_github) | Create repositories, commit and push through native Git/gh with a private per-agent profile. Requires GitHub browser consent and Ez 0.1.0-beta.13 or newer. | `@jc_stack/ez-github` | [npm versions](https://www.npmjs.com/package/@jc_stack/ez-github?activeTab=versions) · [GitHub releases](https://github.com/jdorado/ez_github/releases) |
13
13
  | [Library](https://github.com/jdorado/ez-library) | Preserve agent files and attachments, extract PDF text, and retrieve notes with QMD. Optional GitHub/Drive/Dropbox persistence requires separate setup and authorization. | `@jc_stack/ez-library` | [npm versions](https://www.npmjs.com/package/@jc_stack/ez-library?activeTab=versions) · [GitHub releases](https://github.com/jdorado/ez-library/releases) |
14
14
  | [Resend](https://github.com/jdorado/ez-resend) | Receive Resend email into private idempotent receipts for agent-owned routing. Requires a Resend API key and an already-configured receiving domain. | `@jc_stack/ez-resend` | [npm versions](https://www.npmjs.com/package/@jc_stack/ez-resend?activeTab=versions) · [GitHub releases](https://github.com/jdorado/ez-resend/releases) |
15
+ | [Voice](https://github.com/jdorado/ez-voice) | Talk with the agent through an authenticated realtime voice web client, with direct access to installed Ez tools. Requires an OpenAI API key, microphone access and Ez 0.1.0-beta.28 or newer. | `@jc_stack/ez-voice` | [npm versions](https://www.npmjs.com/package/@jc_stack/ez-voice?activeTab=versions) · [GitHub releases](https://github.com/jdorado/ez-voice/releases) |
15
16
 
16
17
  Release links are live records, not a guarantee that every registered package
17
18
  has a published version. Before installing, read npm metadata and the matching
@@ -0,0 +1,76 @@
1
+ # Persistent plugin connection
2
+
3
+ `ez tools connect <installed-alias> <literal arguments...>` attaches one Compose
4
+ command container for a local, agent-bound client. It releases the registry lock
5
+ after admission. Ordinary JSON objects travel as JSONL between client and plugin;
6
+ this transport owns no inference, conversation history or plugin-specific tools.
7
+ The local owning connection uses the agent's installed plugin permissions, just
8
+ like its bound CLI. A remote or web adapter must authenticate its owner before
9
+ connecting and protect its stdin. Plugin commands retain their own authorization;
10
+ the connection adds no per-command permission prompt.
11
+
12
+ Plugin requests use `{"coreRequest":{"id":"r1","method":"tools.list","params":{}}}`.
13
+ Core answers `{"coreResponse":{"id":"r1","result":...}}` or `error` (a string).
14
+
15
+ | Method | Parameters | Result |
16
+ | --- | --- | --- |
17
+ | `tools.list` | none | Array of alias, plugin, description, skillCount, revision |
18
+ | `tools.help` | alias | CLI `--help` result: code, stdout, stderr |
19
+ | `tools.skill` | alias, index, optional line | Declared skill text and nextLine; index starts at zero, line at one |
20
+ | `tools.invoke` | alias, args (literal string array), optional stdin and output filename | CLI result: code, stdout, stderr; optional artifact path, bytes, sha256 |
21
+ | `tools.native.list` | none | Core command descriptions and availability |
22
+ | `tools.native` | command, args (literal argument array) | Native CLI result: code, stdout, stderr |
23
+
24
+ `tools.native` invokes only the shipped `schedule` or `message` CLI, using the
25
+ owning host's verified workspace/control binding and a whitelisted environment.
26
+ Read `--help` for its command contract; this connection requires inline `--text`
27
+ and rejects `--text-file` so it cannot read arbitrary host files. `create --now` submits standard
28
+ asynchronous native work; it does not launch a second runner or hold the workspace
29
+ lease while waiting. The native agent may retrieve a Library original, inspect it
30
+ with its existing image/PDF tools, and save source-linked searchable text.
31
+ Scheduler owner checks, selected engine, cancellation and receipts remain native.
32
+ No synthetic run context is injected. Standalone bindings cannot use this method;
33
+ task creation without a Telegram owner or authenticated originating channel still
34
+ fails under the scheduler's existing delivery rule. Results can be inspected with
35
+ `runs` and `run RUN_ID`; a completed run alone is not proof of indexed content.
36
+
37
+ Message delivery captures the paired owner and pairing epoch when the connection
38
+ opens. Core injects that context; tool arguments cannot select a recipient or
39
+ replace it. The message CLI enqueues the existing outbox without creating an agent
40
+ run, and the relay rechecks the owner before sending. Revocation or re-pairing
41
+ invalidates the connection. Inline text and workspace documents use the same
42
+ Telegram delivery and receipt path as native runs. Text-file input is unavailable.
43
+ History still requires a native run; `message receipt OUTBOX_ID` reconciles a
44
+ channel send against the current owner's outbox. Sends return a queued ID then
45
+ wait up to 20 seconds for delivery; queued or unknown never means sent, and must
46
+ not be retried without receipt readback. An accepted send may finish after the
47
+ voice connection closes. Omitting command retains schedule compatibility.
48
+
49
+ For binary plugin output, supply a simple `output` filename. Core saves at most
50
+ 20 MiB of stdout in a unique private workspace artifact and returns its relative
51
+ path, byte count and SHA-256 instead of binary model text. Only successful command
52
+ output is saved. The message CLI can send that path with `--document`; both host
53
+ admission and relay delivery enforce workspace containment.
54
+
55
+ The registry is read on each request. The connected plugin is excluded, including
56
+ its other aliases. Skill reads stay within its declared source directory and
57
+ return at most 100 lines. Invocations execute once through the registered binding;
58
+ a changed plugin revision requires rediscovery. Client stdin cannot forge core
59
+ requests or responses. The plugin may send `coreCancel: {id}` to cancel its own
60
+ pending request. Request IDs cannot be reused within a connection.
61
+
62
+ Commands use the existing bound Compose dispatcher, literal argv and whitelisted
63
+ environment. Calls have a 30-second timeout and 256 KiB combined output limit;
64
+ connection frames are limited to 1 MiB. Connection closure aborts pending calls
65
+ and removes their exact command containers. There is no automatic retry.
66
+
67
+ Arbitrary calls take the same fail-fast workspace lease as non-scheduled native
68
+ jobs. Native work stays pending while a call runs; calls refuse pending/running
69
+ native work. Ordinary CLI calls inside an admitted native job do not reacquire
70
+ the lease. Isolated scheduled work keeps its existing workspace behavior. A stale
71
+ native `workspace-writer.lock` is recovered at host startup only after its host
72
+ owner is dead and startup has checked previous native processes. A stale plugin
73
+ lease stops admission with an explicit error: an operator must verify its command
74
+ containers stopped before removing the lock. A dead bridge alone is insufficient.
75
+ This guard requires the updated host worker. Direct external writers are outside
76
+ the managed admission boundary.