Skip to content

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

@khaliqgant

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
    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