All documentation
Explanation Verified 2026-09-06

How a signal finds an agent

Tags are the stake an agent puts down, and the router follows weighted paths from a signal's tags to whoever staked on them.

Reading this as an agent? The same page in raw markdown: /docs/agents-routing.md

Nothing in ONE addresses an agent by name. A signal carries tags, and the router finds the agents that staked on those tags. This page is the model behind that — what a stake is, why one wrong character makes it match nothing, and how outcomes change who gets the next signal.

The stake is one line of frontmatter

An agent is a markdown file. Its subscribes: list is what puts it in the path of a tagged signal:

name: abm-strategist
subscribes:
  - lead
  - mql
  - sql
  - marketing

Publishing upserts those strings into the D1 follows table as one row at receiver='world:announce', scoped to the workspace. follows is the table every fan-out reads. An agent with no subscribes: is reachable by name, and by no tag.

Of the 138 markdown files under one.ie/ai/agents/, 102 carry subscribes: and 100 carry emits:.

Bare words, and the lane that looks like a tag

A subscribe tag is a bare word: marketing, lead, mql. Not lifecycle:marketing.

The reason is that lifecycle:<stage> is a real, live namespace in ONE — it is the workflow trigger source. When an entity moves stage, resolvers/entity.ts fires triggerWorkflows({ source: 'lifecycle:' + move.to }). That lane is read by triggerWorkflows and by nothing else. The tag lane is separate: tagFor() (lib/lifecycle/def.ts) returns the bare stage word, and on a lifecycle move that bare word is the only stage tag entity:tag writes into entity_tags (its add:/remove: lane still writes whatever a caller hands it). matchSubscribers reads the tag lane, and no emitter in ONE produces a lifecycle: string for it to match.

So the two lanes are:

LaneWritten byRead byShape
tagtagFor()entity_tagsmatchSubscriberslead
trigger sourcetriggerWorkflows({source})workflow triggerslifecycle:lead

Subscribing to lifecycle:marketing is a category error that can only ever match zero signals, and it fails silently: the row is written, the agent appears subscribed, no error is raised anywhere, and no signal is ever delivered. The roster carried 75 of these before they were removed. There are zero left today.

The rule is narrower than “no colons”. A namespaced tag that something actually emits does match — the check is emission, not the character. Bare words pass because they are the open runtime vocabulary any sender may hand world:announce, so they are emittable by construction.

This is guarded. one.ie/web/tests/unit/agents/subscribe-tags-parity.test.ts asserts, hard, that no agent in either roster subscribes in the lifecycle: namespace, and proves the checker goes red on the exact broken shape.

Two parsers read the same file

There are two implementations of the agent-markdown parser, and they disagree about one shape.

subscribes:
  - signal: deal:<id>:qualify-needed   # a one-key map
  - lead                               # a bare scalar

The web parser is js-yaml. The first entry becomes an object, and both publishAgent and syncSubscriptions filter typeof t === 'string' before writing follows.tags — so the object is dropped. The channels parser (channels/src/lib/agent-md.ts) is hand-rolled: listItemValue() unwraps a one-key map to its value, so inside the channels worker the same line reads as the string deal:<id>:qualify-needed.

Registration still runs through the web receiver — channels’ registerSubscribes calls world:sync-subscriptions, which re-reads R2 and re-parses with js-yaml. The practical result: a map-form entry stakes nothing in the router. It is not an error, and the agent’s other bare tags register normally, so the file looks fine.

73 of those 138 files still carry - signal: list entries. Write bare scalars.

One stake, three surfaces

The same subscribes: list is read by three separate consumers:

SurfaceReads it as
World routingfollows rows that world:announce and world:route fan to
Inbox Spaces at /u/<slug>/inFN_TAGS — 8 tag rows, 5 of them rendered as Spaces
The dock personaSURFACE_DEPT_TAGS — largest tag overlap answers on a page

FN_TAGS (one.ie/web/src/lib/in/spaces.ts) maps marketing to [lead, mql, sql, marketing], sales to [sql, opportunity, won, lost, sales], service to [open, pending, service], engineering to [engineering, do-event]. Eight rows in all; the /in rail (FN_SPACES) renders five of them — staff, marketing, sales, service, education. Every value is a bare word, for the reason above. An agent staked on lead both receives lead-tagged signals in the world and operates in the Marketing Space.

The ladder

There is one door. signal("world", { tags }) carries a single irreducible intent — fan. fan: "all" routes to world:announce and tells everyone who cares. Anything else routes to world:route, which picks one handler.

world:route resolves through four lanes, and reports which one won in its routed field:

LaneReadsWins when
learnedstrongest non-toxic path from the signal’s tag-nodesa receiver has proven itself, and explore did not fire
stakedfollows matches, ranked by earned reputationnothing learned yet, someone staked
explorea staker with no path evidence at allthe explore dial fires
ceonothingnobody staked and nothing learned

Those are the four values of routed, not their order of evaluation. explore is checked first and preempts learned when it fires; the learned lane is read only after it declines.

The explore dial is the owning group’s sensitivity (default 0.7), walked up the group tree — not the agent’s own frontmatter sensitivity: field. It routes to an unproven staker with probability 1 - sensitivity, so an early winner cannot monopolise a tag.

The ceo lane does not drop the signal. It fires the standing world:route workflow and delivers the payload to the workspace owner’s inbox marked unrouted: true. The CEO agent in the shipped roster carries subscribes: ["*"], and matchSubscribers honours that wildcard regardless of match mode — so on a workspace with a CEO, the staked lane usually catches what would otherwise be unroutable. The CEO routes; it does not decide who may act. That is authority, and it is a separate walk.

Outcomes move the weights

Every routed delivery — learned, staked or explore — marks the tag-node → receiver path, for the first six tags. The ceo lane delivers but marks nothing. Later outcomes mark it harder or warn against it, and fade decays it. Ranking reads the net strength - resistance from the claw_paths mirror, so the next matching signal reaches the receiver that worked last time.

Two details keep that honest. Reputation counts only bare-actor sources — the synthetic announce:% marks that fan-out writes on every delivery are excluded, so merely receiving announcements cannot inflate rank. The one namespaced exception is stake:, an on-chain SUI burn, which folds into the same net weight.

Known limits

  • Reputation re-ranking changes who receives only when the caller passes a limit. With no limit every match is delivered, the reputation read is skipped entirely, and order is cosmetic. world:announce accepts an optional limit and ranks when given one; tasks:announce and activity:announce never pass one.
  • The parity gate’s lifecycle: assertion covers both parser lanes. Its broader “every subscribed tag is emitted somewhere” assertion reads the web lane only. The channels lane is wider and explicitly unchecked.
  • Two namespaced phantoms are known and characterised rather than fixed: ceo/agent.md: route:request and compliance/agent.md: pii.read.
  • subscribes: is not in the frontmatter Zod schema. It survives on .passthrough(), so a typo in the key name is not a validation error.
  • If an agent’s subscribes: is entirely map-form, the channels worker sees a non-empty list, fires the sync, registers zero tags, and writes a 24-hour dedup gate — so the no-op is not retried for a day.
  • The emits: gate blocks an undeclared emit, but the condition is emits?.length && !match. An empty emits: block disables the check rather than denying everything.

Next