RFC-005 — Agent affinity front-end syntax¶
Status: Draft (v0.9 spec-freeze prep).
Tracks amendment: A102 (affinity hints — runtime API frozen, front-
end syntax agent X with affinity = sticky reserved-but-not-parsed
in v1.0).
Target release: v1.1.
Owner: unassigned — design owner needed before promotion.
Implementation Status¶
NOT YET SHIPPED. Forward-looking RFC. The A102 contract —
runtime Affinity::Sticky / Affinity::Elastic API only — remains
the v1.0 surface. Front-end syntax agent X with affinity = sticky
is reserved-but-not-parsed.
Adjacent v0.13..v0.23 work:
- v0.22 — Per-message work-stealing (Tier 5) replaced the v0.10 affinity-hint scheduler with true crossbeam-deque per-worker queues
- NUMA-locality steal ordering. The
Affinityruntime API is unchanged; the underlying scheduler is now stronger. - v0.20 — Cluster
PlacementPolicytrait + 3 bundled policies (RFC-006). These are cluster-scope placement and complementary to RFC-005 (which is worker-scope within a node), so RFC-005 remains substantive.
The v1.1 surface is unchanged:
agent X(ctor_args, ...): MyAgent with affinity = sticky
agent Y(ctor_args, ...): MyAgent with affinity = elastic
agent Z(ctor_args, ...): MyAgent // implicit elastic
The window opened 2026-05-26 (close 2026-06-09, 14-day track — shortest) is substantive and closes first.
Cross-references:
v1.0-rc.md§25.2 — agent affinity (A102).RFC_DASHBOARD.md— live window status.
Summary¶
Promote the v0.6 Affinity runtime API (Sticky / Elastic) to a
first-class declaration in agent spawn syntax. Allow the developer
to opt in or out of scheduler migration at the spawn site without
plumbing the affinity through several layers of helper code.
Grammar:
agent X(ctor_args, ...): MyAgent with affinity = sticky
agent Y(ctor_args, ...): MyAgent with affinity = elastic // explicit default
agent Z(ctor_args, ...): MyAgent // implicit elastic
Optional additional with clauses (already reserved by the v0.5
sandbox manifest grammar) compose:
Motivation¶
The v0.6 multi-worker scheduler (A101) ships a RuntimeBuilder::
spawn_agent_with_affinity API that lets a host pin agents to worker 0
or hand them to the elastic load monitor. But this is a host-side
API: a developer writing Mighty source has no way to express the
affinity at the declaration site. Three consequences:
- Helper-fn proliferation. Real apps wrap every agent spawn in a
fn make_metrics_agent(...) { runtime.spawn_with_affinity(Sticky, ...) }shim. Source-level intent is hidden behind the helper. - No static analysis. A linter can't warn "agent declared as
with affinity = stickybut spawned from inside an elastic supervisor tree" because the affinity isn't visible in source. - Inconsistent with sandbox / budget syntax. Sandboxes already
use the
with { entries }form. Affinity is a similar opt-in but v1.0 forces it through a different surface.
Detailed design¶
Surface syntax¶
AGENT_DECL = "agent" IDENT TYPE_ARGS? "(" ARGS ")" (":" RETURN_TY)?
("with" AFFINITY_CLAUSE)? BLOCK ;
AFFINITY_CLAUSE = AFFINITY_ENTRY ("," AFFINITY_ENTRY)* ;
AFFINITY_ENTRY = "affinity" "=" AFFINITY_VAL
| "worker" "=" INT_LITERAL
| "priority" "=" PRIORITY_VAL ;
AFFINITY_VAL = "sticky" | "elastic" ;
PRIORITY_VAL = "high" | "normal" | "low" ; // reserved for RFC-005.b
The with clause is optional. Omitting it is equivalent to
with affinity = elastic (the v0.6 default).
Multiple with entries are comma-separated:
Spawn lowering¶
agent declarations are syntactic sugar; HIR lowering already
converts them to Spawn rvalues. v1.1 extends HirRvalue::Spawn:
HirRvalue::Spawn {
agent_ty: AdtId,
ctor_args: Vec<ExprId>,
affinity: Option<Affinity>, // RFC-005: from `with affinity =`
pinned_worker: Option<u16>, // RFC-005: from `with worker =`
}
The MtyIR layer threads these through to Stmt::Spawn { ..., affinity,
pinned_worker }, which the runtime ABI bridge passes into
Scheduler::spawn_with_affinity (already wired in v0.6).
Default behaviour¶
- No
withclause →affinity = elastic,pinned_worker = None. with affinity = stickywithout explicit worker → pinned to worker 0 at spawn (existing v0.6 default forAffinity::Sticky).with affinity = sticky, worker = N→ pinned to worker N. The type checker accepts anyN : u16; runtime traps withMT5021 invalid_worker_pinifN >= scheduler.worker_count().with affinity = elastic, worker = N→MT2032 affinity_pin_conflictat type-check time (a pin contradicts elasticity).
Validation rules¶
| Rule | Diagnostic |
|---|---|
worker = N with N >= worker_count at spawn |
MT5021 (runtime) |
affinity = elastic + worker = N |
MT2032 (typeck) |
Duplicate affinity = or worker = entries in same with |
MT2033 duplicate_with_entry |
Unknown affinity value (e.g. affinity = pinned) |
MT2034 unknown_affinity_value |
Supervisor inheritance¶
If an agent declared with affinity = sticky is restarted by a
supervisor (A42 restart window), the restarted instance inherits the
same affinity unless the supervisor explicitly overrides:
supervisor App {
agent metrics(): Metrics with affinity = sticky // inherits on restart
agent worker(): Worker // elastic
}
The supervisor declaration syntax already supports with clauses for
restart policies; affinity slots in alongside them.
Editor / LSP integration¶
- Semantic-token classifier (A74) gains an
affinitykeyword token type so editors highlight it correctly. - Hover on an agent declaration shows the resolved affinity (the default-elastic case explicitly noted).
mty docHTML generator surfaces the affinity in the agent's card.
Drawbacks¶
- Grammar growth. Adds two new keywords-in-context (
affinity,worker) inside awithclause. They are not reserved identifiers globally — keyword-tolerant.method/.fieldrule (A3) ensuresobj.affinitystill parses as a field access. But the spec's keyword set grows. - Default-elastic ambiguity. Some readers may expect
withto always be explicit; defaulting to elastic is a convenience but a surprise. Mitigated bymty docalways showing the resolved value. - Forward compatibility. A future affinity model that's neither
sticky nor elastic (e.g. "cohort": pin to a worker subset) would
need a fresh keyword. Reservations:
cohort = NAMEandaffinity = pinnedare flagged in unresolved questions below.
Alternatives considered¶
- Attribute syntax (
#[affinity(sticky)] agent X(...)). More uniform with#[derive(...)]but heavier-weight; rejected as over-decorated for what's really a spawn-site config. - Keep host-API only. Defeats motivation.
- Implicit affinity from agent type (a marker trait
StickyAgent). Forces the policy into the type system but prevents an app from spawning the same agent type with different policies in different contexts; rejected. - Numeric affinity weights (
affinity = 0.7). More expressive but the v0.6 scheduler doesn't honour weights; would need full load-balancer redesign. Reconsider in RFC-005.b.
Unresolved questions¶
- Should
worker = Npermit a runtime expression (e.g.worker = job_id % WORKER_COUNT)? v1.1 starts with literal-only for static analysis; relax in v1.2 if real usage demands. - Cohort affinity: should the v1.1 grammar already reserve
cohort = NAMEso we don't break syntax later? Recommendation: yes, reserve the keyword now, leave it asMT2035 unsupported_in_v1_1until v1.2 ships the cohort scheduler. - Interaction with deterministic mode (A39): deterministic mode pins
worker count to 1.
with worker = 2under deterministic mode isMT2036 affinity_conflicts_deterministic. Or do we silently coerce to worker 0? Initial answer: hard error.
Adoption plan¶
- v1.1-alpha.1: parser accepts the
withclause; HIR threads affinity + pinned_worker; runtime continues to use the v0.6 API.MT2032..MT2036diagnostics ship. - v1.1-alpha.2: LSP semantic-token + hover support.
- v1.1-beta: supervisor inheritance lands; conformance corpus
adds an
affinity/category. - v1.1.0: A102 reclassified FROZEN;
RuntimeBuilder::spawn_agentbecomes a thin wrapper over the new declaration form.
A 14-day public comment window opens with v1.1-alpha.1 (shorter than RFC-001/002 because the surface area is narrower).