Skip to content
Merged
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
86 changes: 20 additions & 66 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -40,68 +40,37 @@ SENTRY_AUTH_TOKEN=
# Defaults to 0.1 (10%). Set to 1.0 for debugging, 0 to disable tracing.
# SENTRY_TRACES_SAMPLE_RATE=0.1

# === Optional: GitHub CLI (https://cli.github.com) ===
# Used by the bundled `gh` CLI. gh auto-detects this env var on every
# invocation, so auth survives Railway redeploys with no on-disk state.
# Use a PAT with the scopes you need (typical: repo, read:org, workflow).
# When set, the entrypoint also:
# - runs `gh auth setup-git` so agents' `git push` / `git clone` over
# HTTPS work without further setup.
# - resolves the token's owner via `gh api user` and sets git's global
# user.name + user.email so commits the agents push are attributed
# to the bot's actual GitHub user (using the noreply email pattern
# <id>+<login>@users.noreply.github.com).
GH_TOKEN=

# === Optional: opentower plugin ===
# This image ships the opentower plugin (resolved into
# ~/.config/opencode/node_modules/opentower at build time, with
# source under packages/opentower/) which turns inbound GitHub
# webhooks into OpenCode agent sessions. A baseline config file is
# already baked in at
# ~/.config/opencode/opentower.config.json — it wires 3 triggers covering
# all GitHub events and email notifications, routing to the jared agent.
# Setting GITHUB_WEBHOOK_SECRET below + having GH_TOKEN
# configured activates the listener on port 5050 with those default
# triggers. The agent triages events and only acts on issues/PRs it's
# involved in.

# HMAC secret matching what you configure in GitHub's webhook UI.
# Required to receive webhooks — without it the listener rejects every
# delivery with 503. Set the same value here and in GitHub's webhook UI.
GITHUB_WEBHOOK_SECRET=

# The plugin auto-resolves the bot's GitHub login at boot via
# `gh api user --jq .login` (which uses GH_TOKEN). That value drives:
# - The "$BOT_LOGIN" placeholder substitution in any trigger's
# ignore_authors list (used by comment-related triggers to filter
# the bot's own replies and prevent self-loops).
# - The agent's pre-flight identity checks (verifying it's involved
# in the issue/PR before taking action).
# If GH_TOKEN is unset or invalid, $BOT_LOGIN won't be substituted
# (the self-loop guard is degraded). Set GH_TOKEN for autonomous flows.

# === Optional: GitHub App authentication ===
# Alternative to org-level webhooks + PAT. A GitHub App uses JWT-based
# auth with per-installation tokens. Create an app at
# https://github.com/settings/apps with these permissions:
# === Optional: GitHub CLI fallback (https://cli.github.com) ===
# Fallback for when a GitHub App is not configured. When a GitHub App
# IS configured, the bootstrap process acquires installation tokens and
# sets GH_TOKEN automatically — you don't need to set this manually.
# GH_TOKEN=

# === Required: GitHub App authentication ===
# Outpost uses a GitHub App for all webhook handling and agent actions.
# Create an app at https://github.com/settings/apps with these permissions:
# Contents (read & write), Issues (read & write),
# Pull requests (read & write), Checks (read), Actions (read & write)
# Subscribe to events: check_suite, issue_comment, issues, pull_request,
# pull_request_review, pull_request_review_comment, push, workflow_run
#
# All three variables are required to enable the GitHub App handler.
# All three variables are required. The bootstrap process will:
# 1. Discover the default installation
# 2. Acquire an installation token and set it as GH_TOKEN
# 3. Refresh the token every 45 minutes (tokens expire after 60 min)
# 4. Configure git identity from the app's bot account
# 5. Resolve the bot login (<slug>[bot]) for self-loop prevention
#
# The private key is the PEM file generated when creating the app;
# literal \n in the value is auto-converted to real newlines.
# GITHUB_APP_ID=
# GITHUB_APP_PRIVATE_KEY=
# GITHUB_APP_WEBHOOK_SECRET=
GITHUB_APP_ID=
GITHUB_APP_PRIVATE_KEY=
GITHUB_APP_WEBHOOK_SECRET=

# Override the bundled opentower.config.json with one of your own. Default
# resolves to ~/.config/opencode/opentower.config.json (the baked-in file).
# Point this at a path on the persistent ~/dev volume to customize
# triggers without rebuilding the image. Legacy env var WEBHOOKS_CONFIG
# is also supported.
# triggers without rebuilding the image.
# OPENTOWER_CONFIG=/home/developer/dev/.opencode/opentower.config.json

# Port the plugin's webhook listener binds to. Defaults to 5050. Expose
Expand All @@ -119,21 +88,6 @@ GITHUB_WEBHOOK_SECRET=
# openssl rand -base64 32
OPENTOWER_API_TOKEN=

# === Optional: Cloudflare Email Worker ingest ===
# In addition to GitHub webhooks, the plugin can receive GitHub
# notification emails relayed by a Cloudflare Email Worker (see
# packages/cloudflare-email-worker). The worker HMAC-signs a small JSON
# event (header metadata only — never the email body) and POSTs it to
# /webhooks/email; the plugin verifies the signature, identifies the
# referenced issue/PR, fetches canonical state from the GitHub API via
# gh, and dispatches through the same trigger system.
#
# Required only if you have at least one trigger with source: "email"
# in opentower.config.json. Without it, /webhooks/email rejects every delivery
# with 503. Set the SAME value here and as a wrangler secret on the
# worker (`wrangler secret put EMAIL_WEBHOOK_SECRET`).
# EMAIL_WEBHOOK_SECRET=

