Skip to content

feat(mocactl): workloads create/get/delete and spawn --workload - #520

Merged
evaline-ju merged 1 commit into
rossoctl:mainfrom
moonlight16:feat/mocactl-workloads
Oct 14, 2026
Merged

evaline-ju merged 1 commit into
rossoctl:mainfrom
moonlight16:feat/mocactl-workloads

Conversation

@moonlight16

Copy link
Copy Markdown
Member

Part of the work split out of #455 (the original context-delegation PR). Implements the client side agreed in #495 (one CLI for delegating subagent tasks) and the short-term option in #517 (seed sandbox files before a leaf runs).

What this adds

  • mocactl workloads create --context FILE [--name N] [--json]: creates a workload, uploads the context file straight to Context Service with the one-time grant, and activates the uploaded revision. Prints the workload ID. If the grant has expired it gets a fresh one and retries once; if anything fails after the workload exists, it deletes it so the quota is not used up.
  • mocactl workloads get ID and mocactl workloads delete ID, both with --json.
  • mocactl spawn "prompt" --kind prompt --workload ID: the subagent runs with that workload's files (feat: run tasks on a workload's context in shared sandboxes #494, run tasks on a workload's context). The workload is recorded in the subagent ledger and shown by subagents --json.

The file is made with contextctl ctx export; mocactl does not call contextctl. In the CLI the uploaded thing is called a context, since "bundle" already means config bundles in mocactl.

For demos that seed fixtures with kubectl cp: contextctl ctx export ./repo -o repo.context, then mocactl workloads create --context repo.context, then mocactl spawn --workload <id> .... No cluster access is needed, and MOCA puts the files on whichever sandbox a task lands on. Workload files are read-only today; a writable per-run copy is the next PR.

🤖 Generated with Claude Code

mocactl workloads create --context FILE creates a workload, streams the context file to Context Service with the one-time grant, and activates the uploaded revision. mocactl spawn --workload ID runs the subagent on that workload's context. Replaces kubectl cp seeding of sandbox fixtures (rossoctl#517).

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Jeremy Cohn <Jeremy.Cohn@ibm.com>

@ingpaolodettori-dev ingpaolodettori-dev left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The client matches main's /workloads routes, grant fields, name rule, error codes and media type. spawn --workload goes over sync POST /v1/runs with the session token, the only route that resolves workloads. The create rollback and the single fresh-grant retry look correct, and the tests are good (retry, rollback, rollback failure, size limit, unsafe URL, real loopback stream). CI is green. Two non-blocking suggestions and one nit are inline.

try {
created = await client.create(opts.name ? { name: opts.name } : {}, signal);
} catch (err) {
if (aborted(signal)) return 130;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: A Ctrl-C or network drop after POST /workloads was sent can leave a workload the server created that the client never learned the id of. There's no list route, so without --name it can't be found, and it holds one of the per-user quota slots (5 by default). Consider printing a hint here, or generating the name client-side (wl-<random>) when --name is omitted so the id is always known before the request.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Addressed in #532 (writable per-run copy, stacked on this PR).

io.err('--workload needs --kind prompt: the harness runs only prompts on a workload');
return 2;
}
if (!validWorkloadId(opts.workloadId)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: spawn --workload doesn't check the workload before creating a session and forking the worker, so a typo or a not-yet-activated workload only shows up later as a failed row (workload_not_found / workload_not_ready). A cheap GET /workloads/{id} preflight (exit 1 unless status === 'ready') would fail fast. Optional.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Addressed in #532 (writable per-run copy, stacked on this PR).

method: 'PUT',
headers: {
authorization: `Bearer ${grant.token}`,
'content-type': CONTEXT_MEDIA_TYPE,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: asGrant keeps grant.contentType, but the PUT always sends CONTEXT_MEDIA_TYPE. They're equal today (the server enforces it), but sending grant.contentType would make the field meaningful. Otherwise drop it from UploadGrant.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Addressed in #532 (writable per-run copy, stacked on this PR).

@evaline-ju evaline-ju left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independently probed uploadUrlProblem on hostile inputs — correctly refuses AWS metadata, confusable 127.0.0.1.evil.com, file:/ftp: schemes. validWorkloadId correctly refuses path traversal, newline injection, 51-char ids. encodeURIComponent on top of the regex is defense in depth.

Author: moonlight16 (MEMBER) · CI green

@evaline-ju
evaline-ju merged commit 67366cf into rossoctl:main Oct 14, 2026
15 checks passed
moonlight16 added a commit to moonlight16/moca that referenced this pull request Oct 14, 2026
A workload run may set contextWritable: true (mocactl spawn --workload ID --writable). That run gets its own writable copy of the workload's files, discarded when the run ends; the shared cache stays read-only. The cache now keeps exec bits while removing write permission.

Also addresses rossoctl#520 review: workloads create generates the name client-side, spawn --workload checks the workload is ready first, and the upload sends the grant's content type.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Jeremy Cohn <Jeremy.Cohn@ibm.com>
moonlight16 added a commit to moonlight16/moca that referenced this pull request Oct 14, 2026
A workload run may set contextWritable: true (mocactl spawn --workload ID --writable). That run gets its own writable copy of the workload's files, discarded when the run ends; the shared cache stays read-only. The cache now keeps exec bits while removing write permission.

Also addresses rossoctl#520 review: workloads create generates the name client-side, spawn --workload checks the workload is ready first, and the upload sends the grant's content type.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Jeremy Cohn <Jeremy.Cohn@ibm.com>
@evaline-ju evaline-ju linked an issue Oct 15, 2026 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

No way to seed sandbox files before a leaf starts running

3 participants