Runs and events
Everything on Manykind starts with an event landing in a vault scope. The platform turns events into runs: a Job per agent and stream, with one Task per event. A workflow run is a Job too.
Events
An event is one typed message published into a scope. It is the only way work enters the platform.
| Field | Meaning |
|---|---|
type |
What the event is, e.g. user_message, workflow.start. |
context |
The stream it belongs to. Events with the same context form one stream, and one Job. |
payload |
The body. |
role |
source, user, or agent. |
from |
Who published it, when the vault knows: a person’s key or an agent id. |
target |
The agent the event is for, <agentServicePubkey>:<alias>. |
correlationId, metadata, parents |
Correlation, free-form metadata, and the ids of the events that caused this one. |
timestamp |
Set by the vault when absent. |
Where events come from
| Source | How |
|---|---|
| A person | Runs a workflow in ROC, or acts in a widget. |
| Your app | vault.scope(name).publish({ type, context, payload }) with the Vault SDK. |
| A schedule | The vault fires the agent’s declared schedules (cron). See Triggers. |
| A webhook | An outside system calls the workflow’s webhook URL with a key. See Triggers. |
| A connector | A Slack or Telegram message arrives through a vault connector. |
| An agent | ctx.vault.publish(type, payload, { target }) from a running agent. See Event streams. |
An event reaches an agent only through an active connection on that scope.
Without target, an agent’s publish goes to every agent subscribed in the
scope; name the recipient to hand work to one agent. An LLM agent receives
another agent’s output only when it is targeted.
Jobs and Tasks
| Noun | What it is |
|---|---|
| Job | A long-lived run, keyed by vault, agent, scope, and context. The record of a workflow run is a Job. |
| Task | One event’s worth of work inside a Job. |
When an event arrives, the platform finds or opens the Job for that agent and stream, and adds a Task. By default a Job runs one Task at a time, in order.
- Job states:
queued,throttled,processing,active,completed,failed,cancelled,dead. - Task states:
pending,dispatched,running,completed,failed. A Task carries anattemptcount and, on failure, anerrorwithcode,message, andretryable. - Idle timeout. A Job with no new activity for the agent’s
idleTimeout(default30m;"0s"disables it) ends with reasonidle_timeout. An agent can end its own Job withctx.close(reason). - Initiator. A Job records the person who started it. A Job started by a schedule or a connector has none.
Read runs from outside with the Vault SDK: vault.jobs.list(),
vault.jobs.get(jobId).tasks.list(), and .tasks.logs(taskId) for the log lines
your code wrote. In ROC, open a workflow to see its runs. See
Monitor runs.
How a code agent runs
A code agent is a set of
engagements. For a new Job the platform selects one engagement (conditions,
then priority, then weight) and pins it for the Job’s life. Each Task calls the
pinned engagement’s @OnEvent handler for the event’s type.
ctx.params is resolved per Job: manifest defaults, then deployment values, then
the engagement’s own. ctx.settings holds the vault owner’s values from connect
time.
How a workflow runs
A workflow is dispatched exactly like a code agent; its bundle is the platform’s workflow runner. A trigger event opens the run, and the runner walks the graph: each step’s output feeds the next, edges with conditions choose the path, and steps that wait (people, agents, connector actions) resume the run when their answer arrives as an event.
The sandbox
Code agents, and the runner itself, run in a JavaScript sandbox (a V8 isolate) with no Node.js runtime.
| Available | Not available |
|---|---|
fetch |
process, Buffer |
globalThis.crypto (randomUUID(), subtle.*) |
node:* built-in modules |
TextEncoder, TextDecoder, typed arrays |
Database and socket clients |
console.* (captured as task logs) |
A file system |
cef build refuses an agent entry file that imports a banned module: fs,
path, net, http, https, crypto, buffer, stream, os, process,
child_process, and other Node built-ins (with or without the node: prefix),
plus packages such as pg, redis, ws, axios, and node-fetch. The build
checks the entry file only; a Node import deeper in your dependency graph fails
at runtime instead. Use Web globals: globalThis.crypto.randomUUID() for ids,
fetch for HTTP, Uint8Array and TextEncoder for bytes.
Three rules follow:
- Nothing in memory survives. The same isolate may not handle the next event. Keep state in a cubby or the Memory Bank, and read it at the start of each handler.
- A handler can run again. A failed Task is retried. Make every write
idempotent: key rows with
PRIMARY KEYorUNIQUEand use upserts. - No secrets in the bundle. Your bundle is published to content-addressed storage and is readable by whoever can resolve it. To reach an outside system with credentials, use a vault connector, whose secrets are sealed in the vault.
Limits
| Limit | Value | When you hit it |
|---|---|---|
| Publish request body | 1 MiB | 413 PAYLOAD_TOO_LARGE. Store large data as a vault object and publish its path. |
| Events per publish call | 100 | 413 PAYLOAD_TOO_LARGE. Publish in batches of 100 or fewer. |
| Event retention per stream | 7 days | Older events are dropped. Keep what you need later in a cubby or the Memory Bank. |
| Finished Jobs and their Tasks | Kept 7 days | Older runs are deleted. |