Skip to content

OnboardingPlugin Reference

This is a reference doc. It covers the OnboardingPlugin pipeline, request schema, idempotency guarantees, trigger sources, and related tooling.



OnboardingPlugin (lib/plugins/onboarding.ts) implements a deterministic, idempotent 9-step project provisioning pipeline. When triggered, it registers a project across Plane, GitHub, and Google Drive, then writes the project’s metadata to workspace/projects.yaml and notifies downstream consumers.


Steps run in sequence. Each step is individually idempotent — re-running the full pipeline for the same slug is safe.

#StepWhat it doesSkip condition
1validateChecks slug, title, and github are present; validates github is owner/repo format; rejects duplicate in-flight runsMissing required fields or invalid format
2idempotencyReads workspace/projects.yaml and exits early (success) if the slug is already registeredSlug already in projects.yaml
3plane_projectCreates a Plane project with a derived identifier (max 12 chars, uppercase, alphanumeric)PLANE_API_KEY not set
4plane_webhookRegisters a Plane webhook pointing at $WORKSTACEAN_PUBLIC_URL/webhooks/planePLANE_API_KEY or WORKSTACEAN_PUBLIC_URL not set
5github_webhookRegisters a GitHub repo webhook for issues, issue_comment, pull_request, pull_request_review_comment events; skips if webhook URL already registeredNo GitHub auth (QUINN_APP_ID or GITHUB_TOKEN), or WORKSTACEAN_PUBLIC_URL not set, or webhook already exists
6drive_folderCreates a Google Drive folder under the org root folder (drive.orgFolderId in workspace/google.yaml)Google credentials not set, google.yaml missing, or drive.orgFolderId empty
7projects_yamlUpserts the project entry into workspace/projects.yaml under a write lock; validates entry against ProjectEntrySchema before writing; preserves file header commentsSlug already present (double-checked under lock)
8bus_notifyPublishes message.inbound.onboard.complete with project metadata and step outcomesNever skipped
9replySends a confirmation message (or error) to the reply topicNo reply topic in the inbound message

Steps 3–6 are non-fatal: an error in one step is logged, and the pipeline continues to projects_yaml. The pipeline aborts only if projects_yaml (step 7) fails.


Sent as the payload of a message.inbound.onboard bus message (or the JSON body of POST /api/onboard):

FieldTypeRequiredDefaultDescription
slugstringUnique project identifier, e.g. "protolabsai-myproject"
titlestringHuman-readable project name
githubstringRepository in "owner/repo" format
defaultBranchstring"main"Default git branch
teamstring"dev"Team assignment: "dev", "gtm", etc.
agentsstring[]["protomaker", "quinn"]Agent identifiers to associate
discordobject{}Discord channel IDs (general, updates, dev, alerts, releases)

lib/project-schema.ts defines the Zod schema for workspace/projects.yaml entries. It is used by:

  • lib/plugins/onboarding.ts — validates each new entry before writing (step 7 fails if validation fails)
  • lib/plugins/a2a.ts — validates on load (warns and skips invalid entries)
FieldRequiredDescription
slugUnique project slug
github"owner/repo" format
statusProject status string
discord.devDev channel ID (may be empty string until channel is created)
titleHuman-readable name
defaultBranchGit default branch
teamTeam assignment
agentsList of agent identifiers
planeProjectIdUUID of the corresponding Plane project
onboardedAtISO 8601 timestamp of onboarding
onboardingStatePer-step status tracking (ok | skip | error)
googleWorkspace.driveFolderIdGoogle Drive folder ID created during onboarding
googleWorkspace.sharedDocIdProject spec/brief Google Doc ID
googleWorkspace.calendarIdProject-scoped calendar ID
// Validate a single raw entry
validateProjectEntry(raw: unknown): { ok: true; entry: ProjectEntry } | { ok: false; errors: string[] }
// Parse and validate the full projects.yaml structure (throws on invalid)
parseProjectsYaml(raw: unknown): ProjectsYaml

The pipeline is safe to re-run for the same project slug:

  1. Step 2 (idempotency check) exits early with status: "already_onboarded" if the slug is already in projects.yaml. No external API calls are made.
  2. Step 5 (github_webhook) lists existing webhooks before creating; skips if the target URL is already registered.
  3. Step 7 (projects_yaml) double-checks for the slug under a write lock before appending.
  4. In-flight guard — concurrent requests for the same slug are rejected with an error while the first run is in progress.