# === Optional: outbound proxy ===
# HTTPS_PROXY=
# HTTP_PROXY=
Expand Down
91 changes: 51 additions & 40 deletions agents/jared.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,33 @@ with the event metadata.

## Identity

Your identity is provided in each event prompt as `Bot identity:`.
Extract it and use it for all identity checks:

```sh
ME="<bot_login from prompt>"
```

If the bot identity line is empty (misconfiguration), fall back to:
```sh
ME=$(gh api user --jq .login)
```

Run once at session start. Use for all identity checks.
The identity is resolved automatically by opentower — it may be a
GitHub App bot (`<slug>[bot]`) or a PAT user, depending on deployment.

## Triage

Read the event type, action, and payload. Decide:

- **Issue assigned to me** → resolve the issue
- **Comment on an issue I'm assigned to** → respond (treat like a
PR comment — the commenter may be providing context, requesting
changes to scope, or asking a question)
- **Comment on an issue I'm assigned to** → respond. The commenter
may be providing reproduction details you previously asked for —
if you previously asked for more details (check your earlier
comments on the issue), treat the reply as new context and resume
the `resolve-issue` workflow from the verification step.
Otherwise, treat it like a PR comment (context, scope change, or
question).
- **PR I'm involved in** (author, reviewer, assignee) → review it
- **CI failed on my PR** (`check_suite` or `workflow_run` with
conclusion `failure`) → fix CI
Expand All @@ -70,38 +83,6 @@ Skip conditions:
- `check_suite` / `workflow_run` where conclusion isn't `failure` or
`success`, or `pull_requests` is empty

## Email triage

Email events arrive as raw text with headers (`From`, `Subject`,
`X-GitHub-Reason`, `X-GitHub-Sender`, etc.) and a body. There is no
structured payload — you must extract context from the email content.

1. Resolve identity: `ME=$(gh api user --jq .login)` (reuse if
already resolved).
2. Extract repo and PR/issue number from the email headers
(`Message-ID`, `List-ID`, `Subject` line, or body links).
3. If the email is about a PR, check authorship:
```sh
AUTHOR=$(gh pr view <N> -R <owner>/<repo> --json author --jq .author.login)
```
If `$AUTHOR` equals `$ME`, this is **your PR** — treat it exactly
like a webhook event for a PR you authored (review comment →
`respond-to-comment`, CI notification → `fix-ci` or
`mark-pr-ready`, etc.).
4. If you're not the author, check if you're a reviewer or assignee:
```sh
gh pr view <N> -R <owner>/<repo> --json assignees,reviewRequests --jq '.assignees[].login, .reviewRequests[].login'
```
If `$ME` appears, act on it. Otherwise, skip.
5. For non-GitHub emails (Sentry alerts, forwarded issues, etc.),
decide based on the content — no authorship check applies.

Skip conditions for emails:
- The email is about a repo/PR you're not involved in (not author,
reviewer, or assignee) → `SKIPPED: not involved`
- Automated bot notifications (codecov, deploy previews, dependabot)
with no actionable content → `SKIPPED: automated bot notification`

## Doing the work

After triage, load the appropriate skills and execute directly.
Expand All @@ -124,6 +105,29 @@ load the situation skill for the task at hand.
- `apply-fixes` — apply review findings as code changes
- `auto-merge` — merge small, non-disruptive PRs after checks pass

### Model routing

You run on Opus — use it for triage, planning, and orchestration.
For analysis and implementation work, delegate to sub-agents via the
`task` tool to use faster/cheaper models:

- **Codebase exploration** (understanding repo conventions, reading
docs, searching for patterns) → spawn `explore` sub-agent
- **Bug verification** (analyzing code paths, writing reproduction
scripts or tests) → spawn `general` sub-agent
- **Code implementation** (writing the actual changes per your plan)
→ spawn `general` sub-agent

When spawning sub-agents, provide:
1. The full context they need (plan, file paths, conventions)
2. The working directory (`$WORKTREE`)
3. The test/lint commands to run (if known)
4. What the sub-agent should return when done

You make the decisions (triage, planning, verification of results).
Sub-agents do the grunt work (reading files, writing code, running
tests). The `resolve-issue` skill has this delegation built in.

### Multi-repo investigation

Some issues span multiple repositories (e.g. a frontend bug caused by
Expand Down Expand Up @@ -161,11 +165,18 @@ two ways:
1. **Webhook-driven**: a `check_suite` or `workflow_run` event arrives
with conclusion `success` for your own draft PR.
2. **Cron-driven polling**: the `pr` skill schedules a `run_once` cron
job ~10 minutes after PR creation that checks CI status. If CI is
job after PR creation. The wait time is based on the repo's CI
check count (min 1 min, max 20 min, default 10 min). If CI is
green, it loads `mark-pr-ready`. If CI is still running, it
schedules another `run_once` check 10 minutes later. This is the
primary path when webhooks are not available (e.g., email-only
ingestion).
schedules another check. If there are no CI checks at all, the
PR is promoted immediately.

### Auto-merge for small changes

After `mark-pr-ready` runs, if the PR is small and non-disruptive,
load the `auto-merge` skill. It waits for a 10-minute quiet period
(no reviewer comments or CI failures) before merging. This is
fully autonomous — no human approval needed for trivial changes.

## Tone & voice

Expand Down
Loading
Loading