Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 50 additions & 11 deletions docs/concepts/jobs.md
Original file line number Diff line number Diff line change
Expand Up @@ -689,14 +689,14 @@ cache lookup when you already have a proxy in scope.
Symmetric to `post_event`: callers that hold a `job_id` but no
`JobProxy` reference can drive the rest of the post-submit lifecycle
through module-level facades that share the same registry-URL
resolution + cached-proxy machinery. The Python surface lands in
v2.2; TypeScript and Java parity follows in separate PRs.
resolution + cached-proxy machinery. Python and TypeScript surfaces
land in v2.2; Java parity follows in a separate PR.

| Operation | Facade (Python) | Returns |
| ------------------------ | -------------------------------------------------- | ----------------------------- |
| Cancel a running job | `await mesh.jobs.cancel(job_id, reason=None)` | `None` |
| Read latest job state | `await mesh.jobs.status(job_id)` | `dict` (registry `Job` row) |
| Wait for terminal state | `await mesh.jobs.wait(job_id, timeout_secs=None)` | `result` payload on success |
| Operation | Python | TypeScript | Returns |
| ------------------------ | -------------------------------------------------- | ------------------------------------------------ | ----------------------------- |
| Cancel a running job | `await mesh.jobs.cancel(job_id, reason=None)` | `await mesh.jobs.cancel(jobId, reason?)` | `None` / `void` |
| Read latest job state | `await mesh.jobs.status(job_id)` | `await mesh.jobs.status(jobId)` | `dict` / `JobStatus` |
| Wait for terminal state | `await mesh.jobs.wait(job_id, timeout_secs=None)` | `await mesh.jobs.wait(jobId, timeoutSecs?)` | `result` payload on success |

<!-- markdownlint-disable MD046 -->
=== "Python"
Expand Down Expand Up @@ -726,13 +726,52 @@ v2.2; TypeScript and Java parity follows in separate PRs.
result = await mesh.jobs.wait(job_id, timeout_secs=300.0)
return {"result": result}
```

=== "TypeScript"

```typescript
agent.addTool({
name: "abort_workflow",
capability: "abort_workflow",
parameters: z.object({ jobId: z.string(), reason: z.string() }),
execute: async ({ jobId, reason }) => {
await mesh.jobs.cancel(jobId, reason);
return { cancelled: jobId };
},
});

agent.addTool({
name: "check_progress",
capability: "check_progress",
parameters: z.object({ jobId: z.string() }),
execute: async ({ jobId }) => {
const snapshot = await mesh.jobs.status(jobId);
return {
status: snapshot.status,
progress: snapshot.progress,
message: snapshot.progress_message,
};
},
});

agent.addTool({
name: "run_to_completion",
capability: "run_to_completion",
parameters: z.object({ jobId: z.string() }),
execute: async ({ jobId }) => {
const result = await mesh.jobs.wait(jobId, 300);
return { result };
},
});
```
<!-- markdownlint-enable MD046 -->

`cancel` is idempotent — calling it on an already-terminal job returns
ok. `wait` raises `TimeoutError` on `timeout_secs` expiry; pass `None`
(default) to wait until the job reaches a terminal state. `status`
returns the same shape `JobProxy.status()` exposes (registry `Job`
row, field-for-field).
ok. `wait` raises `TimeoutError` (Python) or rejects with an `Error`
whose message starts with `"timeout:"` (TypeScript) on `timeout_secs`
expiry; pass `None` / omit `timeoutSecs` to wait until the job reaches
a terminal state. `status` returns the same shape `JobProxy.status()`
exposes (registry `Job` row, field-for-field).

If the calling code already holds a `JobProxy`, the same surface is
on the proxy directly: `proxy.cancel(reason)`, `proxy.status()`,
Expand Down
23 changes: 23 additions & 0 deletions src/core/cli/man/content/jobs_typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,29 @@ cap 256; tune via `MCP_MESH_JOBPROXY_CACHE_MAX`). If the calling code
already holds a `JobProxy`, use `proxy.sendEvent(eventType, payload)`
directly — same wire shape, skip the helper.

**Lifecycle facades by `jobId`.** Same DDDI-clean pattern as
`postEvent` — module-level helpers that take a `jobId` and dispatch
through the shared proxy cache, for callers that don't hold a
`JobProxy` reference:

```typescript
// Cancel a running job (idempotent — already-terminal jobs return ok)
await mesh.jobs.cancel(jobId, "user requested abort");

// Read latest job state (JobStatus — registry Job row, field-for-field)
const snapshot = await mesh.jobs.status(jobId);
// snapshot.status ∈ "working" | "input_required" | "completed" | "failed" | "cancelled"

// Wait for terminal state and return the result payload
const result = await mesh.jobs.wait(jobId, 300);
```

`wait` rejects with an `Error` whose message starts with `"timeout:"`
on `timeoutSecs` expiry; omit `timeoutSecs` (or pass `undefined`) to
wait until the job reaches a terminal state. All three reject with
`JobNotFoundError` if the registry has reaped the job; `cancel` also
re-classifies a conflict response into `JobTerminalError`.

**Typed errors** (both extend `Error`):

- `JobNotFoundError` — job swept or id typo
Expand Down
Loading
Loading