message.inbound.onboard

Any subscriber can publish to this topic to trigger onboarding. The payload must match the OnboardRequest schema above.

POST /api/onboard
Content-Type: application/json
{
"slug": "protolabsai-myproject",
"title": "My Project",
"github": "protoLabsAI/my-project"
}

The HTTP handler waits up to 30 seconds for the pipeline to complete and returns the result synchronously. If the pipeline takes longer than 30s, it returns { success: true, status: "accepted" } immediately and the pipeline continues in the background.

The Discord plugin routes slash commands to message.inbound.discord.slash.{interactionId} via the command config in workspace/discord.yaml. An /onboard command configured in discord.yaml can pass the project fields as options and publish to message.inbound.onboard. Autocomplete for project fields can be configured via the choices option in the command spec.

GitHub org webhook: repository.created (M2)

Section titled “GitHub org webhook: repository.created (M2)”

When the GitHub org webhook is registered and a new repository is created under the org, lib/plugins/github.ts catches the repository + action: created event and publishes to message.inbound.onboard automatically. The payload uses the repository’s full_name, name, owner.login, description, and visibility.


TopicWhen publishedPayload highlights
message.inbound.onboard.completeAfter projects_yaml step succeedsslug, github, planeProjectId, driveFolderId, per-step status
{msg.reply.topic}After pipeline finishes (success or error)Full result with success, step, human-readable content

VariableRequired byDefaultPurpose
PLANE_API_KEYSteps 3, 4Enables Plane project and webhook creation
PLANE_BASE_URLSteps 3, 4http://ava:3002Plane instance URL
PLANE_WORKSPACE_SLUGSteps 3, 4protolabsaiPlane workspace slug
PLANE_WEBHOOK_SECRETStep 4Optional HMAC secret for Plane webhooks
WORKSTACEAN_PUBLIC_URLSteps 4, 5Base URL for webhook registration (e.g. https://ws.example.com)
QUINN_APP_IDStep 5GitHub App ID for auth (used by makeGitHubAuth)
QUINN_APP_PRIVATE_KEYStep 5GitHub App private key
GITHUB_TOKENStep 5Alternative GitHub PAT for webhook registration
GITHUB_WEBHOOK_SECRETStep 5Optional HMAC secret for GitHub webhooks
GOOGLE_CLIENT_IDStep 6Google OAuth client ID
GOOGLE_CLIENT_SECRETStep 6Google OAuth client secret
GOOGLE_REFRESH_TOKENStep 6Google OAuth refresh token

The following workspace config files are watched at runtime (polled every 5 seconds by Node’s watchFile). Changes take effect without a container restart:

FileWatched byWhat reloads
workspace/github.yamlGithubPluginMonitored repos, org webhook config, mention handle, auto-triage settings
workspace/projects.yamlGithubPlugin, A2aPluginMonitored repo list (GithubPlugin), project routing table (A2aPlugin)
workspace/discord.yamlDiscordPluginChannel config, slash command list (re-registers commands on change)
workspace/agents.yamlDiscordPluginAgent identity list (bot → agent mapping)

A one-shot maintenance script that creates Plane projects for all entries in workspace/projects.yaml that are missing a planeProjectId, then seeds standard workflow states and labels.

When to run: After migrating existing projects into projects.yaml manually (i.e., projects that pre-date the OnboardingPlugin or were added without going through the pipeline).

Standard states seeded: Todo (default), In Progress, In Review, Done, Cancelled

Standard labels seeded: bug, feature, chore

The script is fully idempotent — safe to re-run. Projects that already have a planeProjectId are skipped. State and label creation skips items that already exist.

Terminal window
# Preview changes without writing anything
bun scripts/backfill-plane.ts --dry-run
# Run for real
bun scripts/backfill-plane.ts

Required env vars: PLANE_API_KEY

Optional env vars:

  • PLANE_BASE_URL (default: http://ava:3002)
  • PLANE_WORKSPACE_SLUG (default: protolabsai)
  • PROJECTS_YAML_PATH (default: workspace/projects.yaml relative to cwd)