Tag Vocabulary
Capabilities, priority, and tags describe tasks and agents in @nolag/agents. This page is the reference for each, and for which of them the SDK uses for routing (only capabilities) versus carries for your code to read (priority and tags).
Capabilities
A capability is a single string naming something a worker can do. Workers advertise a list of them through presence; a task names exactly one. dispatch() refuses to publish unless a connected agent advertises that capability, and onTask() hands a worker only the tasks whose capability is in its list.
import { NoLag } from "@nolag/js-sdk";
import { NoLagAgents, Handoff } from "@nolag/agents";
// Worker advertises capabilities through presence
const client = NoLag(WORKER_TOKEN);
const agents = new NoLagAgents({
client,
appName: APP_SLUG,
agentId: "analyst-1",
presence: { name: "analyst-1", role: "agent", capabilities: ["research", "finance", "summarization"] },
});
await client.connect();
await agents.ready();
const room = agents.room("default-workflow");
const handoff = new Handoff(room);
// ...and handles the ones it wants
handoff.onTask(["research", "finance"], (task, respond) => {
respond("success", { handled: task.capability });
});
// Orchestrator dispatches for one capability
await handoff.dispatch("finance", { ticker: "ACME" });
// Who can do what right now, from presence
console.log(room.findAgents("finance").map((a) => a.name));
console.log(room.getAvailableCapabilities());A task has one capability, so there is no "worker must have both" matching. If a job needs two skills, either name a combined capability (analyze-earnings) or dispatch two tasks.
Common capability names
| Capability | Description |
|---|---|
research | Web research, data gathering, source verification |
drafting | Content creation, report writing, email composition |
review | Quality review, fact-checking, compliance checks |
summarization | Text summarization, key point extraction |
finance | Financial analysis, calculations, reporting |
code | Code generation, review, debugging |
translation | Language translation, localization |
customer-support | User queries, issue resolution, FAQ |
These are conventions, not enforced values. Use any string that makes sense for your domain.
Priority
priority is a field on the task envelope. It has four values, defaults to medium, and is delivered to the worker as-is. The SDK does not queue, reorder, or preempt tasks by priority; a worker that cares reads task.priority and decides what to do.
import { Handoff } from "@nolag/agents";
const handoff = new Handoff(room);
// Dispatch with a priority
await handoff.dispatch("drafting", { report: "q3" }, { priority: "critical" });
// A worker reads it and chooses its own policy
handoff.onTask(["drafting"], async (task, respond) => {
if (task.priority === "low" && busy()) {
respond("error", {}, { code: "busy", message: "deferring low-priority work" });
return;
}
respond("success", { draft: await draft(task.payload) });
});Priority values
| Priority | Suggested meaning |
|---|---|
critical | Drop other work for this |
high | Ahead of routine work |
medium | Default |
low | Background work |
The meanings are suggestions for your worker code; the transport treats all four identically.
Task outcomes
There are no automatic status tags or lifecycle events. A task has two observable moments: the dispatcher publishes it, and the worker calls respond(). The result envelope carries the outcome:
result.status | Meaning |
|---|---|
success | Worker finished; payload holds the output |
error | Worker reported a failure; error.code and error.message say why |
partial | Worker returned an incomplete result |
A task nobody answers has no status at all: with waitForResult: true the dispatch promise rejects when the timeout passes. Anything in between (started, claimed, retrying) exists only if your worker emits it with Observe.
Custom tags
Tasks carry an optional tags: string[]. The convention is prefix:value, and the tag() helper builds one; TAG_PREFIX and TAG_FLAGS hold the standard prefixes and flags. Agents carry free-form metadata on their presence instead. Both are delivered as data for your code to read; neither is consulted by dispatch() or onTask().
import { NoLag } from "@nolag/js-sdk";
import { NoLagAgents, Handoff, tag, TAG_PREFIX, TAG_FLAGS } from "@nolag/agents";
// Tags on a task
const handoff = new Handoff(room);
await handoff.dispatch(
"review",
{ documentId: "doc-42" },
{
tags: [
tag(TAG_PREFIX.TENANT, "acme-corp"), // "tenant:acme-corp"
tag("department", "legal"), // "department:legal"
tag("region", "eu-west"),
TAG_FLAGS.REQUIRES_AUDIT, // "requires_audit"
],
},
);
// A worker reads them
handoff.onTask(["review"], (task, respond) => {
const audited = task.tags?.includes(TAG_FLAGS.REQUIRES_AUDIT) ?? false;
respond("success", { audited });
});
// Metadata on an agent, advertised through presence
const client = NoLag(REVIEWER_TOKEN);
const reviewer = new NoLagAgents({
client,
appName: APP_SLUG,
agentId: "reviewer-eu-1",
presence: {
name: "reviewer-eu-1",
role: "agent",
capabilities: ["review"],
metadata: { department: "legal", region: "eu-west", clearance: "confidential" },
},
});Custom tag patterns
| Pattern | Example | Use case |
|---|---|---|
| Department | department:legal | Let workers accept or decline by department |
| Region affinity | region:eu-west | Keep data processing within a geographic region |
| Tenant | tenant:acme-corp | Label work in multi-tenant agent systems (for hard isolation use Access Scopes) |
| Model selection | model:gpt-4o | Ask for a specific LLM |
| Flags | requires_human, requires_audit | Mark work that needs a human step or an audit entry |
Server-side routing
By default every worker in a room receives every task and onTask discards the ones it cannot handle. For a busy room you can move that selection to the broker with subscription filters: a worker joins the room with the filter values it wants, and the dispatcher publishes each task tagged with one. Handoff.dispatch() publishes without a filter, so the dispatcher uses room.publishTask() directly and reads results from the room's result event.
import { NoLag } from "@nolag/js-sdk";
import { NoLagAgents, Handoff } from "@nolag/agents";
const client = NoLag(WORKER_TOKEN);
const agents = new NoLagAgents({
client,
appName: APP_SLUG,
agentId: "researcher-1",
presence: { name: "researcher-1", role: "agent", capabilities: ["research", "finance"] },
});
await client.connect();
await agents.ready();
// The broker now sends this worker only tasks published with one of these filter values
const room = agents.room("default-workflow", { filters: ["research", "finance"] });
new Handoff(room).onTask(["research", "finance"], (task, respond) => {
respond("success", { done: task.taskId });
});Two rules from the filters model apply here. A worker that subscribed without filters still receives filtered publishes, so unfiltered and filtered workers can coexist. With load balancing on, they should not: the broker treats the filtered and unfiltered subscriptions as separate share groups and a mixed pool delivers each task twice, so keep every worker in a group on the same filter shape.