openclaw-scheduler 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +302 -0
- package/BEST-PRACTICES.md +506 -0
- package/CHANGELOG.md +82 -0
- package/CODE_OF_CONDUCT.md +22 -0
- package/CONTEXT.md +26 -0
- package/CONTRIBUTING.md +73 -0
- package/IMPLEMENTATION_SPEC.md +170 -0
- package/INSTALL-ADDITIONAL-HOST.md +333 -0
- package/INSTALL-LINUX.md +419 -0
- package/INSTALL-WINDOWS.md +305 -0
- package/INSTALL.md +364 -0
- package/JOB-QUICK-REF.md +222 -0
- package/LICENSE +21 -0
- package/QUICK-START.md +256 -0
- package/README.md +2170 -0
- package/SECURITY.md +34 -0
- package/UNINSTALL.md +129 -0
- package/UPGRADING.md +436 -0
- package/agents.js +67 -0
- package/approval.js +107 -0
- package/backup.js +390 -0
- package/bin/openclaw-scheduler.js +138 -0
- package/cli.js +1083 -0
- package/db.js +122 -0
- package/dispatch/529-recovery.mjs +204 -0
- package/dispatch/README.md +372 -0
- package/dispatch/config.example.json +24 -0
- package/dispatch/deliver-watcher.sh +57 -0
- package/dispatch/hooks.mjs +171 -0
- package/dispatch/index.mjs +1836 -0
- package/dispatch/watcher.mjs +1396 -0
- package/dispatch-queue.js +112 -0
- package/dispatcher-approvals.js +96 -0
- package/dispatcher-delivery.js +43 -0
- package/dispatcher-maintenance.js +242 -0
- package/dispatcher-shell.js +29 -0
- package/dispatcher-strategies.js +1280 -0
- package/dispatcher-utils.js +81 -0
- package/dispatcher.js +855 -0
- package/docs/adr-schedule-ownership.md +73 -0
- package/docs/gateway-contract.md +904 -0
- package/docs/plans/2026-03-09-fix-typescript-types.md +91 -0
- package/docs/plans/2026-03-09-test-coverage-gaps.md +83 -0
- package/docs/plans/2026-03-10-dispatcher-refactor.md +801 -0
- package/docs/trust-architecture.md +266 -0
- package/gateway.js +473 -0
- package/idempotency.js +119 -0
- package/index.d.ts +864 -0
- package/index.js +17 -0
- package/jobs.js +1224 -0
- package/messages.js +357 -0
- package/migrate-consolidate.js +694 -0
- package/migrate.js +125 -0
- package/package.json +130 -0
- package/paths.js +79 -0
- package/prompt-context.js +94 -0
- package/retrieval.js +176 -0
- package/runs.js +270 -0
- package/scheduler-schema.js +101 -0
- package/schema.sql +480 -0
- package/scripts/dispatch-cli-utils.mjs +65 -0
- package/scripts/inbox-consumer.mjs +288 -0
- package/scripts/stuck-detector.sh +18 -0
- package/scripts/stuck-run-detector.mjs +333 -0
- package/scripts/telegram-webhook-check.mjs +238 -0
- package/setup.mjs +724 -0
- package/shell-result.js +214 -0
- package/task-tracker.js +300 -0
- package/team-adapter.js +335 -0
- package/v02-runtime.js +599 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# Trust Architecture
|
|
2
|
+
|
|
3
|
+
Date: 2026-03-30
|
|
4
|
+
Status: Accepted
|
|
5
|
+
|
|
6
|
+
## Purpose
|
|
7
|
+
|
|
8
|
+
This document describes the trust architecture of the scheduler/sub-agent
|
|
9
|
+
execution model: what the boundary guarantees, what it does not, and how
|
|
10
|
+
operators reason about the security properties of scheduled workflows.
|
|
11
|
+
|
|
12
|
+
## The Core Design: Scheduler as Broker, Child as Bounded Actor
|
|
13
|
+
|
|
14
|
+
The scheduler is a control-plane broker. It owns the dispatch queue,
|
|
15
|
+
credential resolution, trust evaluation, authorization gates, and run
|
|
16
|
+
lifecycle. It authenticates to the gateway with a single operator-provisioned
|
|
17
|
+
bearer token and holds whatever master/scoped keys the operator has loaded
|
|
18
|
+
into its environment or provider plugins.
|
|
19
|
+
|
|
20
|
+
Child tasks are bounded execution principals. They receive only what the
|
|
21
|
+
scheduler gives them and cannot escalate their own authority. A child's
|
|
22
|
+
credentials are resolved by an identity provider, narrowed via
|
|
23
|
+
`prepareHandoff` when the policy requires it, and materialized as scoped
|
|
24
|
+
environment variables (shell tasks) or auth-profile headers (agent tasks).
|
|
25
|
+
The child never sees the scheduler's own bearer token or master keys.
|
|
26
|
+
|
|
27
|
+
This is the broker/orchestrator + bounded actor pattern. The scheduler
|
|
28
|
+
decides what to run, with what credentials, under what trust constraints.
|
|
29
|
+
The child executes within those constraints.
|
|
30
|
+
|
|
31
|
+
## When the Boundary Is a Real Security Boundary
|
|
32
|
+
|
|
33
|
+
The scheduler/child separation is a meaningful security boundary when the
|
|
34
|
+
child is narrower than the parent in at least one of these dimensions:
|
|
35
|
+
|
|
36
|
+
- **Identity** -- different principal, different subject kind, or
|
|
37
|
+
provider-resolved session with narrower scope.
|
|
38
|
+
- **Credentials** -- downscoped key, narrower OAuth scope, shorter-lived
|
|
39
|
+
token. With dynamic RAK minting, each child gets a per-task restricted
|
|
40
|
+
API key that is revoked on cleanup.
|
|
41
|
+
- **Tools** -- different tool set, different sandbox mode, spawn depth cap.
|
|
42
|
+
- **State** -- isolated session with no access to parent's memory or
|
|
43
|
+
conversation history.
|
|
44
|
+
- **Network/filesystem scope** -- different `allowed_paths`, different
|
|
45
|
+
network policy (`unrestricted` / `restricted` / `none`).
|
|
46
|
+
|
|
47
|
+
When the child is meaningfully narrower, the boundary limits blast radius:
|
|
48
|
+
a compromised or misbehaving child cannot access the parent's full
|
|
49
|
+
credential set, cannot read or write the parent's session state, and
|
|
50
|
+
cannot escalate to the parent's trust level.
|
|
51
|
+
|
|
52
|
+
A concrete example: a workflow like
|
|
53
|
+
`check balance (read-only) -> process payment (payments:write) -> send receipt (email:send)`
|
|
54
|
+
gives each step a per-task restricted key with exactly the scope it needs.
|
|
55
|
+
The payments step cannot read customer PII, the receipt step cannot make
|
|
56
|
+
charges, and if any step is compromised, the blast radius is one
|
|
57
|
+
short-lived restricted key -- not the master.
|
|
58
|
+
|
|
59
|
+
## When the Boundary Is an Operational Boundary
|
|
60
|
+
|
|
61
|
+
If you cannot make the child meaningfully narrower in identity, tools,
|
|
62
|
+
state, or network/filesystem scope, then the sub-agent boundary is mostly
|
|
63
|
+
an execution/lifecycle boundary, not a strong security boundary. This
|
|
64
|
+
happens when:
|
|
65
|
+
|
|
66
|
+
- The child inherits the parent's full credentials
|
|
67
|
+
(`child_credential_policy: inherit`) without further narrowing.
|
|
68
|
+
- The child runs with the same tool set and no additional sandbox
|
|
69
|
+
constraints.
|
|
70
|
+
- The child's identity profile is identical to the parent's.
|
|
71
|
+
|
|
72
|
+
In this case the boundary still provides:
|
|
73
|
+
|
|
74
|
+
- **Lifecycle isolation.** The child can be timed out, retried, or
|
|
75
|
+
cancelled independently of the parent.
|
|
76
|
+
- **Attribution.** Each child has its own run record, execution ID, and
|
|
77
|
+
audit trail.
|
|
78
|
+
- **Context isolation.** The child session cannot read the parent's
|
|
79
|
+
conversation history or tool state.
|
|
80
|
+
- **Crash containment.** A child crash does not crash the parent or
|
|
81
|
+
sibling tasks.
|
|
82
|
+
- **Observability.** Independent run status, duration tracking, and
|
|
83
|
+
delivery.
|
|
84
|
+
|
|
85
|
+
These are real operational benefits, but they are not security guarantees
|
|
86
|
+
in the credential-scoping or access-control sense.
|
|
87
|
+
|
|
88
|
+
## What the Model Guarantees
|
|
89
|
+
|
|
90
|
+
**Does guarantee:**
|
|
91
|
+
|
|
92
|
+
- Credential narrowing when `child_credential_policy` is `downscope` and
|
|
93
|
+
the provider implements scope hierarchy. The provider mints a restricted
|
|
94
|
+
key scoped to exactly the permissions the child declared, with a lifetime
|
|
95
|
+
tied to the task's timeout. The key is revoked in cleanup.
|
|
96
|
+
- Trust level enforcement: a child cannot run if its resolved trust level
|
|
97
|
+
is below the contract's `required_trust_level` (when
|
|
98
|
+
`contract_trust_enforcement` is `strict` or `block`).
|
|
99
|
+
- Trust level ceiling: `independent` and `downscope` policies both enforce
|
|
100
|
+
that the child's trust level cannot exceed the parent's. Violations
|
|
101
|
+
abort dispatch.
|
|
102
|
+
- Fail-closed behavior: missing providers, unresolvable credentials,
|
|
103
|
+
invalid delegation chains, and failed proof verification all abort the
|
|
104
|
+
run rather than proceeding with degraded security.
|
|
105
|
+
- Session isolation: child sessions have independent memory, history, and
|
|
106
|
+
tool scope from the parent.
|
|
107
|
+
- Audit attribution: every run is attributed to a resolved identity with
|
|
108
|
+
full trust/authorization/evidence chain.
|
|
109
|
+
- Security aborts do not fire triggered children: a parent that fails a
|
|
110
|
+
security gate (identity, trust, authorization, proof) does not dispatch
|
|
111
|
+
downstream work.
|
|
112
|
+
|
|
113
|
+
**Does not guarantee:**
|
|
114
|
+
|
|
115
|
+
- Network isolation between parent and child. They run on the same host,
|
|
116
|
+
same gateway, same network unless the contract's `network` policy adds
|
|
117
|
+
OS-level restrictions.
|
|
118
|
+
- Filesystem isolation beyond what the contract declares in
|
|
119
|
+
`allowed_paths`. There is no container or namespace boundary.
|
|
120
|
+
- That an inherited credential is narrower than the parent's. `inherit`
|
|
121
|
+
passes through verbatim.
|
|
122
|
+
- That a child cannot observe side effects of the parent's execution
|
|
123
|
+
through shared filesystem state.
|
|
124
|
+
- That the gateway itself enforces credential boundaries. Credential
|
|
125
|
+
enforcement happens at dispatch time in the scheduler; the gateway
|
|
126
|
+
trusts the auth-profile header it receives.
|
|
127
|
+
|
|
128
|
+
## Credential Flow
|
|
129
|
+
|
|
130
|
+
The complete credential flow from operator to child execution:
|
|
131
|
+
|
|
132
|
+
### 1. Operator provisions
|
|
133
|
+
|
|
134
|
+
Credentials enter the system via environment variables, Vault, managed
|
|
135
|
+
identity, or files. The operator controls `SCHEDULER_PROVIDER_PATH` and
|
|
136
|
+
the scheduler's execution environment. The operator may pre-provision
|
|
137
|
+
scoped keys (e.g. `STRIPE_KEY_FULL`, `STRIPE_KEY_PAYMENTS`,
|
|
138
|
+
`STRIPE_KEY_READONLY`) or a single master key that the provider uses to
|
|
139
|
+
mint restricted keys dynamically.
|
|
140
|
+
|
|
141
|
+
### 2. Scheduler loads providers at startup
|
|
142
|
+
|
|
143
|
+
Every `*.js` file in `SCHEDULER_PROVIDER_PATH` is dynamically imported
|
|
144
|
+
and registered by type (identity, authorization, proof-verifier). The
|
|
145
|
+
directory must not be world-writable. This is the root of trust for the
|
|
146
|
+
provider plugin system.
|
|
147
|
+
|
|
148
|
+
### 3. Scheduler resolves credentials at dispatch time
|
|
149
|
+
|
|
150
|
+
For each dispatched job, the scheduler runs the v0.2 evaluation chain:
|
|
151
|
+
|
|
152
|
+
1. `resolveIdentity()` -- provider resolves a credential session or
|
|
153
|
+
structural fallback extracts identity from the job declaration.
|
|
154
|
+
2. Child credential policy enforcement -- `none` strips credentials,
|
|
155
|
+
`inherit` forwards the parent's auth profile, `downscope` calls
|
|
156
|
+
`prepareHandoff()` to create a narrower session, `independent` uses
|
|
157
|
+
the child's own credentials (trust-capped at parent's level).
|
|
158
|
+
3. `evaluateTrust()` -- compares effective trust level against the
|
|
159
|
+
contract floor. Blocks on `deny`, warns on `warn`.
|
|
160
|
+
4. `verifyAuthorizationProof()` -- validates proof if declared. Blocks
|
|
161
|
+
if verification fails or verifier is missing.
|
|
162
|
+
5. `evaluateAuthorization()` -- evaluates inline policy or invokes
|
|
163
|
+
authorization provider. Blocks on `deny`, aborts on `escalate`.
|
|
164
|
+
|
|
165
|
+
### 4. Provider narrows credentials
|
|
166
|
+
|
|
167
|
+
When `child_credential_policy` is `downscope`, the provider's
|
|
168
|
+
`prepareHandoff()` creates a derivative credential with reduced scope.
|
|
169
|
+
With dynamic RAK minting, this means the provider calls the credential
|
|
170
|
+
issuer's API (e.g. Stripe Restricted Keys API) to mint a per-task key
|
|
171
|
+
scoped to exactly the permissions the child declared. The key's lifetime
|
|
172
|
+
is tied to the task's timeout plus a cleanup buffer.
|
|
173
|
+
|
|
174
|
+
Scope hierarchy validation ensures the child's requested scope is
|
|
175
|
+
reachable from the parent's scope via the declared hierarchy.
|
|
176
|
+
Unreachable scopes abort dispatch. If the handoff session's trust level
|
|
177
|
+
would exceed the parent's, dispatch is aborted.
|
|
178
|
+
|
|
179
|
+
### 5. Child receives scoped credentials
|
|
180
|
+
|
|
181
|
+
For shell tasks, credentials are injected as environment variables via
|
|
182
|
+
`provider.materialize()`. For agent tasks, the scheduler either forwards a
|
|
183
|
+
resolved auth profile or, when materialization yields env vars, sends an
|
|
184
|
+
`x-openclaw-env-inject` header that the gateway can apply to the child
|
|
185
|
+
session. Gateway receiver support is still required for env injection, so
|
|
186
|
+
profile forwarding remains the compatibility path until that support is
|
|
187
|
+
present. The child never sees the master key.
|
|
188
|
+
|
|
189
|
+
### 6. Cleanup
|
|
190
|
+
|
|
191
|
+
On task completion (success or failure), `provider.cleanup()` revokes
|
|
192
|
+
dynamically minted keys and removes temporary materialization artifacts.
|
|
193
|
+
Cleanup runs even on error paths.
|
|
194
|
+
|
|
195
|
+
## Trust Boundary Definition
|
|
196
|
+
|
|
197
|
+
The operator controls:
|
|
198
|
+
|
|
199
|
+
- The scheduler's execution environment (host, env vars, process).
|
|
200
|
+
- The provider plugin directory (`SCHEDULER_PROVIDER_PATH`).
|
|
201
|
+
- The gateway connection (`OPENCLAW_GATEWAY_URL`,
|
|
202
|
+
`OPENCLAW_GATEWAY_TOKEN`).
|
|
203
|
+
- The manifest content (via `agentcli compile` + `agentcli apply`).
|
|
204
|
+
|
|
205
|
+
Everything downstream of the operator's control surface narrows only:
|
|
206
|
+
|
|
207
|
+
- A child task MUST NOT receive broader credentials than its parent.
|
|
208
|
+
- A child task MUST NOT run at a higher trust level than its parent.
|
|
209
|
+
- Provider plugins MUST NOT widen scope during handoff.
|
|
210
|
+
- The scheduler MUST NOT auto-escalate trust on retry or timeout.
|
|
211
|
+
|
|
212
|
+
If the provider directory is compromised, the trust model is broken.
|
|
213
|
+
If the scheduler's environment variables are compromised, the trust model
|
|
214
|
+
is broken. These are root-of-trust assumptions, not runtime invariants.
|
|
215
|
+
|
|
216
|
+
## Benefits by Dimension
|
|
217
|
+
|
|
218
|
+
| Benefit | Always present | Only with narrowing |
|
|
219
|
+
|---------|---------------|---------------------|
|
|
220
|
+
| Blast radius (credential) | No | Yes (`downscope` / `independent`) |
|
|
221
|
+
| Blast radius (crash) | Yes | Yes |
|
|
222
|
+
| Attribution | Yes | Yes |
|
|
223
|
+
| Context isolation | Yes | Yes |
|
|
224
|
+
| Lifecycle independence | Yes | Yes |
|
|
225
|
+
| Least privilege (credentials) | No | Yes (`downscope` + dynamic RAK) |
|
|
226
|
+
| Least privilege (trust level) | Partial (contract floor) | Yes |
|
|
227
|
+
| Audit traceability | Yes | Yes |
|
|
228
|
+
| Independent timeout/retry | Yes | Yes |
|
|
229
|
+
|
|
230
|
+
## Credential Strategies
|
|
231
|
+
|
|
232
|
+
### Precreated keys (available now)
|
|
233
|
+
|
|
234
|
+
The operator creates restricted API keys ahead of time and stores them in
|
|
235
|
+
environment variables or Vault. The provider resolves the correct key by
|
|
236
|
+
scope name at dispatch time. No runtime API calls to the credential
|
|
237
|
+
issuer.
|
|
238
|
+
|
|
239
|
+
Trade-offs: simpler setup, works today, but keys are static and rotation
|
|
240
|
+
is operator-managed. Key count grows with the number of distinct scopes.
|
|
241
|
+
|
|
242
|
+
### Dynamic key minting (available now)
|
|
243
|
+
|
|
244
|
+
The provider uses an operator-provisioned master key to mint a per-task
|
|
245
|
+
restricted key via the credential issuer's API at dispatch time. The
|
|
246
|
+
minted key has:
|
|
247
|
+
|
|
248
|
+
- Scope limited to exactly the permissions the task declared.
|
|
249
|
+
- Lifetime tied to the task's timeout plus a cleanup buffer.
|
|
250
|
+
- Automatic revocation in the cleanup phase.
|
|
251
|
+
|
|
252
|
+
The master key itself is operator-provisioned and the minted keys are
|
|
253
|
+
always narrower than the master, never wider. This gives true per-task
|
|
254
|
+
credential lifecycle without operator-managed key inventories.
|
|
255
|
+
|
|
256
|
+
Both strategies use the same manifest syntax. The provider's
|
|
257
|
+
`key_strategy` configuration determines which path runs.
|
|
258
|
+
|
|
259
|
+
## Cross-References
|
|
260
|
+
|
|
261
|
+
- Execution Identity Architecture: `agentcli/docs/execution-identity.md`
|
|
262
|
+
- Gateway contract (session isolation, auth-profile forwarding):
|
|
263
|
+
`docs/gateway-contract.md`
|
|
264
|
+
- Provider plugin system: `provider-registry.js`
|
|
265
|
+
- v0.2 runtime evaluation: `v02-runtime.js`
|
|
266
|
+
- ADR on schedule ownership: `docs/adr-schedule-ownership.md`
|