Skip to content

feat(persona-kit): input() + provider scoping fields — typed, deploy-time input references (kill the ${SLACK_CHANNEL} magic string) #219

Description

@khaliqgant

Problem

Scoping a trigger to a deploy-time input today means hand-writing a magic string:

slack: [{ on: 'message.created', paths: ['/slack/channels/${SLACK_CHANNEL}/**'] }]

Three footguns, all hit in practice:

  1. Looks like a JS template literal but isn't. It's in single quotes, so ${SLACK_CHANNEL} is a literal the cloud substitutes at deploy (the #1999 interpolation), not JS. "Fixing" it to backticks makes JS interpolate it to undefined → silently broken. A quote character changes behavior.
  2. No link to the declared input. Nothing connects the string to inputs: { SLACK_CHANNEL: … }. Typos surface as a deploy error at best, a silent miss at worst.
  3. Raw relayfile glob. Authors must know /slack/channels/<id>/**, the /**-vs-mid-* rules, provider-root drop, etc.

Proposed authoring surface

input(name) — a branded deploy-time reference (returns DeployRef, not string; renders to ${NAME}):

import { defineAgent, input } from '@agentworkforce/runtime';

First-class provider scoping fields on triggers (headline):

slack:  [{ on: 'message.created', channel: input('SLACK_CHANNEL') }]
github: [{ on: 'issues.opened',   repo:    input('REPO') }]

persona-kit expands channel/slack/channels/<resolved>/**, repo/github/repos/<resolved>/**. No glob, no /**, no ${}. Fields accept DeployRef | string (a literal id works too).

Escape hatch for raw paths (tagged template):

paths: [rfPath`/slack/channels/${input('SLACK_CHANNEL')}/**`]

rfPath substitutes the DeployRef${NAME} itself, so the backtick footgun is gone.

Types

declare const DEPLOY_REF: unique symbol;
export interface DeployRef {
  readonly [DEPLOY_REF]: true;
  readonly inputName: string;
  toString(): string; // "${inputName}"
}
export function input(name: string): DeployRef;
export function rfPath(s: TemplateStringsArray, ...refs: (DeployRef | string)[]): string;

Type safety — compile-time validation

Inputs live in persona.ts, refs in agent.ts, so full editor types would mean threading the inputs type across files (invasive). Instead, validate at parse/compile:

  • Collect every DeployRef.inputName used in trigger fields/paths.
  • Cross-check vs declared persona.inputs.
  • Error loudly on unknowns: agent.ts references input 'SLAK_CHANNEL' not declared in persona.inputs (did you mean 'SLACK_CHANNEL'?).

Deterministic, pre-deploy. (A typed input<keyof Inputs> variant is a possible follow-up if a persona exports its input keys.)

Compile-down (no cloud change)

  • input('X').toString()'${X}'.
  • channel/repo/rfPath expand to the existing paths: ['…${X}…'] form during parse.
  • The cloud's existing interpolateTriggerPathInputs (#1999, AgentWorkforce/cloud) resolves ${X} at deploy unchanged.

Back-compat

Raw paths: ['…${SLACK_CHANNEL}…'] keeps working; add a compile warning pointing at input()/channel:. No hard break.

Implementation (persona-kit + runtime only)

File Change
packages/persona-kit/src/input.ts (new) input(), DeployRef, rfPath
packages/persona-kit/src/define.ts extend TypedTrigger<P> with provider fields (channel?/repo?: DeployRef | string)
packages/persona-kit/src/parse.ts expand fields → paths; render refs → ${name}; collect referenced names
packages/persona-kit/src/define.ts / compile cross-check referenced names vs declared inputs → loud error
packages/runtime/src/index.ts re-export input, rfPath

No AgentWorkforce/cloud change.

v1 scope / decisions

  • Fields: channel (slack) + repo (github) only; add others on demand.
  • Validation: compile-time (above), not editor types.
  • Deprecation: keep the string form + emit a compile warning.

Acceptance

  • input('X') + channel:/repo: + rfPath compile down to the existing ${X} token; deploy-time resolution unchanged.
  • Unknown input ref → loud compile error (typo caught pre-deploy).
  • Legacy ${} string form still resolves, with a deprecation warning.
  • Tests cover: ref rendering, field→path expansion, validation error, back-compat.

Context

Motivated by the linear-slack channel-scoping work (cloud#1999/#2000/#2007) — the interpolation engine exists; this is purely the authoring DX on top of it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions