Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
690 changes: 124 additions & 566 deletions README.md

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions examples/linear-shipper/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Linear Shipper

This deployable persona follows the paraglide pattern: a Linear issue triggers a sandboxed implementation run, then the agent links the result back to Linear.

## Setup

Connect Linear and GitHub before deploying.

```bash
workforce deploy ./examples/linear-shipper/persona.json --mode dev
```

Set the target repository through the persona inputs: `GITHUB_OWNER`, `GITHUB_REPO`, and `REPO_URL`.

## Current GitHub Handoff

The v1 client contract exposes `createIssue`, not `createPr`, so the example creates a draft handoff issue and includes a `TODO(human)` where `createPr` should be used once the runtime exposes it.
61 changes: 61 additions & 0 deletions examples/linear-shipper/agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { handler } from '@agentworkforce/runtime';

type LinearIssueEvent = {
issue?: { id?: string; identifier?: string; title?: string; url?: string };
};

function inputDefault(ctx: Parameters<Parameters<typeof handler>[0]>[0], name: string): string {
const value = ctx.persona.inputs?.[name]?.default;
if (!value) throw new Error(`${name} input is required`);
return value;
}

function shellQuote(value: string): string {
return `'${value.replace(/'/g, `'\\''`)}'`;
}

function safeRepoDirName(value: string): string {
if (!/^[A-Za-z0-9._-]+$/.test(value)) {
throw new Error('GITHUB_REPO must be a repository name, not a path or shell fragment');
}
return value;
}

export default handler(async (ctx, event) => {
if (event.source !== 'linear' || event.type !== 'issue.created') return;
if (!ctx.linear) throw new Error('linear-shipper requires the linear integration');
if (!ctx.github) throw new Error('linear-shipper requires the github integration');

const issueRef = (event as LinearIssueEvent).issue;
const issueId = issueRef?.id ?? issueRef?.identifier;
if (!issueId) throw new Error('Linear event is missing an issue id');

const issue = await ctx.linear.getIssue(issueId);
const repoUrl = inputDefault(ctx, 'REPO_URL');
const owner = inputDefault(ctx, 'GITHUB_OWNER');
const repo = safeRepoDirName(inputDefault(ctx, 'GITHUB_REPO'));
const repoDir = `${ctx.sandbox.cwd}/${repo}`;

await ctx.sandbox.exec(`git clone ${shellQuote(repoUrl)} ${shellQuote(repoDir)}`);
const result = await ctx.harness.run({
prompt: `Implement this Linear issue. Create the smallest reviewable change and include verification notes.\n\nTitle: ${issue.title}\n\n${issue.description ?? ''}`,
cwd: repoDir,
tier: 'best'
});

// TODO(human): createPr is not in the published GithubClient contract yet.
const created = await ctx.github.createIssue({
owner,
repo,
title: `Draft PR needed: ${issue.title}`,
body: [
`Linear issue: ${issue.url ?? issueId}`,
'',
'The harness produced an implementation attempt, but GithubClient.createPr is not exposed yet.',
'',
result.output
].join('\n')
});

await ctx.linear.comment(issueId, `Implementation attempt captured in GitHub issue: ${created.url}`);
});
68 changes: 68 additions & 0 deletions examples/linear-shipper/persona.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"id": "linear-shipper",
"intent": "implementation",
"tags": ["implementation"],
"description": "Turns a new Linear issue into an implementation attempt and links the resulting GitHub work back to Linear.",
"cloud": true,
"integrations": {
"linear": {
"triggers": [{ "on": "issue.created" }]
},
"github": {
"scope": {
"repo": "AgentWorkforce/workforce"
}
}
},
"sandbox": true,
"inputs": {
"GITHUB_OWNER": {
"description": "GitHub owner containing the target repository.",
"default": "AgentWorkforce"
},
"GITHUB_REPO": {
"description": "Target repository name.",
"default": "workforce"
},
"REPO_URL": {
"description": "Clone URL for the target repository.",
"default": "https://github.com/AgentWorkforce/workforce.git"
}
},
"onEvent": "./agent.ts",
"tiers": {
"best": {
"harness": "codex",
"model": "gpt-5.5",
"systemPrompt": "Implement Linear issues with small, reviewable changes and clear handoff notes.",
"harnessSettings": {
"reasoning": "high",
"timeoutSeconds": 1800,
"sandboxMode": "workspace-write",
"workspaceWriteNetworkAccess": true
}
},
"best-value": {
"harness": "codex",
"model": "gpt-5.4",
"systemPrompt": "Implement Linear issues with small, reviewable changes and clear handoff notes.",
"harnessSettings": {
"reasoning": "medium",
"timeoutSeconds": 1200,
"sandboxMode": "workspace-write",
"workspaceWriteNetworkAccess": true
}
},
"minimum": {
"harness": "codex",
"model": "gpt-5.4-mini",
"systemPrompt": "Implement Linear issues with small, reviewable changes and clear handoff notes.",
"harnessSettings": {
"reasoning": "low",
"timeoutSeconds": 900,
"sandboxMode": "workspace-write",
"workspaceWriteNetworkAccess": true
}
}
}
}
21 changes: 21 additions & 0 deletions examples/review-agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Review Agent

This deployable persona listens for GitHub pull request events and Slack mentions, delegates code reasoning to the configured harness, and posts the result back through the connected integration.

## Setup

Connect GitHub and Slack before deploying. Because `useSubscription` is enabled, deployment also connects the model provider derived from the selected tier.

```bash
workforce deploy ./examples/review-agent/persona.json --mode dev
```

## Events

The persona handles opened pull requests, issue comment mentions, pull request review comments, failed check runs, and Slack app mentions.

## Run

```bash
workforce deploy ./examples/review-agent/persona.json --mode sandbox
```
107 changes: 107 additions & 0 deletions examples/review-agent/agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { handler } from '@agentworkforce/runtime';

type GithubTarget = { owner: string; repo: string; number: number };

function githubTarget(event: Record<string, unknown>): GithubTarget {
const repository = event.repository as {
owner?: string | { login?: string };
name?: string;
full_name?: string;
} | undefined;
const pullRequest = event.pull_request as { number?: number } | undefined;
const issue = event.issue as { number?: number } | undefined;
const checkRun = event.check_run as { pull_requests?: Array<{ number?: number }> } | undefined;
const owner = typeof repository?.owner === 'string'
? repository.owner
: repository?.owner?.login ?? repository?.full_name?.split('/')[0];
const repo = repository?.name ?? repository?.full_name?.split('/')[1];
const checkRunPullRequest = checkRun?.pull_requests?.find((pr) => typeof pr.number === 'number');
const number = pullRequest?.number ?? issue?.number ?? checkRunPullRequest?.number ?? Number(event.number);
if (!owner || !repo || !Number.isFinite(number)) {
throw new Error('GitHub event is missing owner, repo, or number');
}
return { owner, repo, number };
}

async function reviewPullRequest(ctx: Parameters<Parameters<typeof handler>[0]>[0], event: Record<string, unknown>) {
if (!ctx.github) throw new Error('review-agent requires the github integration');
const target = githubTarget(event);
const pr = await ctx.github.getPr(target);
const result = await ctx.harness.run({
prompt: `Review this PR for correctness, risk, and missing tests.\n\nTitle: ${pr.title}\nAuthor: ${pr.author}\nBase: ${pr.base}\nHead: ${pr.head}\n\n${pr.diff}`,
cwd: ctx.sandbox.cwd,
tier: 'best-value'
});
await ctx.github.postReview(target, { event: 'COMMENT', body: result.output });
}

async function replyToGithubMention(ctx: Parameters<Parameters<typeof handler>[0]>[0], event: Record<string, unknown>) {
if (!ctx.github) throw new Error('review-agent requires the github integration');
const target = githubTarget(event);
const comment = event.comment as { body?: string } | undefined;
const result = await ctx.harness.run({
prompt: `Reply to this GitHub discussion in context. Keep it specific and actionable.\n\n${comment?.body ?? ''}`,
cwd: ctx.sandbox.cwd,
tier: 'best-value'
});
await ctx.github.comment(target, result.output);
}

async function handleFailedCheck(ctx: Parameters<Parameters<typeof handler>[0]>[0], event: Record<string, unknown>) {
if (!ctx.github) throw new Error('review-agent requires the github integration');
const checkRun = event.check_run as { conclusion?: string; output?: { title?: string; summary?: string } } | undefined;
if (checkRun?.conclusion !== 'failure') return;
const target = githubTarget(event);
const result = await ctx.harness.run({
prompt: `CI failed. Inspect the failure and propose the smallest safe fix.\n\n${checkRun.output?.title ?? ''}\n\n${checkRun.output?.summary ?? ''}`,
cwd: ctx.sandbox.cwd,
tier: 'best'
});
await ctx.github.comment(target, result.output);
}

async function replyInSlack(ctx: Parameters<Parameters<typeof handler>[0]>[0], event: Record<string, unknown>) {
if (!ctx.slack) throw new Error('review-agent requires the slack integration');
const text = typeof event.text === 'string' ? event.text : '';
const channel = typeof event.channel === 'string' ? event.channel : '';
const ts = typeof event.threadTs === 'string'
? event.threadTs
: typeof event.thread_ts === 'string'
? event.thread_ts
: typeof event.ts === 'string'
? event.ts
: '';
if (!channel || !ts) throw new Error('Slack app_mention event is missing channel or thread timestamp');
const memories = await ctx.memory.recall(text, { limit: 5 });
const result = await ctx.harness.run({
prompt: `Answer this Slack mention using the remembered context when useful.\n\nContext:\n${JSON.stringify(memories)}\n\nMessage:\n${text}`,
cwd: ctx.sandbox.cwd,
tier: 'best-value'
});
await ctx.slack.reply({ channel, ts }, result.output);
await ctx.memory.save(`Slack mention handled: ${text.slice(0, 180)}`, {
tags: ['slack', 'review-agent'],
scope: 'workspace'
});
}

export default handler(async (ctx, event) => {
if (event.source === 'github') {
if (event.type === 'pull_request.opened') {
await reviewPullRequest(ctx, event);
return;
}
if (event.type === 'issue_comment.created' || event.type === 'pull_request_review_comment.created') {
await replyToGithubMention(ctx, event);
return;
}
if (event.type === 'check_run.completed') {
await handleFailedCheck(ctx, event);
return;
}
}

if (event.source === 'slack' && event.type === 'app_mention') {
await replyInSlack(ctx, event);
}
});
71 changes: 71 additions & 0 deletions examples/review-agent/persona.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
"id": "review-agent",
"intent": "review",
"tags": ["review"],
"description": "Reviews opened PRs, responds to @mentions in comments, attempts autofix on red CI.",
"cloud": true,
"useSubscription": true,
"integrations": {
"github": {
"triggers": [
{ "on": "pull_request.opened" },
{ "on": "issue_comment.created", "match": "@mention" },
{ "on": "pull_request_review_comment.created", "match": "@mention" },
{ "on": "check_run.completed", "where": "conclusion=failure" }
]
},
"slack": {
"triggers": [{ "on": "app_mention" }]
}
},
"sandbox": true,
"memory": {
"enabled": true,
"scopes": ["session", "workspace"]
},
"traits": {
"voice": "professional-warm",
"formality": "low",
"proactivity": "medium",
"riskPosture": "conservative",
"domain": "engineering",
"vocabulary": ["PR", "diff", "CI"],
"preferMarkdown": true
},
"onEvent": "./agent.ts",
"tiers": {
"best": {
"harness": "codex",
"model": "gpt-5.5",
"systemPrompt": "Review pull requests for correctness, regression risk, security concerns, and missing tests. Be concise and concrete.",
"harnessSettings": {
"reasoning": "high",
"timeoutSeconds": 1800,
"sandboxMode": "workspace-write",
"workspaceWriteNetworkAccess": true
}
},
"best-value": {
"harness": "codex",
"model": "gpt-5.4",
"systemPrompt": "Review pull requests for correctness, regression risk, security concerns, and missing tests. Be concise and concrete.",
"harnessSettings": {
"reasoning": "medium",
"timeoutSeconds": 1200,
"sandboxMode": "workspace-write",
"workspaceWriteNetworkAccess": true
}
},
"minimum": {
"harness": "codex",
"model": "gpt-5.4-mini",
"systemPrompt": "Review pull requests for correctness, regression risk, security concerns, and missing tests. Be concise and concrete.",
"harnessSettings": {
"reasoning": "low",
"timeoutSeconds": 900,
"sandboxMode": "workspace-write",
"workspaceWriteNetworkAccess": true
}
}
}
}
6 changes: 6 additions & 0 deletions examples/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"baseUrl": "..",
"paths": {
"@agentworkforce/persona-kit": ["packages/persona-kit/src/index.ts"],
"@agentworkforce/runtime": ["packages/runtime/src/index.ts"],
"@agentworkforce/workload-router": ["packages/workload-router/src/index.ts"]
},
"noEmit": true
},
"include": ["./**/*.ts"]
Expand Down
19 changes: 19 additions & 0 deletions examples/weekly-digest/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Weekly Digest

This deployable persona runs on a weekly cron schedule, searches Brave for the configured topics, clusters findings by source host, and upserts a single GitHub issue for the ISO week.

## Setup

Connect the GitHub integration before deploying:

```bash
workforce deploy ./examples/weekly-digest/persona.json --mode dev
```

Set `BRAVE_API_KEY` in the runner environment. The persona also accepts `TOPICS`, `GITHUB_OWNER`, and `GITHUB_REPO` through its input defaults.

## Run

```bash
BRAVE_API_KEY=... workforce deploy ./examples/weekly-digest/persona.json --mode sandbox
```
Loading
Loading