mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-12 22:56:58 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Agents split work into child tasks, and each task runs in an execution workspace (a checkout, branch, or sandbox folder) > - Since early 2026, a new child task used its parent's realized workspace by default: the same checkout, branch and sandbox > - A parent and its child often run at the same time. Two writers in one folder lose each other's files, the waking parent is cancelled with `workspace_busy`, and sandbox lease ownership crosses from the parent into the child, so retained-file recovery refuses the workspace as ambiguous > - The product rule is simple: a subtask gets its own workspace, independent of the parent's saved files. Paperclip never needs to carry a parent's unmerged changes into a child > - This pull request makes that the only behaviour on every creation path, and moves existing shared children to their own workspace on their next run, with no user or admin action > - The benefit is that parents and children never block or overwrite each other, and recovery ownership stays with one task ## Linked Issues or Issue Description **What happened?** A child task inherited its parent's realized execution workspace by default (`createChild` defaulted to `executionWorkspaceInheritanceMode: "linkage"`, and `issueService.create` fell back from `inheritExecutionWorkspaceFromIssueId` to `parentId`). When the parent woke while the child wrote to the shared workspace, the parent run was cancelled with `workspace_busy`. In sandbox environments, the lease ownership chain crossed from the parent into the child, and retained-file recovery refused the workspace with `allocation_ownership_ambiguous`. **Expected behavior** Every new child task gets its own execution workspace, realized from the project base exactly as a top-level task in that project. It never uses the parent's checkout, branch, saved snapshot or unmerged changes. A parent and its descendant never share a workspace by any route. **Steps to reproduce** 1. Run a parent task in a project with isolated (git worktree) or sandbox workspaces, so the parent has a realized execution workspace. 2. From the parent run, create a child task with only `parentId` (or through the runner `create_task` tool, chat suggestions, decision effects, plugins, or the New sub-task dialog). 3. See that the child's `executionWorkspaceId` is the parent's, with `executionWorkspacePreference: "reuse_existing"`. 4. Wake the parent while the child runs in a sandbox environment. The parent run is cancelled with `workspace_busy`. **Additional context** Refs #9233 (the existing `strategy_only` opt-out). The opt-out `executionWorkspaceInheritanceMode: "strategy_only"` already existed for accepted-plan decomposition. This change makes that behaviour (tightened to drop a pinned existing branch) the only behaviour. ## What Changed - **One rule for every creation path (`server/src/services/issues.ts`).** `issueService.create` still uses the parent (or `inheritExecutionWorkspaceFromIssueId`) as a source for the project, project workspace and workspace *strategy*, but it never copies the source's `executionWorkspaceId`, `reuse_existing` preference, or pinned existing branch. All server paths that create a task go through `create()`/`createChild()`: issue routes (`POST /issues`, `POST /issues/:id/children`, the run-bound default parent and run workspace source), runner `create_task`, chat suggestion acceptance, native status decisions, accepted-plan decomposition, decision effects, plugins (`ctx.issues.create`), external agents, watchdog follow-ups, monitor recovery issues, case handoffs and email channels. - **Explicit ancestor workspaces are dropped.** If a create names an `executionWorkspaceId` (or an `existing` workspace selection) that an ancestor of the new task created or is bound to, `create()` drops it and keeps only the strategy. Older clients and skills that copied the parent's id keep working and get an independent workspace. - **`createChild` no longer has a "linkage" mode.** `executionWorkspaceInheritanceMode` is still accepted from internal callers and is ignored. - **`inheritExecutionWorkspaceFromIssueId` decision.** I kept the field for API compatibility, but it now copies only the source issue's project and workspace strategy. I removed its workspace-sharing behaviour for every pair, not only parent/child: the source task and its follow-up can run at the same time (the runner, watchdog and status-decision callers all create the follow-up while the source run is live), so sharing would again let two tasks write one folder. Explicit sharing between unrelated tasks stays available through an explicit `existing` workspace selection. - **New `server/src/services/task-workspace-lineage.ts`.** `buildIndependentTaskWorkspaceSettings` (strategy only: mode, environment, runtime, network egress, shared-workspace concurrency, base ref, branch template, commands; no `reuse_existing`, no `operator_branch`, no `existingBranch`), `loadTaskLineageIssueIds` (the parent chain plus, for a task that depth placement put beside its requester, the requester's chain via `privacyParentIssueId`) and `isAncestorExecutionWorkspace` (the workspace's `sourceIssueId` is in that lineage, or a lineage issue is bound to it). A child in another project does not take the parent's strategy. - **Upgrade-safe rebind at admission.** `executionWorkspaceService.releaseAncestorWorkspaceBindingAtAdmission` runs at the same admission point as the pending workspace selection (`heartbeat.ts`). For a child (or a flattened task beside its requester) that an older release bound to an ancestor's workspace, it clears the child's binding (keeps the project workspace and strategy, bumps the binding revision) so the run realizes the child's own workspace. It does nothing while another run of the child is running, while the child's earlier run still owns workspace finalization or recovery, for native restart/lease-owner recovery, for a process-loss retry, or for a Retry that keeps the current workspace. The parent and its workspace are never changed. - **Projects without a project workspace (per-task repository acquisition).** A parent with no project workspace worked in its own task files, so its settings (for example `isolated_workspace`) do not describe the project base. A child of such a parent, and an older child released from one, takes the project's defaults exactly like a top-level task, instead of an isolated mode that would resolve to a git worktree with no git base ("needs a project / project workspace"). When the child's new workspace is created at admission, `executionWorkspaceRepositoryService.prepareForAdmission` repeats the parent's (or requester's) repository requests as the child's own pending requests (identity, URL, catalog id and requested ref only; no pinned commit, branch or files). The normal acquisition path then clones a fresh copy into the child's task files with the child's own access checks. This runs only for a workspace created by that admission, so existing tasks never gain repositories. - **UI.** The New sub-task dialog no longer pre-selects the parent's workspace. `buildSubIssueDefaults` drops `executionWorkspaceId` and `reuse_existing`, and derives the parent's independent mode (`shared_workspace`, `isolated_workspace` or `agent_default`) from its preference, saved settings or current workspace. The "This sub-task will no longer use the parent task workspace" warning is removed. - **Contracts and docs.** Shared validator and plugin SDK comments describe the new meaning of `inheritExecutionWorkspaceFromIssueId`. Updated `doc/execution-semantics.md` (new rule in §6 and a wording fix in the retained-sandbox paragraph), `doc/SPEC-implementation.md` (child workspace rule, requester keeps *strategy* inheritance, watchdog follow-ups), `doc/SPEC.md` (task workspace ownership), `docs/specs/external-task-protocol.md`, `skills/paperclip/SKILL.md` (step 9 and the workspace bullet), `skills/paperclip/references/issue-workspaces.md`, the CEO onboarding `HEARTBEAT.md`, and the plugin SDK README. ### Previous-release story (`rules/upgrade-safe-run-state.mdc`) - **Migrated on next admission.** A child that the previous release bound to its parent's (or any ancestor's) execution workspace keeps that binding until its next normal admission. Then the server clears the binding and the run realizes the child's own workspace from the project base. No schema migration is needed. No user or admin action is needed. - **In-flight writers finish first.** A running child is not interrupted. Its current run, its native restart recovery (admitted native input or lease owner), a process-loss retry, and a Retry that keeps the current workspace all finish in the shared workspace. While a child's earlier run is still running or its workspace finalization or recovery is unsettled, the binding stays and a later admission releases it. - **No files are dropped.** The release changes only the child's binding row. The parent keeps its binding, and the shared workspace row, folder, branch and saved snapshots are not touched. Anything the child left there stays in the parent's workspace. - **No new refusal codes.** Saved run identity (native execution input, leases, checkpoints) is not changed or refused; the rebind only affects new admissions. ## Verification - New `server/src/__tests__/subtask-workspace-independence.test.ts` (embedded Postgres): `create` with `parentId`, `createChild` with no mode / `"linkage"` / `"strategy_only"`, `inheritExecutionWorkspaceFromIssueId` pointing at the parent, explicit parent and grandparent `executionWorkspaceId`, a non-child follow-up, and unchanged top-level defaults and explicit top-level reuse. 7 creation cases fail on `master` and pass with this change. Admission cases: an old-release child is rebound while its parent is running and the parent keeps its workspace; a running child is not rebound (deploy lands mid-run); an unrelated explicit share is kept; the parent is never rebound. - New heartbeat case in `server/src/__tests__/heartbeat-workspace-busy.test.ts`: an old-release child bound to its running parent's workspace in a sandbox environment runs to `succeeded` with no `workspace_busy`, gets a new workspace, and the parent run and binding stay unchanged. It fails on `master` (the child waits with `workspace_busy`). - Updated tests that asserted the old inheritance in `issues-service.test.ts`, `task-workspace-binding.test.ts`, `chat-project-tools.test.ts`, `task-workspace-child-trust.test.ts`, `heartbeat-git-restore-point-resume.test.ts` (a child bound to a closed parent's expired workspace now starts from the project base and leaves the parent's restore point alone), and `ui/src/lib/subIssueDefaults.test.ts`. Remote-workspace reservation, continuation and writer-wait suites now seed the older-release shared shape directly (or an unrelated explicit share), so those safety paths stay covered. - `npx vitest run` on the changed suites and about 45 related server suites (heartbeat workspace, recovery admission, issue routes, routines, plugins), `ui` `subIssueDefaults` and `NewIssueDialog` tests, `pnpm check:token-gates`, `pnpm -r typecheck`. - New `server/src/__tests__/heartbeat-subtask-acquired-repository.test.ts`: a project with no project workspace, a parent whose task files hold an acquired repository with an unmerged change, and a child (new, and the older-release shape bound to the parent's workspace) that runs to `succeeded` in its own task files with a fresh clone at the project base and its own ready repository receipt. The parent's checkout keeps its change. Both cases fail on the previous head with "needs a project / project workspace". ## Risks - **Project-level configuration still applies to children exactly as to top-level tasks.** In a project configured for `shared_workspace`, every task (top-level or child) uses the project's one folder under the existing shared-workspace concurrency policy. A branch that the project itself pins applies to every task in the project. This PR removes only the parent-to-child carry-over of the realized workspace; it does not change those project defaults. - **Standalone follow-ups that an older release bound through `inheritExecutionWorkspaceFromIssueId`** are migrated only when the source is in their recorded lineage (parent or privacy parent). Others keep their explicit share and the existing writer-wait serialization. - **Behaviour change for agents that relied on a shared checkout.** A child or follow-up no longer sees the parent's unpushed or uncommitted changes. Agents must push or merge code they want a subtask to build on. The skill and onboarding text now say this. - **Explicit ancestor workspace ids are silently dropped** instead of rejected, so older clients do not break. The task gets an independent workspace. - **First run of a migrated child starts from the project base.** If an older child had uncommitted work in the parent's workspace, that work stays in the parent's workspace and is not copied into the child's new workspace. - **Admission adds one indexed read** for tasks with a parent and a bound workspace; the locking transaction and ancestor query run only when the bound workspace was created by another issue. ## Model Used - Claude Opus 5.5 (Anthropic), model ID `claude-opus-5-5`, used through Claude Code with tool use (file edits, shell, test execution) and extended reasoning. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1199 lines
57 KiB
Markdown
1199 lines
57 KiB
Markdown
# Paperclip External Task Protocol Specification
|
||
|
||
Status: Draft v1 (provider-agnostic)
|
||
|
||
Purpose: Define the protocol by which Paperclip interoperates with external task managers (Linear,
|
||
Jira, Asana, Notion, Trello, GitHub Issues, and others) so that external tasks can drive Paperclip
|
||
agent work and Paperclip work is visible in the external tool, without requiring an external user
|
||
account for every Paperclip agent.
|
||
|
||
## Normative Language
|
||
|
||
The key words `MUST`, `MUST NOT`, `REQUIRED`, `SHOULD`, `SHOULD NOT`, `RECOMMENDED`, `MAY`, and
|
||
`OPTIONAL` in this document are to be interpreted as described in RFC 2119.
|
||
|
||
`Implementation-defined` means the behavior is part of the implementation contract, but this
|
||
specification does not prescribe one universal policy. Implementations MUST document the selected
|
||
behavior.
|
||
|
||
Terminology in this document:
|
||
|
||
- `Provider` — an external task manager product (Linear, Jira, Asana, Notion, Trello, GitHub
|
||
Issues, ...).
|
||
- `Connector` — an implementation of this protocol for one provider, hosted in the Paperclip
|
||
plugin runtime.
|
||
- `External task` — a task record that lives in the provider.
|
||
- `Paperclip issue` — the native Paperclip task record.
|
||
- `Host` — the Paperclip control plane: companies, agents, issues, checkout, heartbeat runs,
|
||
execution workspaces, budgets, approvals, blockers, issue documents, and work products.
|
||
|
||
## 1. Problem Statement
|
||
|
||
Teams already run their work in external task managers. Paperclip runs companies of agents. Today
|
||
these worlds only meet through manual copy/paste: a human reads a Linear issue, rewrites it as a
|
||
Paperclip issue, and later rewrites the outcome back into Linear.
|
||
|
||
This protocol defines the contract that lets an external task manager act as a **task source and
|
||
collaboration surface** for a Paperclip company, while Paperclip remains the **execution and
|
||
governance control plane**.
|
||
|
||
The protocol solves four interoperability problems:
|
||
|
||
- It defines one normalized task model so each connector translates provider payloads once, at the
|
||
edge, instead of leaking provider-specific shapes into agent prompts and core logic.
|
||
- It defines how an external task becomes (or links to) exactly one Paperclip issue, with
|
||
idempotent sync state that survives retries, webhook replays, and restarts.
|
||
- It defines how external assignment routes to Paperclip agents without creating a provider user
|
||
per agent.
|
||
- It defines which writes flow back to the provider, under what policy, and with what provenance,
|
||
so sync is predictable rather than chatty or destructive.
|
||
|
||
Important boundary (inherited from OpenAI Symphony, see Section 3.3):
|
||
|
||
- The connector is a tracker reader, sync engine, and routing layer.
|
||
- Provider writes (state transitions, comments, links) are explicit, policy-bound connector
|
||
operations — never hidden side effects of core host logic.
|
||
- A successful agent run can end at a workflow-defined handoff state (for example review), not
|
||
necessarily the provider's `Done`.
|
||
|
||
## 2. Goals and Non-Goals
|
||
|
||
### 2.1 Goals
|
||
|
||
- Let an external task manager act as a task source: active external tasks create or wake
|
||
Paperclip issues under configured policy.
|
||
- Keep one normalized external-task model across all providers, with provider-specific data
|
||
carried in an envelope, not in the core model.
|
||
- Route external assignment intent to Paperclip agents through deterministic, configurable rules.
|
||
- Reuse Paperclip's existing execution semantics — checkout as claim, heartbeat runs, execution
|
||
workspaces, retries and recovery — as a Symphony-compatible orchestration profile.
|
||
- Make outbound writes explicit, policy-bound, idempotent, and attributable.
|
||
- Detect conflicts instead of silently overwriting either side.
|
||
- Give operators observability: link state, sync health, cursors, queues, retries, and conflicts.
|
||
- Keep connectors implementable as plugins, without private host internals.
|
||
|
||
### 2.2 Non-Goals
|
||
|
||
- Replacing the Paperclip board with a clone of any provider's UI.
|
||
- Real-time field-level collaborative editing between Paperclip and a provider.
|
||
- A generic workflow engine or a universal schema covering every provider feature. Provider
|
||
features beyond the normalized model stay in the provider envelope.
|
||
- Creating a provider user account per Paperclip agent (supported as an OPTIONAL mapping, never
|
||
required).
|
||
- Letting external systems bypass Paperclip governance: checkout, budgets, approvals, blockers,
|
||
execution policies, and company boundaries remain host-owned.
|
||
- Migrating data: this is a sync protocol, not an importer/exporter for full historical fidelity.
|
||
|
||
## 3. System Overview
|
||
|
||
### 3.1 Main Components
|
||
|
||
1. `Task Source Adapter`
|
||
- Provider client owned by the connector.
|
||
- Fetches candidate external tasks and task states.
|
||
- Normalizes provider payloads into the `ExternalTask` model.
|
||
- Performs provider writes when policy allows.
|
||
|
||
2. `Task Link Store`
|
||
- Persists the 1:1 association between an external task and a Paperclip issue.
|
||
- Owns sync state: snapshots, fingerprints, cursors, status.
|
||
- MUST be stored in plugin state/entities first; MAY be promoted to core schema once at least
|
||
two connectors prove the model (see Section 16.4).
|
||
|
||
3. `Inbound Sync Engine`
|
||
- Consumes webhooks and poll results.
|
||
- Creates/updates Paperclip issues and imports comments with provenance.
|
||
- Runs scheduled reconciliation to repair drift.
|
||
|
||
4. `Routing Engine`
|
||
- Resolves external assignment intent into a Paperclip assignee using the ordered rule
|
||
pipeline in Section 8.
|
||
|
||
5. `Execution Bridge`
|
||
- Maps tracker-driven work onto host execution: issue checkout (claim), heartbeat runs,
|
||
execution workspaces, retry/recovery. Defined as the Symphony profile in Section 9.
|
||
|
||
6. `Outbound Write Engine`
|
||
- Applies the write policy in Section 10: progress comments, status projection, backlinks,
|
||
artifact/PR links.
|
||
|
||
7. `Conflict Queue`
|
||
- Records detected conflicts and exposes resolution actions (Section 11).
|
||
|
||
8. `Health Surface`
|
||
- Operator-visible connector status: last webhook, last poll, last reconcile, cursor positions,
|
||
queue depth, retry counts, current errors (Section 12).
|
||
|
||
### 3.2 Protocol Layers
|
||
|
||
The protocol is layered. A connector MAY implement lower layers without higher ones.
|
||
|
||
1. `Layer 1 — Task Source Adapter` (REQUIRED)
|
||
- Normalized read access to provider tasks. Sufficient for browse/import UX.
|
||
|
||
2. `Layer 2 — Task Link` (REQUIRED)
|
||
- Durable external-task ↔ Paperclip-issue association with sync state.
|
||
|
||
3. `Layer 3 — Execution Orchestration, Symphony Profile` (RECOMMENDED)
|
||
- Active external states drive Paperclip issue creation/wake; Paperclip checkout/run/retry
|
||
semantics are projected back as claim state.
|
||
|
||
4. `Layer 4 — Write and Sync Policy` (RECOMMENDED)
|
||
- Policy-bound outbound writes and two-way comment/status sync.
|
||
|
||
A connector that implements Layers 1–3 for read-driven execution is a **Symphony-compatible task
|
||
source** (Section 9.6).
|
||
|
||
### 3.3 Relationship to OpenAI Symphony
|
||
|
||
OpenAI Symphony specifies a service in which an issue tracker drives isolated, autonomous coding
|
||
agent runs: normalized tracker issues, active/terminal state mapping, claim/run/retry/release
|
||
lifecycle, per-issue workspaces, repository-owned workflow policy (`WORKFLOW.md`), and
|
||
operator-visible observability.
|
||
|
||
This protocol deliberately defers to Symphony's semantics wherever Symphony solves the same
|
||
problem, and maps them onto existing host primitives:
|
||
|
||
| Symphony concept | This protocol |
|
||
| --- | --- |
|
||
| Tracker client + normalized issue | Task Source Adapter + `ExternalTask` (Section 5) |
|
||
| Active / terminal states | Connector state mapping (Section 7.2) |
|
||
| Claim | Paperclip issue checkout + execution lock (Section 9.2) |
|
||
| Running map / live session | Heartbeat runs + issue liveness state (Section 9.3) |
|
||
| Retry queued | Host retry/recovery + scheduled wakes (Section 9.3) |
|
||
| Released | Issue terminal/review/blocked, or external task no longer active (Section 9.3) |
|
||
| Per-issue workspace | Execution workspace / worktree (Section 9.4) |
|
||
| `WORKFLOW.md` | Workflow policy document (Section 9.5) |
|
||
| Tracker writes via agent tools, not orchestrator | Outbound Write Engine + connector tools (Section 10.1) |
|
||
| Status surface | Issue/run/connector health UI (Section 12) |
|
||
|
||
Where Symphony is intentionally narrow — single repository, coding agents only, one tracker, no
|
||
multi-tenant control plane — this protocol extends it with company scoping, multi-agent routing,
|
||
governance gates, issue documents/work products, and two-way sync policy. Section 9.6 defines what
|
||
a connector MUST do to claim Symphony-profile conformance. Appendix B gives a section-by-section
|
||
crosswalk.
|
||
|
||
### 3.4 Core vs Connector Boundary
|
||
|
||
The host MUST own:
|
||
|
||
- Company scoping and authorization of every mutation.
|
||
- Issue checkout, execution locks, heartbeat invocation, and execution workspace lifecycle.
|
||
- Budget, approval, blocker, and execution-policy enforcement.
|
||
- Issue documents and work products.
|
||
- The normalized protocol types, once validated (shared SDK package).
|
||
|
||
Connectors MUST own:
|
||
|
||
- Provider authentication and webhook verification.
|
||
- Provider polling, pagination, and cursor management.
|
||
- Provider-specific state mapping and the provider envelope.
|
||
- All provider writes (comments, transitions, fields, links).
|
||
- Provider-specific setup, settings, and issue-detail UI.
|
||
|
||
A connector MUST perform all Paperclip issue/comment/status mutations through the capability-gated
|
||
plugin host clients (for example, `ctx.issues`) with plugin attribution. It MUST use `ctx.entities`
|
||
or `ctx.state` for connector-owned link and cursor data, and secret references for provider
|
||
credentials. A connector MUST NOT write host tables directly.
|
||
|
||
## 4. Core Domain Model
|
||
|
||
Field names use camelCase, matching Paperclip API conventions. (Symphony uses snake_case; the
|
||
crosswalk in Appendix B maps equivalent fields.)
|
||
|
||
### 4.1 Entities
|
||
|
||
#### 4.1.1 ExternalTask
|
||
|
||
Normalized external task record produced by the Task Source Adapter. This is the only shape the
|
||
host, routing engine, and prompt/task-packet rendering may depend on.
|
||
|
||
Fields:
|
||
|
||
- `providerKey` (string)
|
||
- Stable provider identifier: `linear`, `jira`, `asana`, `notion`, `trello`, `github_issues`.
|
||
- `externalId` (string)
|
||
- Stable provider-internal ID. Used for lookups and link keys.
|
||
- `externalKey` (string or null)
|
||
- Human-readable key where the provider has one (example: `ABC-123`). Null for providers
|
||
without keys (Trello, Notion).
|
||
- `title` (string)
|
||
- `description` (string or null)
|
||
- Normalized to Markdown on a best-effort basis; lossy conversions MUST be noted in the
|
||
envelope.
|
||
- `state` (object)
|
||
- `name` (string) — provider state/lane name.
|
||
- `category` (enum) — normalized category: `backlog`, `active`, `review`, `blocked`, `terminal`
|
||
(Section 7.2).
|
||
- `priority` (integer or null)
|
||
- Normalized 0–4, lower is more urgent. Non-mappable provider scales become null with the raw
|
||
value preserved in the envelope.
|
||
- `assignees` (list of external actor refs)
|
||
- `labels` (list of strings)
|
||
- Trimmed, lowercased.
|
||
- `blockedBy` (list of external task refs: `{externalId, externalKey, state}`)
|
||
- Derived from provider relations where supported; empty otherwise.
|
||
- `url` (string or null)
|
||
- `branchName` (string or null)
|
||
- Provider-supplied branch metadata if available.
|
||
- `dueAt`, `createdAt`, `updatedAt` (timestamps or null)
|
||
- `revision` (string or null)
|
||
- Provider revision/version/etag when available; used for conflict detection.
|
||
- `envelope` (object)
|
||
- Provider-specific payload (Section 4.1.2). Opaque to the host.
|
||
|
||
#### 4.1.2 ProviderEnvelope
|
||
|
||
Provider-specific data that does not generalize. Carried alongside the normalized model, persisted
|
||
with the task link, surfaced in provider-specific UI, and available to connector tools. The host
|
||
core MUST NOT branch on envelope contents.
|
||
|
||
Representative envelope content per provider is listed in Appendix A.
|
||
|
||
#### 4.1.3 TaskLink
|
||
|
||
The durable 1:1 association between one external task and one Paperclip issue.
|
||
|
||
Fields:
|
||
|
||
- `id` (string)
|
||
- `providerKey` (string)
|
||
- `connectorInstanceId` (string)
|
||
- One installed connector configuration (a provider MAY be installed multiple times per
|
||
company, e.g. two Jira sites).
|
||
- `companyId`, `projectId`, `goalId`, `issueId` (strings; `issueId` is the linked Paperclip issue)
|
||
- `handoffHistory` (list of `{issueId, endedAt, cause}`; empty for a link that has never moved)
|
||
- Prior Paperclip issue associations retained when reopened work is handed to a follow-up.
|
||
- `externalWorkspaceId`, `externalProjectId` (strings or null)
|
||
- Provider container coordinates (team/project/board/database).
|
||
- `externalTaskId`, `externalKey`, `externalUrl`
|
||
- `originSide` (enum: `paperclip`, `external`, `manual_link`)
|
||
- Which side created the pairing.
|
||
- `syncMode` (enum, Section 4.2.1)
|
||
- `runMode` (enum, Section 4.2.2)
|
||
- `fieldPolicy` (map field → owner, Section 4.1.4)
|
||
- `baseSnapshot` (object)
|
||
- Last agreed projected state of both sides; the three-way merge base for conflict detection.
|
||
- `lastExternalRevision` (string or null)
|
||
- `lastPaperclipFingerprint` (string or null)
|
||
- Hash of the last Paperclip-side state this connector projected outbound.
|
||
- `lastInboundAt`, `lastOutboundAt`, `lastReconcileAt` (timestamps or null)
|
||
- `status` (enum, Section 4.2.3)
|
||
- `statusDetail` (string or null)
|
||
- Human-readable explanation for `conflict`, `error`, `paused`.
|
||
|
||
Invariants:
|
||
|
||
- A `(connectorInstanceId, externalTaskId)` pair MUST map to at most one active link.
|
||
- An `issueId` MUST appear in at most one active link per connector instance, and SHOULD appear in
|
||
at most one active link overall; cross-connector double-linking is implementation-defined and
|
||
MUST be surfaced in UI when allowed.
|
||
- Every link MUST belong to exactly one `companyId`, resolved at link creation, before any
|
||
mutation on either side.
|
||
|
||
#### 4.1.4 FieldOwnershipPolicy
|
||
|
||
Per-field ownership controlling sync direction and conflict classification.
|
||
|
||
- Owners: `external`, `paperclip`, `shared`.
|
||
- Coverable fields (minimum): `title`, `description`, `state`, `priority`, `labels`, `assignee`,
|
||
`dueAt`.
|
||
- Default policy (RECOMMENDED): `title`/`description`/`priority`/`labels`/`dueAt` owned by
|
||
`external` for links with `originSide=external` (and by `paperclip` when
|
||
`originSide=paperclip`); `state` is `shared` and mediated by Section 7.4 and Section 10.3;
|
||
`assignee` is never directly synced — it flows through routing (Section 8).
|
||
- Writes from the non-owning side to an owned field MUST NOT be applied silently; they produce a
|
||
conflict entry (Section 11) or are dropped per policy, and the choice MUST be visible in link
|
||
health.
|
||
|
||
#### 4.1.5 RoutingRule
|
||
|
||
One rule in the connector's ordered routing pipeline (Section 8).
|
||
|
||
Fields:
|
||
|
||
- `order` (integer)
|
||
- `kind` (enum: `default_route`, `custom_field`, `label`, `state_lane`, `comment_command`,
|
||
`user_mapping`)
|
||
- `match` (object, kind-specific: field id + value pattern, label pattern, lane/state name,
|
||
external user id)
|
||
- `target` (object)
|
||
- `agentId` (string) or `routingAlias` (string, e.g. `triage`, `qa`, resolved against company
|
||
configuration).
|
||
- `enabled` (boolean)
|
||
|
||
#### 4.1.6 ExternalActor
|
||
|
||
Provenance identity for inbound content.
|
||
|
||
Fields:
|
||
|
||
- `providerKey`
|
||
- `externalUserId` (string)
|
||
- `displayName` (string)
|
||
- `mappedUserId` (string or null) — Paperclip user, if mapped.
|
||
- `mappedAgentId` (string or null) — Paperclip agent, if explicitly mapped (OPTIONAL feature).
|
||
- `isConnectorServiceAccount` (boolean)
|
||
- True when the actor is the connector's own provider identity. REQUIRED for echo suppression
|
||
(Section 10.4).
|
||
|
||
#### 4.1.7 SyncCursor
|
||
|
||
Per-connector-instance incremental sync state.
|
||
|
||
Fields:
|
||
|
||
- `scope` (string) — e.g. `tasks:<externalProjectId>`, `comments:<externalTaskId>`.
|
||
- `cursor` (string) — provider-opaque pagination/delta token or high-water-mark timestamp.
|
||
- `updatedAt` (timestamp)
|
||
|
||
Cursors MUST be persisted transactionally with the effects of the page they describe, so a crash
|
||
between "apply page" and "save cursor" re-applies idempotently rather than skipping.
|
||
|
||
#### 4.1.8 ConnectorHealth
|
||
|
||
Operator-visible runtime state per connector instance.
|
||
|
||
Fields:
|
||
|
||
- `lastWebhookAt`, `lastPollAt`, `lastReconcileAt` (timestamps or null)
|
||
- `webhookVerified` (boolean)
|
||
- `queueDepth` (integer) — pending inbound events.
|
||
- `retryCount` (integer) — currently scheduled retries.
|
||
- `conflictCount` (integer) — open conflict entries.
|
||
- `lastError` (object or null: `{category, message, at}`)
|
||
- `status` (enum: `healthy`, `degraded`, `error`, `paused`)
|
||
|
||
### 4.2 Enumerations
|
||
|
||
#### 4.2.1 `syncMode`
|
||
|
||
Per-link data flow direction:
|
||
|
||
- `import_only` — external → Paperclip only.
|
||
- `export_only` — Paperclip → external only.
|
||
- `bidirectional` — both directions under `fieldPolicy`.
|
||
- `observer` — read and display external state; mutate neither side.
|
||
- `disabled` — link retained, no sync.
|
||
|
||
#### 4.2.2 `runMode`
|
||
|
||
Per-link execution posture:
|
||
|
||
- `paperclip_controlled` — Paperclip governance decides when agents run; external state changes
|
||
inform but do not command execution.
|
||
- `symphony_compatible` — active external states create/wake Paperclip work automatically per
|
||
Section 9; the external tracker effectively drives dispatch.
|
||
- `external_observer` — no execution coupling; link exists for visibility only.
|
||
|
||
#### 4.2.3 Link `status`
|
||
|
||
- `pending` — link created, first sync not yet completed.
|
||
- `healthy` — last sync succeeded, no open conflicts.
|
||
- `conflict` — one or more unresolved conflict entries.
|
||
- `error` — last sync attempt failed; retry scheduled or exhausted.
|
||
- `paused` — operator suspended sync.
|
||
- `unlinked` — association severed; record retained for audit.
|
||
|
||
### 4.3 Stable Identifiers and Normalization Rules
|
||
|
||
- `External Task ID` — use for provider lookups and link keys. Never display-only keys.
|
||
- `External Key` — use for human-readable logs, comments, and UI.
|
||
- `Link key` — `(connectorInstanceId, externalTaskId)`.
|
||
- `Idempotency key` for inbound effects — `(connectorInstanceId, externalTaskId, eventId)` where
|
||
the provider supplies event IDs, else a content hash of the normalized change. Replayed webhooks
|
||
MUST NOT duplicate issues or comments.
|
||
- `Idempotency key` for outbound effects — `(linkId, effectKind, paperclipSourceId)` (e.g. the
|
||
Paperclip comment ID being mirrored). Retried jobs MUST NOT double-post.
|
||
- State names compare after trim + lowercase.
|
||
- Labels normalize to trimmed, lowercased strings.
|
||
- Timestamps normalize to ISO-8601 UTC.
|
||
|
||
## 5. Task Source Adapter Contract
|
||
|
||
### 5.1 REQUIRED Operations
|
||
|
||
A connector MUST implement:
|
||
|
||
1. `listCandidateTasks(scope, cursor) -> {tasks: ExternalTask[], cursor}`
|
||
- Return tasks in configured containers (team/project/board/database), filtered to configured
|
||
states where the provider supports server-side filtering, paginated.
|
||
|
||
2. `getTask(externalTaskId) -> ExternalTask`
|
||
|
||
3. `getTaskStates(externalTaskIds) -> {externalTaskId -> state}`
|
||
- Batched state refresh for reconciliation (Section 7.1.3) and run-eligibility checks
|
||
(Section 9.3).
|
||
|
||
4. `listComments(externalTaskId, cursor) -> {comments: ExternalComment[], cursor}`
|
||
- Each comment carries `externalCommentId`, `ExternalActor`, body (Markdown best-effort),
|
||
timestamps.
|
||
|
||
5. `verifyWebhook(request) -> VerifiedEvent | reject`
|
||
- Validate provider signatures/secrets where the provider supports them (Section 14.2). For
|
||
providers without signed webhooks, the connector MUST treat webhook payloads as untrusted
|
||
hints and confirm by API read-back before applying effects.
|
||
|
||
### 5.2 OPTIONAL Write Operations and Capability Declaration
|
||
|
||
Write operations are OPTIONAL per connector and gated by policy (Section 10):
|
||
|
||
- `createTask(projection) -> ExternalTask`
|
||
- `updateTaskState(externalTaskId, targetStateName)`
|
||
- `postComment(externalTaskId, body, attribution) -> externalCommentId`
|
||
- `setFields(externalTaskId, partial fields per fieldPolicy)`
|
||
- `setBacklink(externalTaskId, paperclipUrl)`
|
||
- Via link field, custom field, or pinned comment — provider-dependent.
|
||
|
||
A connector MUST declare a static capability set (readable by host UI) enumerating which
|
||
operations it implements, which state categories it can map, whether webhooks are signed, and
|
||
which routing rule kinds it supports. Host UX MUST degrade gracefully (hide actions, explain
|
||
gaps) based on declared capabilities.
|
||
|
||
### 5.3 Normalization Requirements
|
||
|
||
- Adapter output MUST match Section 4.1.1. Provider payload fields with no normalized home go in
|
||
the envelope.
|
||
- State category mapping (Section 7.2) is part of adapter configuration, not code, wherever the
|
||
provider has user-defined workflows (Jira, Linear, Trello lists, Notion selects).
|
||
- Rich text MUST convert to Markdown best-effort; the envelope SHOULD retain the source format
|
||
reference for lossless round-trips where the connector supports them.
|
||
- Person references in body text SHOULD be converted to plain display names; raw provider mention
|
||
syntax MUST NOT leak into Paperclip issue bodies where it could be misparsed as Paperclip
|
||
mentions.
|
||
|
||
### 5.4 Error Handling Contract
|
||
|
||
RECOMMENDED error categories:
|
||
|
||
- `provider_auth` (expired/invalid credentials)
|
||
- `provider_rate_limited` (with retry-after when available)
|
||
- `provider_request` (transport failures)
|
||
- `provider_status` (non-2xx)
|
||
- `provider_payload` (unparseable/unknown payload)
|
||
- `cursor_invalid` (provider rejected or lost the cursor)
|
||
- `webhook_signature` (verification failure)
|
||
|
||
Engine behavior on adapter errors:
|
||
|
||
- Candidate fetch failure: log, mark health `degraded`, skip this tick; never tear down existing
|
||
links or active work.
|
||
- State refresh failure during reconciliation: keep current link state; do not release claims or
|
||
stop runs on missing data (matches Symphony's reconciliation posture).
|
||
- `cursor_invalid`: fall back to a bounded full re-list with idempotent re-apply; never wipe and
|
||
re-import.
|
||
- `provider_rate_limited`: back off honoring provider guidance; webhook intake continues to queue.
|
||
|
||
## 6. Task Link Lifecycle
|
||
|
||
### 6.1 Link State Machine
|
||
|
||
```
|
||
create -> pending
|
||
pending -- first sync ok -> healthy
|
||
healthy -- sync failure -> error
|
||
error -- retry ok -> healthy
|
||
healthy -- conflict detected -> conflict
|
||
conflict -- resolved -> healthy
|
||
any state -- operator pause -> paused (resume returns to previous state)
|
||
any state -- unlink -> unlinked (terminal; record retained)
|
||
```
|
||
|
||
Transitions MUST be recorded with timestamps and causes in the link record or its audit trail.
|
||
|
||
### 6.2 Link Creation Paths
|
||
|
||
1. `Import` (`originSide=external`)
|
||
- An external task is selected (manually, or automatically under `runMode=symphony_compatible`
|
||
state mapping) and a Paperclip issue is created from its projection.
|
||
- The created issue MUST carry: source attribution (provider, external key, URL), the
|
||
normalized description, and routing-resolved assignee (Section 8).
|
||
|
||
2. `Export` (`originSide=paperclip`)
|
||
- A Paperclip issue is projected into the provider via `createTask`.
|
||
- REQUIRED only for connectors declaring write capability.
|
||
|
||
3. `Manual link` (`originSide=manual_link`)
|
||
- Operator pairs an existing external task with an existing Paperclip issue.
|
||
- The connector MUST compute an initial `baseSnapshot` from both sides and surface immediate
|
||
divergence as conflicts rather than picking a winner.
|
||
|
||
### 6.3 Deduplication
|
||
|
||
Before creating an issue or external task, the connector MUST check, in order:
|
||
|
||
1. Active link with the same `(connectorInstanceId, externalTaskId)`.
|
||
2. Paperclip backlink already present on the external task (custom field/comment marker).
|
||
3. External URL/key reference already present on a Paperclip issue in the same company.
|
||
|
||
Title-similarity matching MAY be used to *warn* in import UX; it MUST NOT silently merge.
|
||
|
||
### 6.4 Unlink and External Deletion Semantics
|
||
|
||
- Unlink severs sync but MUST NOT delete either side's record.
|
||
- When an external task is deleted/archived: the connector MUST NOT delete or cancel the Paperclip
|
||
issue. Default behavior: mark the link `conflict` (`statusDetail: external task deleted`),
|
||
comment on the Paperclip issue, and let the assignee/operator decide. `import_only` links with
|
||
no Paperclip-side activity MAY auto-unlink.
|
||
- When a Paperclip issue is cancelled/deleted: outbound policy decides whether to comment and/or
|
||
transition the external task; the connector MUST NOT delete the external task unless an
|
||
operator explicitly invokes a delete capability.
|
||
|
||
## 7. Inbound Synchronization (External → Paperclip)
|
||
|
||
### 7.1 Event Channels
|
||
|
||
A connector MUST implement at least one push or pull channel, and MUST implement reconciliation.
|
||
|
||
#### 7.1.1 Webhooks
|
||
|
||
Webhooks are RECOMMENDED where the provider offers them. They are verified per Section 5.1(5).
|
||
Events are queued, deduplicated by idempotency key, and applied in per-task order. Webhooks are
|
||
treated as *hints*: on any doubt, re-read via the adapter.
|
||
|
||
#### 7.1.2 Polling
|
||
|
||
Polling uses cursor-based incremental listing on a configured cadence. It is REQUIRED when
|
||
webhooks are unavailable or unverified, and RECOMMENDED as a backstop even with webhooks.
|
||
|
||
#### 7.1.3 Reconciliation
|
||
|
||
Reconciliation is REQUIRED. A scheduled job compares linked tasks' current provider state against
|
||
`baseSnapshot` and the linked issue. It repairs missed events, detects deletions or archivals, and
|
||
refreshes health timestamps. Reconciliation MUST be rate-bounded and incremental (cursor or rolling
|
||
window). It MUST NOT re-read every link on every run for large installations.
|
||
|
||
### 7.2 State Mapping
|
||
|
||
Each connector instance MUST carry an explicit mapping from provider states/lanes to the
|
||
normalized categories:
|
||
|
||
- `backlog` — visible, not requesting execution.
|
||
- `active` — requesting execution (Symphony's "active states").
|
||
- `review` — provider-side human review/handoff.
|
||
- `blocked` — provider-side blocked indication, where representable.
|
||
- `terminal` — done/cancelled/archived.
|
||
|
||
Mapping rules:
|
||
|
||
- Mapping is configuration, validated at setup against live provider workflow metadata where
|
||
available.
|
||
- Unmapped states MUST be treated as `backlog` (inert) and reported in health, never guessed.
|
||
- Category transitions drive issue effects per Section 7.4 and run effects per Section 9.3.
|
||
|
||
### 7.3 Comment Import and Provenance
|
||
|
||
- Imported comments MUST carry `ExternalActor` provenance and a deep link to the external comment
|
||
where the provider supports it.
|
||
- Imported comment content is **untrusted input**: it MUST be clearly attributed in the issue
|
||
thread, MUST NOT be interpreted as Paperclip system/agent instructions, and mention-like syntax
|
||
MUST NOT trigger Paperclip mention semantics except via the strict command path (Section 8.2).
|
||
- Whether an imported comment wakes the issue assignee is link policy (`wakeOnExternalComment`,
|
||
default true for `symphony_compatible`, false for `observer`).
|
||
- Comments authored by the connector's own service account MUST be suppressed on import
|
||
(Section 10.4).
|
||
|
||
### 7.4 External State Transitions Against Paperclip Work
|
||
|
||
When the external task changes category:
|
||
|
||
- `backlog -> active`: create or wake per `runMode` (Section 9.3).
|
||
- `active -> backlog` or `active -> blocked`: the connector SHOULD surface this on the issue; under
|
||
`symphony_compatible` it MUST make the issue ineligible for new tracker-driven dispatch and
|
||
MUST request stop of a tracker-driven run only when the plugin host declares an issue-scoped
|
||
run-stop capability (Section 9.3). Without that capability, it records a health warning and lets
|
||
the active run finish. It MUST NOT pause the whole agent because that could stop unrelated work.
|
||
Paperclip-native obligations (approvals in flight, blockers) are unaffected.
|
||
- `* -> terminal` while the Paperclip issue is active (checked out, running, in review, or carrying
|
||
unresolved blockers/approvals): default is a **conflict**, not silent closure. The issue gets a
|
||
comment naming the external actor and transition; the link enters `conflict` until an agent or
|
||
operator resolves it. Auto-close MAY be enabled per link policy only when no active run, no
|
||
pending review/approval, and no blockers exist.
|
||
- `terminal -> active` (reopen): if the linked issue is terminal, policy chooses between creating a
|
||
follow-up Paperclip issue linked to the same external task (RECOMMENDED default) or reopening,
|
||
subject to host rules for resuming closed issues. The connector MUST create or find the follow-up
|
||
with a stable idempotency key while the existing link remains active. After the follow-up is
|
||
durably identified, it retargets that same `TaskLink` record to the follow-up and appends the old
|
||
`issueId` to `handoffHistory` in one record update. On the current plugin SDK, this is one
|
||
`ctx.entities.upsert` of the entity keyed by the external task, not an unlink plus a second entity
|
||
write; inbound processing is already serialized per task (Section 7.1.1). A crash before the
|
||
upsert leaves the old link active, and a retry reuses the idempotent follow-up. A crash after it
|
||
leaves the retargeted link active. This preserves the link-key invariant in Section 4.1.3 without
|
||
requiring a multi-record transaction, while `handoffHistory` retains the original association
|
||
for audit.
|
||
|
||
External transitions MUST NOT directly set Paperclip issue status; they translate into host-level
|
||
requests that respect checkout, approvals, blockers, budget stops, and execution policy.
|
||
|
||
## 8. Agent Routing (Assignment Without External Agent Users)
|
||
|
||
External assignment intent resolves to a Paperclip assignee through an ordered pipeline. The first
|
||
matching enabled rule wins. Routing runs at import, and again whenever external assignment intent
|
||
changes (assignee change, label/field change, lane move, command comment).
|
||
|
||
### 8.1 Routing Pipeline
|
||
|
||
1. `comment_command` — strict slash command in an external comment (Section 8.2).
|
||
2. `custom_field` — provider custom field naming an agent (example: `Paperclip Agent = CodexCoder`).
|
||
3. `label` — namespaced label (example: `pc:agent/codexcoder`, `pc:route/qa`).
|
||
4. `state_lane` — provider state/lane mapped to a route (example: Trello list `Paperclip: QA`).
|
||
5. `user_mapping` — explicit external-user → agent mapping table (OPTIONAL; for teams that choose
|
||
to create provider users for agents).
|
||
6. `default_route` — connector/project default (example: all imported tasks go to a triage agent
|
||
or the CTO agent).
|
||
|
||
Requirements:
|
||
|
||
- Routing MUST resolve before the issue is created; every imported issue has an assignee.
|
||
- If no rule matches and no default route exists, the connector MUST assign the configured triage
|
||
fallback and post a visible "unresolved routing" comment on both sides (external side only if
|
||
write policy allows). Import MUST NOT be dropped silently.
|
||
- Routing targets are validated against the live company agent registry at apply time; routes to
|
||
missing/paused agents fall through to triage with a health entry.
|
||
- Re-routing an issue that is checked out MUST NOT force-reassign; it posts a handoff request
|
||
comment for the current owner (host reassignment rules apply).
|
||
|
||
### 8.2 Strict Comment Commands
|
||
|
||
Comment commands give external users explicit control without new UI in the provider.
|
||
|
||
- Syntax: line-anchored `/paperclip <verb> [args]`. Minimum verbs: `assign <agent>`,
|
||
`status`, `pause`, `resume`. Connectors MAY add verbs; all verbs MUST be listed in capability
|
||
metadata.
|
||
- Parsing MUST be deterministic connector code. An LLM MUST NOT infer intent from free text.
|
||
- The connector MUST reply (externally, if writes allowed; on the Paperclip issue otherwise) with
|
||
the command outcome, including rejections (unknown agent, not permitted).
|
||
- Command authorization is implementation-defined but MUST be documented (e.g. any provider
|
||
member vs. mapped users only).
|
||
|
||
### 8.3 Identity Model
|
||
|
||
- Paperclip agents MUST NOT be required to exist as provider users.
|
||
- Outbound writes use one connector service account per connector instance (Section 10.4), with
|
||
agent attribution carried in message content ("CodexCoder via Paperclip").
|
||
- `user_mapping` rules MAY map specific provider users to Paperclip users (for `assign it back to
|
||
me` flows) and to agents, but every such mapping is explicit configuration.
|
||
|
||
## 9. Execution Orchestration: Symphony Profile
|
||
|
||
This section defines how tracker-driven execution maps onto host primitives. It applies to links
|
||
with `runMode=symphony_compatible`; `paperclip_controlled` links use only Sections 9.4–9.5 advice.
|
||
|
||
### 9.1 Dispatch Model
|
||
|
||
Symphony's poll-tick loop becomes, in Paperclip:
|
||
|
||
1. Inbound sync (webhook/poll/reconcile) maintains link + normalized state.
|
||
2. For each link whose external category is `active`, the connector first moves a linked `backlog`
|
||
issue to `todo` through the host issue client, subject to normal host transition rules. A
|
||
`backlog` issue is not wakeable and MUST NOT receive a wake request directly.
|
||
3. When the linked issue is `todo`, not blocked, and not awaiting approval, the connector wakes the
|
||
assignee through `ctx.issues.requestWakeup`.
|
||
4. Host heartbeat scheduling — not the connector — decides actual run start, respecting company
|
||
concurrency, budget, and execution policy. The connector MUST NOT spawn agent processes
|
||
itself.
|
||
|
||
Bounded concurrency, dispatch ordering by priority, and per-issue serialization are host
|
||
responsibilities; the connector's job ends at "make the issue actionable and wake the right
|
||
agent."
|
||
|
||
### 9.2 Claim Semantics
|
||
|
||
- Paperclip issue checkout is the claim. One agent owns an issue at a time; checkout conflicts
|
||
(409) mean the issue is already claimed.
|
||
- The connector MUST treat checkout state as authoritative and MUST NOT maintain a parallel claim
|
||
registry for linked issues.
|
||
- Claim state SHOULD be projected outbound (Section 10.3) so external users can see that an agent
|
||
has picked the task up.
|
||
|
||
### 9.3 Run Lifecycle, Retry, Release
|
||
|
||
Host-side equivalents of Symphony's run-attempt machine:
|
||
|
||
- `Running` — an active heartbeat run exists for the issue's assignee on this issue.
|
||
- `RetryQueued` — host recovery/scheduled wake exists (failed run recovery, blocked-resume,
|
||
scheduled continuation).
|
||
- `Released` — issue reached terminal/review/blocked state, or the external task left `active`.
|
||
|
||
Profile requirements:
|
||
|
||
- When the external task leaves `active` during tracker-driven work, the connector MUST mark the
|
||
link state accordingly and stop issuing new connector wake requests. If the plugin host declares
|
||
an issue-scoped run-stop capability, the connector MUST use it for the active issue run. It MUST
|
||
NOT kill processes directly or approximate issue-scoped stop by pausing the assigned agent.
|
||
- The current plugin SDK does not expose an issue-scoped run-stop operation. On that host version,
|
||
a connector MUST record `issue_run_stop_unsupported`, let the active run finish, and MUST NOT
|
||
claim full Symphony profile conformance. Layers 1, 2, and 4 remain implementable.
|
||
- Failed runs follow host retry/recovery; the connector MUST NOT re-wake an issue in a tight loop
|
||
(wake requests for the same link MUST be debounced, RECOMMENDED minimum 60s).
|
||
- A successful run that ends at `in_review`/handoff is a valid terminal outcome for the
|
||
tracker-driven cycle (mirrors Symphony's `Human Review` boundary); the connector projects
|
||
`review`, it does not force `terminal`.
|
||
|
||
### 9.4 Workspaces
|
||
|
||
Per-issue isolation is provided by host execution workspaces/worktrees keyed by issue. Follow-up
|
||
issues can inherit the source issue's project and workspace strategy
|
||
(`inheritExecutionWorkspaceFromIssueId`), but each gets its own workspace; code carries over through
|
||
pushed branches, not a shared checkout. The connector never manages filesystem workspaces.
|
||
|
||
### 9.5 Workflow Policy
|
||
|
||
Symphony's repository-owned `WORKFLOW.md` maps to a layered policy lookup for rendering the task
|
||
packet (Section 9.5.1) and run guidance:
|
||
|
||
1. Paperclip project workflow document (RECOMMENDED MVP form).
|
||
2. Repository `WORKFLOW.md` discovered in the project workspace (OPTIONAL, code projects).
|
||
3. Connector instance defaults.
|
||
|
||
Precedence and safety:
|
||
|
||
- Workflow policy is versioned project policy. It MAY shape prompts, validation steps, and
|
||
handoff targets.
|
||
- Workflow policy MUST NOT override host system/company/agent instructions, governance gates, or
|
||
this protocol's invariants. It is additive guidance, lowest precedence.
|
||
|
||
#### 9.5.1 Task Packet Rendering
|
||
|
||
Tracker-originated work MUST reach the agent as a rendered task packet, not raw provider text:
|
||
|
||
- Inputs: normalized `ExternalTask`, link metadata (backlink URL, sync/run mode), workflow policy,
|
||
attempt/continuation context.
|
||
- The packet MUST mark external content as externally-authored untrusted input.
|
||
- The packet MUST include the external key/URL so agents can reference the source task in
|
||
comments and PRs.
|
||
- Rendering MUST be strict: missing required variables fail the dispatch with a health entry
|
||
rather than emitting a partial prompt.
|
||
|
||
### 9.6 Symphony Profile Conformance
|
||
|
||
A connector MAY claim "Symphony-compatible task source" when:
|
||
|
||
- Layers 1–3 are implemented.
|
||
- Active/terminal state mapping is explicit configuration (Section 7.2).
|
||
- Active external tasks create/wake issues without manual import, under operator-enabled policy.
|
||
- Checkout-as-claim is respected (Section 9.2) and external de-activation stops tracker-driven
|
||
dispatch (Section 9.3).
|
||
- The host exposes an issue-scoped run-stop capability to plugins, and external de-activation uses
|
||
it when a tracker-driven run is active (Section 9.3).
|
||
- Per-issue execution uses host workspaces (Section 9.4).
|
||
- Workflow policy lookup is implemented for at least one source (Section 9.5).
|
||
- Claim/run/retry/release state is observable per link (Section 12).
|
||
|
||
## 10. Outbound Synchronization and Write Policy (Paperclip → External)
|
||
|
||
### 10.1 Write Boundary
|
||
|
||
All provider writes flow through the connector's Outbound Write Engine or connector-provided
|
||
agent tools. Host core logic MUST NOT write to providers. Agent tools that write externally are
|
||
connector tools with the same policy checks as engine writes. (This is Symphony's tracker-writes
|
||
boundary, kept.)
|
||
|
||
Every outbound write MUST be:
|
||
|
||
- Policy-checked against the link's `syncMode` and `fieldPolicy` and the write toggles below.
|
||
- Idempotent under the outbound idempotency key (Section 4.3).
|
||
- Attributed (Section 10.4) and audit-logged (Section 14.5).
|
||
|
||
### 10.2 Write Policy Toggles and Defaults
|
||
|
||
Per link (with connector-instance defaults):
|
||
|
||
| Toggle | Default | Meaning |
|
||
| --- | --- | --- |
|
||
| `postBacklink` | on | Paperclip URL on the external task (field or pinned comment). |
|
||
| `postProgressComments` | on | Concise milestone comments: claimed, plan ready, PR opened, review requested, done. |
|
||
| `postArtifactLinks` | on | PR/work-product/document links when produced. |
|
||
| `mirrorAgentComments` | off | Full Paperclip comment thread mirrored externally. |
|
||
| `postTranscripts` | off | Run logs/transcripts externally. SHOULD remain off; transcripts may contain sensitive context. |
|
||
| `projectStatus` | on | Status projection per Section 10.3. |
|
||
| `mirrorExternalComments` | on (import side) | External comments imported per Section 7.3. |
|
||
|
||
Progress comments MUST be concise and milestone-based, not per-heartbeat chatter. Connectors
|
||
SHOULD batch/debounce outbound comments (RECOMMENDED minimum interval 5 minutes per link except
|
||
for claim/done/review milestones).
|
||
|
||
### 10.3 Status Projection
|
||
|
||
Paperclip issue status projects to provider states through the same mapping table as Section 7.2,
|
||
inverted, with these rules:
|
||
|
||
- Projection only moves the external task between states the operator mapped; unmapped Paperclip
|
||
statuses project as comments, not transitions.
|
||
- `in_review` projects to the mapped `review` state where one exists — the Symphony handoff
|
||
pattern — otherwise stays in `active` with a review-requested comment.
|
||
- `done` projects to the mapped terminal state only when `fieldPolicy.state` permits
|
||
Paperclip-side closure; otherwise it posts a completion comment and leaves the transition to
|
||
external users.
|
||
- Claim/run state (claimed by which agent, running, retrying, released) SHOULD be projected into a
|
||
custom field or status comment where the provider allows, so external users see liveness.
|
||
|
||
### 10.4 Attribution and Echo Suppression
|
||
|
||
- Outbound writes use the connector instance's service account. Message bodies MUST carry agent
|
||
attribution and the Paperclip issue link.
|
||
- Inbound processing MUST drop events authored by the connector's own service account
|
||
(`isConnectorServiceAccount`) **and** events matching a recently-issued outbound idempotency
|
||
key, preventing echo loops with providers that obscure authorship.
|
||
- Mirrored content MUST be marked so a second connector instance never re-mirrors it (loop
|
||
prevention across instances): a stable marker (hidden metadata or footer convention) is
|
||
REQUIRED on all mirrored comments.
|
||
|
||
## 11. Conflict Detection and Resolution
|
||
|
||
### 11.1 Detection
|
||
|
||
Three-way comparison per synced field: `baseSnapshot` vs current external value vs current
|
||
Paperclip value.
|
||
|
||
- Changed on one side only → propagate per `fieldPolicy` (or queue conflict if the changed side
|
||
is not the owner).
|
||
- Changed on both sides since base → conflict entry.
|
||
- Structural conflicts (Section 6.4 deletion, Section 7.4 terminal-while-active, routing failure)
|
||
are first-class conflict kinds.
|
||
|
||
### 11.2 Conflict Entry
|
||
|
||
Fields: `linkId`, `kind` (`field`, `deletion`, `closure`, `routing`, `policy`), `field` (when
|
||
applicable), `baseValue`, `externalValue`, `paperclipValue`, `detectedAt`, `actors` (both sides
|
||
where known), `status` (`open`, `resolved`, `dismissed`), `resolution`.
|
||
|
||
### 11.3 Resolution Actions
|
||
|
||
Exposed in the conflict queue UI and as agent-invocable connector tools:
|
||
|
||
- `keep_paperclip` (push Paperclip value outbound)
|
||
- `keep_external` (apply external value inbound)
|
||
- `merge_manual` (operator/agent supplies the merged value)
|
||
- `unlink`
|
||
- `dismiss` (acknowledge without change; updates `baseSnapshot` to current values)
|
||
|
||
Resolving a conflict MUST update `baseSnapshot` so the same divergence is not re-detected. Open
|
||
conflicts MUST NOT block unrelated fields from syncing.
|
||
|
||
## 12. Observability and Health
|
||
|
||
### 12.1 Logging Conventions
|
||
|
||
Connector log entries MUST carry: `connectorInstanceId`, `providerKey`, `linkId` (when bound),
|
||
`externalTaskId`/`externalKey`, `issueId`/issue identifier, event kind, idempotency key, and
|
||
outcome. Plugin activity logging is the REQUIRED sink; additional sinks are
|
||
implementation-defined.
|
||
|
||
### 12.2 Health Surface (REQUIRED)
|
||
|
||
Per connector instance, operators MUST be able to see `ConnectorHealth` (Section 4.1.8) plus:
|
||
|
||
- cursor positions and lag estimates,
|
||
- inbound queue depth and oldest pending event age,
|
||
- scheduled retries with next-due times,
|
||
- open conflicts (count + queue link),
|
||
- last 50 sync errors with categories.
|
||
|
||
### 12.3 Link Surface (REQUIRED)
|
||
|
||
Per linked issue (issue-detail integration), users MUST be able to see: external key/title/URL and
|
||
current external state; sync and run mode; field ownership; the routing rule that selected the
|
||
assignee; last inbound/outbound/reconcile times; current claim/run/retry/release state; and
|
||
actions (open external, resync now, pause/resume, unlink, resolve conflict).
|
||
|
||
### 12.4 External-Side Surface (RECOMMENDED)
|
||
|
||
On the provider side, a linked task SHOULD show: the Paperclip backlink, current Paperclip
|
||
owner/status (field or comment), and concise progress per Section 10.2.
|
||
|
||
## 13. Failure Model and Recovery
|
||
|
||
### 13.1 Failure Classes
|
||
|
||
1. `Provider outage / transport failure` — retry with exponential backoff and jitter; health
|
||
`degraded`; links untouched.
|
||
2. `Credential expiry` — health `error`, operator notification path REQUIRED; no link mutations.
|
||
3. `Webhook loss / delivery gaps` — repaired by polling backstop and reconciliation; this is why
|
||
Section 7.1.3 is REQUIRED.
|
||
4. `Cursor loss/corruption` — bounded re-list with idempotent re-apply (Section 5.4).
|
||
5. `Rate limiting` — honor provider guidance; shed reconciliation load before shedding webhook
|
||
processing; never drop queued inbound events on rate limits.
|
||
6. `Partial apply crash` — idempotency keys + transactional cursor persistence (Section 4.1.7)
|
||
make re-apply safe.
|
||
7. `Poison event` — an event that repeatedly fails application is parked with a health entry after
|
||
a bounded retry count (RECOMMENDED 5); it MUST NOT block the per-task queue forever.
|
||
8. `Provider schema drift` — unknown payload shapes degrade to `provider_payload` errors with the
|
||
raw payload preserved for diagnosis; known-good fields continue to apply.
|
||
|
||
### 13.2 Restart Recovery
|
||
|
||
Connector restart recovery is cursor- and link-store-driven: re-verify webhook registration,
|
||
resume cursors, run one reconciliation pass. In-memory queue contents may be lost; reconciliation
|
||
repairs the gap. No durable orchestrator state beyond the link store and cursors is REQUIRED.
|
||
|
||
### 13.3 Operator Intervention Points
|
||
|
||
Operators MUST be able to: pause/resume a connector instance; pause/resume a single link; force
|
||
resync of a link; replay a parked event; rotate credentials; and unlink. All interventions are
|
||
audit-logged.
|
||
|
||
## 14. Security and Governance
|
||
|
||
### 14.1 Company Boundary
|
||
|
||
Every connector instance binds to exactly one company. Every inbound effect MUST resolve its
|
||
target company from the link/connector instance before any mutation, and MUST NOT cross
|
||
companies. Multi-company installations are separate connector instances with separate credentials
|
||
and state.
|
||
|
||
### 14.2 Credentials and Webhooks
|
||
|
||
- Provider tokens/OAuth grants MUST be stored as secret references, never inline configuration or
|
||
plugin state.
|
||
- Webhook endpoints MUST verify provider signatures where offered; unverifiable webhook payloads
|
||
are hints requiring API read-back (Section 5.1(5)).
|
||
- Webhook endpoints MUST reject payloads that do not match the connector instance's registered
|
||
provider workspace.
|
||
|
||
### 14.3 Untrusted Content and Commands
|
||
|
||
- All external content (titles, descriptions, comments, field values, workflow files) is
|
||
untrusted input with provenance, per Sections 7.3 and 9.5.1.
|
||
- Comment commands are parsed deterministically (Section 8.2); free-text external content MUST
|
||
NOT trigger privileged actions.
|
||
- Workflow policy is lowest-precedence guidance (Section 9.5) — it cannot grant capabilities,
|
||
alter governance, or override system instructions.
|
||
|
||
### 14.4 Governance Invariants
|
||
|
||
No connector path may bypass:
|
||
|
||
- issue checkout/claim exclusivity,
|
||
- budget hard-stops and pause/cancel,
|
||
- approval gates and execution-policy stages,
|
||
- blocker semantics,
|
||
- company boundaries.
|
||
|
||
External signals translate into host-level requests subject to all of the above (Section 7.4).
|
||
Destructive cascade is prohibited: external deletion/archival never deletes Paperclip work
|
||
(Section 6.4).
|
||
|
||
### 14.5 Audit
|
||
|
||
Every mutation on either side carries attribution: connector instance, link, triggering event
|
||
idempotency key, and acting identity (external actor inbound; agent + connector service account
|
||
outbound). Audit entries are queryable per link.
|
||
|
||
## 15. Reference Flows (Language-Agnostic)
|
||
|
||
### 15.1 Import One External Task
|
||
|
||
```
|
||
on import_request(externalTaskId):
|
||
task = adapter.getTask(externalTaskId)
|
||
if dedupe_hit(task): surface_existing_link(); stop
|
||
company, project, goal = resolve_target(connector_instance)
|
||
assignee = routing.resolve(task) # Section 8; triage fallback guaranteed
|
||
packet = render_projection(task) # normalized fields only
|
||
issue = host.create_issue(company, project, goal, assignee, packet,
|
||
source_attribution(task))
|
||
link = links.create(task, issue, originSide=external,
|
||
baseSnapshot=snapshot(task, issue))
|
||
if policy.postBacklink: adapter.setBacklink(task.externalId, issue.url)
|
||
health.record(inbound_ok)
|
||
```
|
||
|
||
### 15.2 Inbound Comment
|
||
|
||
```
|
||
on provider_comment_event(evt):
|
||
evt = verify_or_readback(evt)
|
||
if evt.actor.isConnectorServiceAccount or echo_match(evt): drop
|
||
if not idempotency.first_time(evt): drop
|
||
link = links.by_external(evt.externalTaskId) or stop
|
||
if command = parse_strict_command(evt.body): # Section 8.2
|
||
execute_and_reply(command); record; stop
|
||
host.post_issue_comment(link.issueId, attributed(evt)) # provenance, untrusted
|
||
if link.policy.wakeOnExternalComment: host.wake_assignee(link.issueId)
|
||
```
|
||
|
||
### 15.3 Tracker-Driven Execution Cycle (Symphony Profile)
|
||
|
||
```
|
||
on external_state_change(task) where link.runMode == symphony_compatible:
|
||
category = state_mapping(task.state)
|
||
issue = host.issue(link.issueId)
|
||
if category == active and issue.status in {done, cancelled}:
|
||
if reopen_policy == follow_up:
|
||
issue = host.create_or_get_follow_up(
|
||
issue, inherit_workspace=true, idempotency_key=stable_key(task))
|
||
link = links.retarget(link, issue) # one entity upsert: new issueId + handoffHistory
|
||
else:
|
||
issue = host.request_resume(issue) # normal host resume rules
|
||
if category == active and issue.status == backlog:
|
||
issue = host.set_status(issue.id, todo) # backlog is not wakeable
|
||
if category == active and issue_dispatchable(issue):
|
||
host.wake_assignee(issue.id) # debounced
|
||
if category in {backlog, blocked, terminal} and tracker_driven_run_active(link):
|
||
if host.capabilities.issueScopedRunStop:
|
||
host.request_issue_run_stop(link.issueId)
|
||
else:
|
||
health.record(issue_run_stop_unsupported) # let this run finish
|
||
if category == terminal and issue_active(link.issueId):
|
||
conflicts.open(link, kind=closure) # Section 7.4
|
||
|
||
on issue_status_change(issue) where linked(issue):
|
||
outbound.project_status(link, issue.status) # Section 10.3, policy-gated
|
||
outbound.post_milestone_comment_if_due(link, issue)
|
||
```
|
||
|
||
### 15.4 Reconciliation Tick
|
||
|
||
```
|
||
every reconcile_interval per connector_instance:
|
||
for link in links.window(cursor): # rate-bounded slice
|
||
state = adapter.getTaskStates([link.externalTaskId])
|
||
if missing(state): conflicts.open(link, kind=deletion); continue
|
||
diff = three_way(link.baseSnapshot, state, host.issue(link.issueId))
|
||
apply_owned_changes(diff) # Section 11.1
|
||
queue_conflicts(diff)
|
||
link.lastReconcileAt = now
|
||
health.record(reconcile_ok, cursor)
|
||
```
|
||
|
||
## 16. Conformance Checklist (Definition of Done)
|
||
|
||
### 16.1 REQUIRED for Core Conformance (Layers 1–2)
|
||
|
||
- Task Source Adapter with `listCandidateTasks`, `getTask`, `getTaskStates`, `listComments`,
|
||
webhook verification or documented poll-only posture
|
||
- Static capability declaration consumed by host UX
|
||
- Normalized `ExternalTask` output with provider envelope separation
|
||
- Task link store with the Section 4.1.3 invariants and Section 6.1 state machine
|
||
- Idempotent inbound apply (event idempotency keys + transactional cursors)
|
||
- Deduplicated link creation (Section 6.3)
|
||
- External deletion handled non-destructively (Section 6.4)
|
||
- Explicit state-category mapping with unmapped-state safety (Section 7.2)
|
||
- Comment import with provenance and untrusted-content handling (Section 7.3)
|
||
- Routing pipeline with guaranteed triage fallback (Section 8)
|
||
- Reconciliation job (Section 7.1.3)
|
||
- Health and link surfaces (Sections 12.2, 12.3)
|
||
- Secret-ref credentials, company-boundary enforcement, audit attribution (Section 14)
|
||
|
||
### 16.2 REQUIRED for Symphony Profile Conformance (Layer 3)
|
||
|
||
- `runMode=symphony_compatible` honoring Section 9.1 dispatch (wake, never spawn)
|
||
- Checkout-as-claim with no parallel claim registry (Section 9.2)
|
||
- External de-activation stops tracker-driven dispatch and uses an issue-scoped host run-stop
|
||
capability for an active issue run (Section 9.3)
|
||
- Debounced wake requests (Section 9.3)
|
||
- Task packet rendering with strict variables and untrusted marking (Section 9.5.1)
|
||
- Workflow policy lookup from at least one source with lowest-precedence guarantee (Section 9.5)
|
||
- Claim/run/retry/release state visible per link (Section 12.3)
|
||
|
||
### 16.3 REQUIRED for Write Conformance (Layer 4)
|
||
|
||
- Outbound writes only via Outbound Write Engine / connector tools (Section 10.1)
|
||
- Policy toggles with Section 10.2 defaults, including transcripts off
|
||
- Idempotent outbound effects
|
||
- Status projection rules including review-handoff and gated closure (Section 10.3)
|
||
- Service-account attribution, echo suppression, and cross-instance loop markers (Section 10.4)
|
||
- Three-way conflict detection with resolution actions and base-snapshot advance (Section 11)
|
||
|
||
### 16.4 RECOMMENDED Extensions (Not REQUIRED for Conformance)
|
||
|
||
- External-side claim/run state projection into provider custom fields
|
||
- `user_mapping` routing and "assign back to me" flows
|
||
- Lossless rich-text round-trip per provider
|
||
- Comment-command verb extensions (e.g. `/paperclip plan`, `/paperclip qa`)
|
||
- Promotion of task links from plugin state to core schema once two providers validate the model
|
||
|
||
## Appendix A. Provider Profiles
|
||
|
||
Each profile lists container coordinates, envelope content, state-mapping notes, and known write
|
||
constraints. Profiles are informative; the normative contract is Sections 4–14.
|
||
|
||
### A.1 Linear
|
||
|
||
- Containers: team, project, cycle. Envelope: issue key, workflow state id/type, labels with ids,
|
||
branch name, relations, estimates, cycle/project ids.
|
||
- State mapping: Linear workflow state `type` (`backlog`, `unstarted`, `started`, `completed`,
|
||
`canceled`) gives a reliable default category mapping; per-team overrides supported.
|
||
- Webhooks: signed; verification REQUIRED. API: GraphQL; pagination REQUIRED; keep queries
|
||
isolated (schema drift).
|
||
- Notes: closest provider to the Symphony reference; RECOMMENDED first connector.
|
||
|
||
### A.2 Jira
|
||
|
||
- Containers: site/cloud id, project key. Envelope: issue type, workflow transition ids, status
|
||
category, components, epic/parent links, sprints, custom fields by id.
|
||
- State mapping: map via Jira status categories (`To Do`, `In Progress`, `Done`) plus named-status
|
||
overrides; transitions require transition ids, so `updateTaskState` resolves transitions at
|
||
apply time.
|
||
- Webhooks: available (signed via secret on Cloud); polling backstop RECOMMENDED.
|
||
- Notes: per-project workflow variance is the main mapping risk; setup MUST validate against live
|
||
workflow metadata.
|
||
|
||
### A.3 Asana
|
||
|
||
- Containers: workspace, project, section. Envelope: section memberships, custom fields,
|
||
followers, multi-project memberships.
|
||
- State mapping: Asana has completion plus sections; map sections→categories per project;
|
||
completion maps to `terminal`.
|
||
- Webhooks: handshake + HMAC signatures; verification REQUIRED.
|
||
- Notes: multi-project membership means one task can match several containers — the link binds to
|
||
the connector instance's configured container.
|
||
|
||
### A.4 Notion
|
||
|
||
- Containers: database id. Envelope: property schema snapshot, select/status property values,
|
||
relation properties, block-content references.
|
||
- State mapping: a designated status/select property maps to categories; databases without one
|
||
are import/browse only (no Symphony profile).
|
||
- Webhooks: limited; polling with `last_edited_time` high-water mark is the primary channel.
|
||
- Notes: rich-text conversion is lossy; envelope SHOULD retain block references for backlinks.
|
||
|
||
### A.5 Trello
|
||
|
||
- Containers: board, list. Envelope: list id, checklists, members, power-up metadata, card
|
||
position.
|
||
- State mapping: lists are lanes; map lists→categories (`Paperclip: QA` style lanes also drive
|
||
`state_lane` routing).
|
||
- Webhooks: available with callback verification; signatures via content digest.
|
||
- Notes: no native blockers/priority; both normalize to null/empty with envelope passthrough.
|
||
|
||
### A.6 GitHub Issues
|
||
|
||
- Containers: repository (and optionally Projects v2). Envelope: labels, milestones, assignees,
|
||
linked PR references, reactions.
|
||
- State mapping: open/closed plus label conventions or Projects v2 status field for richer
|
||
categories.
|
||
- Webhooks: HMAC-signed; verification REQUIRED.
|
||
- Notes: PR linkage is first-class here; `postArtifactLinks` SHOULD use native cross-references.
|
||
|
||
## Appendix B. Symphony Spec Crosswalk
|
||
|
||
| Symphony SPEC.md section | This document |
|
||
| --- | --- |
|
||
| 1 Problem Statement | 1 |
|
||
| 2 Goals / Non-Goals | 2 |
|
||
| 3 System Overview | 3 |
|
||
| 4 Core Domain Model (Issue, Run Attempt, Retry Entry, Runtime State) | 4 (`ExternalTask`, `TaskLink`, host run state via 9.3) |
|
||
| 5 Workflow Specification (`WORKFLOW.md`) | 9.5 (layered workflow policy) |
|
||
| 6 Configuration Specification | connector instance configuration (3.4, 5.2, 7.2, 10.2) |
|
||
| 7 Orchestration State Machine | 6.1 (link), 9.3 (claim/run/retry/release on host primitives) |
|
||
| 8 Polling, Scheduling, Reconciliation | 7.1, 9.1, 15.4 |
|
||
| 9 Workspace Management and Safety | 9.4 (host execution workspaces) |
|
||
| 10 Agent Runner Protocol | host heartbeat runtime (out of scope here; see 9.1 boundary) |
|
||
| 11 Issue Tracker Integration Contract | 5 (Task Source Adapter contract) |
|
||
| 11.5 Tracker Writes boundary | 10.1 |
|
||
| 12 Prompt Construction | 9.5.1 (task packet rendering) |
|
||
| 13 Logging, Status, Observability | 12 |
|
||
| 14 Failure Model and Recovery | 13 |
|
||
| 15 Security and Operational Safety | 14 |
|
||
| 16 Reference Algorithms | 15 |
|
||
| 17–18 Test Matrix / Implementation Checklist | 16 |
|
||
| Appendix A SSH Worker | not applicable (host owns execution substrate) |
|
||
|
||
Differences are deliberate: Symphony assumes one tracker, one repo, and coding agents only, with
|
||
the orchestrator as a standalone daemon. This protocol assumes a multi-tenant control plane that
|
||
already owns claims, runs, workspaces, and governance — so the connector's scope shrinks to
|
||
normalization, linking, routing, sync policy, and observability, and Symphony's
|
||
orchestrator/runner sections collapse onto existing host primitives.
|