Build your own

Author a cloud persona: persona.json for deploy wiring, agent.ts for behavior.

Start with the job in one sentence:

When <trigger> happens, inspect <context>, take <bounded action>, and report <where>.

If that needs a paragraph, split it into smaller agents.

Use the skill

Don't hand-write the shape from memory — it moves. Install the skill and let your coding agent do the authoring:

# with prpm
npx prpm install @agent-relay/creating-cloud-persona

# with skills.sh
npx skills add https://github.com/agentworkforce/skills --skill creating-cloud-persona

Then ask it to "create a cloud persona for <job>". The skill covers the current persona shape, the event model, integration scopes and adapter config, inputs, memory, sandbox modes, and the production traps that break deploys.

The rest of this page is what the skill encodes — worth reading so you can tell whether what it wrote is right.

The two files

A cloud persona is always a pair:

  • persona.json — deploy and runtime wiring: the integrations it requires and their scopes, inputs, model and harness, memory, sandbox mode, and the system prompt that defines its role.
  • agent.ts — the behavior. defineAgent(...) declares the triggers, schedules, and watch rules; the handler branches on event.type and does the work.

Wakeups are declarative, behavior is imperative. Every provider your agent.ts listens on must also be declared in persona.json — an undeclared integration never mounts, and its triggers never fire.

Complexity stays declarative

Complex agents aren't more code — they're more declarations. The review agent reviews PRs, pushes fixes, and merges on approval. Its entire wakeup surface:

export default defineAgent({
  triggers: {
    github: [
      { on: 'pull_request.opened' },
      { on: 'pull_request.synchronize' },
      { on: 'pull_request_review.submitted' },
      { on: 'pull_request_review_comment.created' },
      { on: 'check_run.completed' },
      { on: 'issue_comment.created' }
    ],
    slack: [{ on: 'message.created', paths: ['/slack/channels/${SLACK_CHANNEL}/**'] }]
  },
  handler: async (ctx, event) => {
    const data = (await event.expand('full')).data;
    if (event.type.startsWith('slack.')) return handleSlackMergeRequest(ctx, data);
    // ...route the github events
  }
});

Seven event sources across two providers, and event is fully narrowed to exactly those types — an unhandled case is a type error, not a 3am surprise.

The heavy lifting delegates to a coding harness in one call:

const run = await ctx.harness.run({ cwd: ctx.sandbox.cwd, prompt: reviewHarnessPrompt(pr) });

The PR's repo is already materialized at ctx.sandbox.cwd; the agent edits files there and cloud commits and pushes after the harness exits. No git, no gh, no checkout logic in your code.

Keep context structured

Prefer durable references over pasted prose: Relay channels and threads, Relayfile paths, PR URLs and commit SHAs, Linear issue IDs. Structured context lets the agent resume, verify, and explain itself.

Verify before deploying

npx agentworkforce persona compile ./my-agent/persona.ts   # if typed
npx agentworkforce invoke ./my-agent/persona.json --schedule daily
npx agentworkforce deploy ./my-agent/persona.json --mode cloud --dry-run

invoke runs the handler locally against a fixture or a named schedule and prints a run record. The dry-run validates the deploy without side effects.

Every command takes the persona.json file, not the directory — a directory path fails with EISDIR. If you author a typed persona.ts, persona compile writes the JSON next to it.